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
@@ -86,10 +86,9 @@ module ReactOnRails
86
86
  puts
87
87
  @printed_once = true
88
88
 
89
- if ReactOnRails::PackerUtils.using_packer? &&
90
- ReactOnRails::Utils.using_packer_source_path_is_not_defined_and_custom_node_modules?
89
+ if ReactOnRails::Utils.using_packer_source_path_is_not_defined_and_custom_node_modules?
91
90
  msg = <<-MSG.strip_heredoc
92
- WARNING: Define config/#{ReactOnRails::PackerUtils.packer_type}.yml to include sourcePath to configure
91
+ WARNING: Define config/shakapacker.yml to include sourcePath to configure
93
92
  the location of your JavaScript source for React on Rails.
94
93
  Default location of #{source_path} is used.
95
94
  MSG
@@ -6,6 +6,7 @@ require "rainbow"
6
6
  require "active_support"
7
7
  require "active_support/core_ext/string"
8
8
 
9
+ # rubocop:disable Metrics/ModuleLength
9
10
  module ReactOnRails
10
11
  module Utils
11
12
  TRUNCATION_FILLER = "\n... TRUNCATED #{
@@ -57,6 +58,8 @@ module ReactOnRails
57
58
  MSG
58
59
 
59
60
  puts wrap_message(msg)
61
+ puts ""
62
+ puts default_troubleshooting_section
60
63
 
61
64
  # Rspec catches exit without! in the exit callbacks
62
65
  exit!(1)
@@ -68,44 +71,107 @@ module ReactOnRails
68
71
  server_bundle_js_file_path =~ %r{https?://}
69
72
  end
70
73
 
74
+ def self.bundle_js_file_path(bundle_name)
75
+ # Priority order depends on bundle type:
76
+ # SERVER BUNDLES (normal case): Try private non-public locations first, then manifest, then legacy
77
+ # CLIENT BUNDLES (normal case): Try manifest first, then fallback locations
78
+ if bundle_name == "manifest.json"
79
+ # Default to the non-hashed name in the specified output directory, which, for legacy
80
+ # React on Rails, this is the output directory picked up by the asset pipeline.
81
+ # For Shakapacker, this is the public output path defined in the (shaka/web)packer.yml file.
82
+ File.join(public_bundles_full_path, bundle_name)
83
+ else
84
+ bundle_js_file_path_with_packer(bundle_name)
85
+ end
86
+ end
87
+
88
+ private_class_method def self.bundle_js_file_path_with_packer(bundle_name)
89
+ is_server_bundle = server_bundle?(bundle_name)
90
+ config = ReactOnRails.configuration
91
+ root_path = Rails.root || "."
92
+
93
+ # If server bundle and server_bundle_output_path is configured, return that path directly
94
+ if is_server_bundle && config.server_bundle_output_path.present?
95
+ private_server_bundle_path = File.expand_path(File.join(root_path, config.server_bundle_output_path,
96
+ bundle_name))
97
+
98
+ # Don't fall back to public directory if enforce_private_server_bundles is enabled
99
+ if config.enforce_private_server_bundles || File.exist?(private_server_bundle_path)
100
+ return private_server_bundle_path
101
+ end
102
+ end
103
+
104
+ # Try manifest lookup for all bundles
105
+ begin
106
+ ReactOnRails::PackerUtils.bundle_js_uri_from_packer(bundle_name)
107
+ rescue Shakapacker::Manifest::MissingEntryError
108
+ handle_missing_manifest_entry(bundle_name, is_server_bundle)
109
+ end
110
+ end
111
+
112
+ private_class_method def self.server_bundle?(bundle_name)
113
+ config = ReactOnRails.configuration
114
+ bundle_name == config.server_bundle_js_file ||
115
+ bundle_name == config.rsc_bundle_js_file ||
116
+ bundle_name == config.react_server_client_manifest_file
117
+ end
118
+
119
+ private_class_method def self.handle_missing_manifest_entry(bundle_name, is_server_bundle)
120
+ config = ReactOnRails.configuration
121
+ root_path = Rails.root || "."
122
+
123
+ # For server bundles with server_bundle_output_path configured, use that
124
+ if is_server_bundle && config.server_bundle_output_path.present?
125
+ candidate_paths = [File.expand_path(File.join(root_path, config.server_bundle_output_path, bundle_name))]
126
+ unless config.enforce_private_server_bundles
127
+ candidate_paths << File.expand_path(File.join(ReactOnRails::PackerUtils.packer_public_output_path,
128
+ bundle_name))
129
+ end
130
+
131
+ candidate_paths.each do |path|
132
+ return path if File.exist?(path)
133
+ end
134
+ return candidate_paths.first
135
+ end
136
+
137
+ # For client bundles and server bundles without special config, use packer's public path
138
+ # This returns the environment-specific path configured in shakapacker.yml
139
+ File.expand_path(File.join(ReactOnRails::PackerUtils.packer_public_output_path, bundle_name))
140
+ end
141
+
71
142
  def self.server_bundle_js_file_path
72
- # Either:
73
- # 1. Using same bundle for both server and client, so server bundle will be hashed in manifest
74
- # 2. Using a different bundle (different Webpack config), so file is not hashed, and
75
- # bundle_js_path will throw so the default path is used without a hash.
76
- # 3. The third option of having the server bundle hashed and a different configuration than
77
- # the client bundle is not supported for 2 reasons:
78
- # a. The webpack manifest plugin would have a race condition where the same manifest.json
79
- # is edited by both the webpack-dev-server
80
- # b. There is no good reason to hash the server bundle name.
81
143
  return @server_bundle_path if @server_bundle_path && !Rails.env.development?
82
144
 
83
145
  bundle_name = ReactOnRails.configuration.server_bundle_js_file
84
- @server_bundle_path = if ReactOnRails::PackerUtils.using_packer?
85
- begin
86
- bundle_js_file_path(bundle_name)
87
- rescue Object.const_get(
88
- ReactOnRails::PackerUtils.packer_type.capitalize
89
- )::Manifest::MissingEntryError
90
- File.expand_path(
91
- File.join(ReactOnRails::PackerUtils.packer_public_output_path,
92
- bundle_name)
93
- )
94
- end
95
- else
96
- bundle_js_file_path(bundle_name)
97
- end
146
+ @server_bundle_path = bundle_js_file_path(bundle_name)
98
147
  end
99
148
 
100
- def self.bundle_js_file_path(bundle_name)
101
- if ReactOnRails::PackerUtils.using_packer? && bundle_name != "manifest.json"
102
- ReactOnRails::PackerUtils.bundle_js_uri_from_packer(bundle_name)
103
- else
104
- # Default to the non-hashed name in the specified output directory, which, for legacy
105
- # React on Rails, this is the output directory picked up by the asset pipeline.
106
- # For Shakapacker, this is the public output path defined in the (shaka/web)packer.yml file.
107
- File.join(generated_assets_full_path, bundle_name)
149
+ def self.rsc_bundle_js_file_path
150
+ return @rsc_bundle_path if @rsc_bundle_path && !Rails.env.development?
151
+
152
+ bundle_name = ReactOnRails.configuration.rsc_bundle_js_file
153
+ @rsc_bundle_path = bundle_js_file_path(bundle_name)
154
+ end
155
+
156
+ def self.react_client_manifest_file_path
157
+ return @react_client_manifest_path if @react_client_manifest_path && !Rails.env.development?
158
+
159
+ file_name = ReactOnRails.configuration.react_client_manifest_file
160
+ @react_client_manifest_path = ReactOnRails::PackerUtils.asset_uri_from_packer(file_name)
161
+ end
162
+
163
+ # React Server Manifest is generated by the server bundle.
164
+ # So, it will never be served from the dev server.
165
+ def self.react_server_client_manifest_file_path
166
+ return @react_server_manifest_path if @react_server_manifest_path && !Rails.env.development?
167
+
168
+ asset_name = ReactOnRails.configuration.react_server_client_manifest_file
169
+ if asset_name.nil?
170
+ raise ReactOnRails::Error,
171
+ "react_server_client_manifest_file is nil, ensure it is set in your configuration"
108
172
  end
173
+
174
+ @react_server_manifest_path = bundle_js_file_path(asset_name)
109
175
  end
110
176
 
111
177
  def self.running_on_windows?
@@ -133,26 +199,21 @@ module ReactOnRails
133
199
  end
134
200
 
135
201
  def self.source_path
136
- if ReactOnRails::PackerUtils.using_packer?
137
- ReactOnRails::PackerUtils.packer_source_path
138
- else
139
- ReactOnRails.configuration.node_modules_location
140
- end
202
+ ReactOnRails::PackerUtils.packer_source_path
141
203
  end
142
204
 
143
205
  def self.using_packer_source_path_is_not_defined_and_custom_node_modules?
144
- return false unless ReactOnRails::PackerUtils.using_packer?
145
-
146
206
  !ReactOnRails::PackerUtils.packer_source_path_explicit? &&
147
207
  ReactOnRails.configuration.node_modules_location.present?
148
208
  end
149
209
 
210
+ def self.public_bundles_full_path
211
+ ReactOnRails::PackerUtils.packer_public_output_path
212
+ end
213
+
214
+ # DEPRECATED: Use public_bundles_full_path for clarity about public vs private bundle paths
150
215
  def self.generated_assets_full_path
151
- if ReactOnRails::PackerUtils.using_packer?
152
- ReactOnRails::PackerUtils.packer_public_output_path
153
- else
154
- File.expand_path(ReactOnRails.configuration.generated_assets_dir)
155
- end
216
+ public_bundles_full_path
156
217
  end
157
218
 
158
219
  def self.gem_available?(name)
@@ -185,6 +246,30 @@ module ReactOnRails
185
246
  end
186
247
  end
187
248
 
249
+ def self.react_on_rails_pro_licence_valid?
250
+ return @react_on_rails_pro_licence_valid if defined?(@react_on_rails_pro_licence_valid)
251
+
252
+ @react_on_rails_pro_licence_valid = begin
253
+ return false unless react_on_rails_pro?
254
+
255
+ # Maintain compatibility with legacy versions of React on Rails Pro:
256
+ # Earlier releases did not require license validation, as they were distributed as private gems.
257
+ # This check ensures that the method works correctly regardless of the installed version.
258
+ return true unless ReactOnRailsPro::Utils.respond_to?(:licence_valid?)
259
+
260
+ ReactOnRailsPro::Utils.licence_valid?
261
+ end
262
+ end
263
+
264
+ def self.rsc_support_enabled?
265
+ return false unless react_on_rails_pro?
266
+
267
+ return @rsc_support_enabled if defined?(@rsc_support_enabled)
268
+
269
+ rorp_config = ReactOnRailsPro.configuration
270
+ @rsc_support_enabled = rorp_config.respond_to?(:enable_rsc_support) && rorp_config.enable_rsc_support
271
+ end
272
+
188
273
  def self.full_text_errors_enabled?
189
274
  ENV["FULL_TEXT_ERRORS"] == "true"
190
275
  end
@@ -226,5 +311,16 @@ module ReactOnRails
226
311
 
227
312
  puts "Prepended\n#{text_to_prepend}to #{file}."
228
313
  end
314
+
315
+ def self.default_troubleshooting_section
316
+ <<~DEFAULT
317
+ 📞 Get Help & Support:
318
+ • 🚀 Professional Support: react_on_rails@shakacode.com (fastest resolution)
319
+ • 💬 React + Rails Slack: https://invite.reactrails.com
320
+ • 🆓 GitHub Issues: https://github.com/shakacode/react_on_rails/issues
321
+ • 📖 Discussions: https://github.com/shakacode/react_on_rails/discussions
322
+ DEFAULT
323
+ end
229
324
  end
230
325
  end
326
+ # rubocop:enable Metrics/ModuleLength
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ReactOnRails
4
- VERSION = "14.2.1"
4
+ VERSION = "16.1.1"
5
5
  end
@@ -6,7 +6,8 @@ module ReactOnRails
6
6
  class VersionChecker
7
7
  attr_reader :node_package_version
8
8
 
9
- MAJOR_MINOR_PATCH_VERSION_REGEX = /(\d+)\.(\d+)\.(\d+)/
9
+ # Semver uses - to separate pre-release, but RubyGems use .
10
+ VERSION_PARTS_REGEX = /(\d+)\.(\d+)\.(\d+)(?:[-.]([0-9A-Za-z.-]+))?/
10
11
 
11
12
  def self.build
12
13
  new(NodePackageVersion.build)
@@ -23,13 +24,7 @@ module ReactOnRails
23
24
  return if node_package_version.raw.nil? || node_package_version.local_path_or_url?
24
25
  return log_node_semver_version_warning if node_package_version.semver_wildcard?
25
26
 
26
- node_major_minor_patch = node_package_version.major_minor_patch
27
- gem_major_minor_patch = gem_major_minor_patch_version
28
- versions_match = node_major_minor_patch[0] == gem_major_minor_patch[0] &&
29
- node_major_minor_patch[1] == gem_major_minor_patch[1] &&
30
- node_major_minor_patch[2] == gem_major_minor_patch[2]
31
-
32
- log_differing_versions_warning unless versions_match
27
+ log_differing_versions_warning unless node_package_version.parts == gem_version_parts
33
28
  end
34
29
 
35
30
  private
@@ -39,20 +34,20 @@ module ReactOnRails
39
34
  Detected: #{node_package_version.raw}
40
35
  gem: #{gem_version}
41
36
  Ensure the installed version of the gem is the same as the version of
42
- your installed node package. Do not use >= or ~> in your Gemfile for react_on_rails.
43
- Do not use ^ or ~ in your package.json for react-on-rails.
37
+ your installed Node package. Do not use >= or ~> in your Gemfile for react_on_rails.
38
+ Do not use ^, ~, or other non-exact versions in your package.json for react-on-rails.
44
39
  Run `yarn add react-on-rails --exact` in the directory containing folder node_modules.
45
40
  MSG
46
41
  end
47
42
 
48
43
  def log_differing_versions_warning
49
- msg = "**WARNING** ReactOnRails: ReactOnRails gem and node package versions do not match\n#{common_error_msg}"
44
+ msg = "**WARNING** ReactOnRails: ReactOnRails gem and Node package versions do not match\n#{common_error_msg}"
50
45
  Rails.logger.warn(msg)
51
46
  end
52
47
 
53
48
  def log_node_semver_version_warning
54
- msg = "**WARNING** ReactOnRails: Your node package version for react-on-rails contains a " \
55
- "^ or ~\n#{common_error_msg}"
49
+ msg = "**WARNING** ReactOnRails: Your Node package version for react-on-rails is not an exact version\n" \
50
+ "#{common_error_msg}"
56
51
  Rails.logger.warn(msg)
57
52
  end
58
53
 
@@ -60,9 +55,8 @@ module ReactOnRails
60
55
  ReactOnRails::VERSION
61
56
  end
62
57
 
63
- def gem_major_minor_patch_version
64
- match = gem_version.match(MAJOR_MINOR_PATCH_VERSION_REGEX)
65
- [match[1], match[2], match[3]]
58
+ def gem_version_parts
59
+ gem_version.match(VERSION_PARTS_REGEX)&.captures&.compact
66
60
  end
67
61
 
68
62
  class NodePackageVersion
@@ -100,7 +94,7 @@ module ReactOnRails
100
94
  # See https://docs.npmjs.com/cli/v10/configuring-npm/package-json#dependencies
101
95
  # We want to disallow all expressions other than exact versions
102
96
  # and the ones allowed by local_path_or_url?
103
- raw.blank? || raw.match(/[~^><|*-]/).present?
97
+ raw.blank? || raw.start_with?(/[~^><*]/) || raw.include?(" - ") || raw.include?(" || ")
104
98
  end
105
99
 
106
100
  def local_path_or_url?
@@ -110,15 +104,15 @@ module ReactOnRails
110
104
  !raw.nil? && raw.include?("/") && !raw.start_with?("npm:")
111
105
  end
112
106
 
113
- def major_minor_patch
107
+ def parts
114
108
  return if local_path_or_url?
115
109
 
116
- match = raw.match(MAJOR_MINOR_PATCH_VERSION_REGEX)
110
+ match = raw.match(VERSION_PARTS_REGEX)
117
111
  unless match
118
112
  raise ReactOnRails::Error, "Cannot parse version number '#{raw}' (only exact versions are supported)"
119
113
  end
120
114
 
121
- [match[1], match[2], match[3]]
115
+ match.captures.compact
122
116
  end
123
117
 
124
118
  private
@@ -5,7 +5,7 @@ require_relative "version"
5
5
  module ReactOnRails
6
6
  class VersionSyntaxConverter
7
7
  def rubygem_to_npm(rubygem_version = ReactOnRails::VERSION)
8
- regex_match = rubygem_version.match(/(\d+\.\d+\.\d+)[.\-]?(.+)?/)
8
+ regex_match = rubygem_version.match(/(\d+\.\d+\.\d+)[.-]?(.+)?/)
9
9
  return "#{regex_match[1]}-#{regex_match[2]}" if regex_match[2]
10
10
 
11
11
  regex_match[1].to_s
@@ -26,3 +26,4 @@ require "react_on_rails/test_helper/ensure_assets_compiled"
26
26
  require "react_on_rails/locales/base"
27
27
  require "react_on_rails/locales/to_js"
28
28
  require "react_on_rails/locales/to_json"
29
+ require "react_on_rails/dev"
@@ -26,7 +26,7 @@ namespace :react_on_rails do
26
26
  else
27
27
  # Left in this warning message in case this rake task is run directly
28
28
  msg = <<~MSG
29
- React on Rails is aborting webpack compilation from task react_on_rails:assets:webpack
29
+ React on Rails is aborting Webpack compilation from task react_on_rails:assets:webpack
30
30
  because you do not have the `config.build_production_command` defined.
31
31
  MSG
32
32
  puts Rainbow(msg).red
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../react_on_rails"
4
+ require_relative "../react_on_rails/doctor"
5
+
6
+ begin
7
+ require "rainbow"
8
+ rescue LoadError
9
+ # Fallback if Rainbow is not available
10
+ class Rainbow
11
+ def self.method_missing(_method, text)
12
+ SimpleColorWrapper.new(text)
13
+ end
14
+
15
+ def self.respond_to_missing?(_method, _include_private = false)
16
+ true
17
+ end
18
+ end
19
+
20
+ class SimpleColorWrapper
21
+ def initialize(text)
22
+ @text = text
23
+ end
24
+
25
+ def method_missing(_method, *_args)
26
+ self
27
+ end
28
+
29
+ def respond_to_missing?(_method, _include_private = false)
30
+ true
31
+ end
32
+
33
+ def to_s
34
+ @text
35
+ end
36
+ end
37
+ end
38
+
39
+ namespace :react_on_rails do
40
+ desc "Diagnose React on Rails setup and configuration"
41
+ task :doctor do
42
+ verbose = ENV["VERBOSE"] == "true"
43
+ fix = ENV["FIX"] == "true"
44
+
45
+ doctor = ReactOnRails::Doctor.new(verbose: verbose, fix: fix)
46
+ doctor.run_diagnosis
47
+ end
48
+ end
@@ -1,11 +1,168 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rubocop:disable Metrics/BlockLength
3
4
  namespace :react_on_rails do
4
5
  desc <<~DESC
5
6
  If there is a file inside any directory matching config.components_subdirectory, this command generates corresponding packs.
7
+
8
+ This task will:
9
+ - Clean out existing generated directories (javascript/generated and javascript/packs/generated)
10
+ - List all files being deleted for transparency
11
+ - Generate new pack files for discovered React components
12
+ - Skip generation if files are already up to date
13
+
14
+ Generated directories:
15
+ - app/javascript/packs/generated/ (client pack files)
16
+ - app/javascript/generated/ (server bundle files)
6
17
  DESC
7
18
 
8
19
  task generate_packs: :environment do
9
- ReactOnRails::PacksGenerator.instance.generate_packs_if_stale
20
+ puts Rainbow("🚀 Starting React on Rails pack generation...").bold
21
+ puts Rainbow("📁 Auto-load bundle: #{ReactOnRails.configuration.auto_load_bundle}").cyan
22
+ puts Rainbow("📂 Components subdirectory: #{ReactOnRails.configuration.components_subdirectory}").cyan
23
+ puts ""
24
+
25
+ begin
26
+ start_time = Time.now
27
+ ReactOnRails::PacksGenerator.instance.generate_packs_if_stale
28
+ end_time = Time.now
29
+
30
+ puts ""
31
+ puts Rainbow("✨ Pack generation completed in #{((end_time - start_time) * 1000).round(1)}ms").green
32
+ rescue ReactOnRails::Error => e
33
+ handle_react_on_rails_error(e)
34
+ exit 1
35
+ rescue StandardError => e
36
+ handle_standard_error(e)
37
+ exit 1
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ # rubocop:disable Metrics/AbcSize
44
+ def handle_react_on_rails_error(error)
45
+ puts ""
46
+ puts Rainbow("❌ REACT ON RAILS ERROR").red.bold
47
+ puts Rainbow("=" * 80).red
48
+ puts Rainbow("🚨 Pack generation failed with the following error:").red
49
+ puts ""
50
+ puts Rainbow("📋 ERROR DETAILS:").yellow
51
+ puts Rainbow(" Type: #{error.class.name}").white
52
+ puts Rainbow(" Message: #{error.message}").white
53
+ puts ""
54
+
55
+ highlight_main_error(error)
56
+ show_common_solutions(error)
57
+ show_debugging_steps
58
+ show_documentation_links
59
+ end
60
+ # rubocop:enable Metrics/AbcSize
61
+
62
+ def highlight_main_error(error)
63
+ return unless error.message.include?("**ERROR**")
64
+
65
+ error_lines = error.message.split("\n")
66
+ error_lines.each do |line|
67
+ next unless line.include?("**ERROR**")
68
+
69
+ puts Rainbow("🔥 MAIN ISSUE:").red.bold
70
+ puts Rainbow(" #{line.gsub('**ERROR**', '').strip}").yellow
71
+ end
72
+ end
73
+
74
+ # rubocop:disable Metrics/AbcSize
75
+ def show_common_solutions(error)
76
+ puts ""
77
+ puts Rainbow("💡 COMMON SOLUTIONS:").green.bold
78
+
79
+ case error.message
80
+ when /client specific definition.*overrides the common definition/
81
+ puts Rainbow(" • You have both common and client/server specific component files").white
82
+ puts Rainbow(" • Delete the common component file (e.g., Component.jsx)").white
83
+ puts Rainbow(" • Keep only the client/server specific files " \
84
+ "(Component.client.jsx, Component.server.jsx)").white
85
+ puts Rainbow(" • See: https://www.shakacode.com/react-on-rails/docs/guides/" \
86
+ "auto-bundling-file-system-based-automated-bundle-generation.md").cyan
87
+
88
+ when /Cannot find component/
89
+ puts Rainbow(" • Check that your component file exists in the expected location").white
90
+ puts Rainbow(" • Verify the component is exported as default export").white
91
+ puts Rainbow(" • Ensure the file extension is .jsx or .js").white
92
+
93
+ when /CSS module.*not found/
94
+ puts Rainbow(" • Check that the CSS module file exists").white
95
+ puts Rainbow(" • Verify the import path is correct").white
96
+ puts Rainbow(" • Ensure all CSS classes referenced in the component exist").white
97
+
98
+ else
99
+ puts Rainbow(" • Check component file structure and naming").white
100
+ puts Rainbow(" • Verify all imports and exports are correct").white
101
+ puts Rainbow(" • Run with --trace for more detailed error information").white
102
+ end
103
+ end
104
+ # rubocop:enable Metrics/AbcSize
105
+
106
+ def show_debugging_steps
107
+ puts ""
108
+ puts Rainbow("🔧 DEBUGGING STEPS:").blue.bold
109
+ components_path = "app/javascript/src/**/#{ReactOnRails.configuration.components_subdirectory}/"
110
+ puts Rainbow(" 1. Check component files in: #{components_path}").white
111
+ puts Rainbow(" 2. Verify component exports: export default ComponentName").white
112
+ puts Rainbow(" 3. Check for conflicting common/client/server files").white
113
+ puts Rainbow(" 4. Run: rake react_on_rails:generate_packs --trace").white
114
+ puts Rainbow(" 5. Check Rails logs for additional details").white
115
+ end
116
+
117
+ def show_documentation_links
118
+ puts ""
119
+ puts Rainbow("📚 DOCUMENTATION:").magenta.bold
120
+ puts Rainbow(" • File-system based components: https://www.shakacode.com/react-on-rails/docs/" \
121
+ "guides/auto-bundling-file-system-based-automated-bundle-generation.md").cyan
122
+ puts Rainbow(" • Component registration: https://www.shakacode.com/react-on-rails/docs/").cyan
123
+ puts Rainbow("=" * 80).red
124
+ end
125
+
126
+ def show_help_and_support
127
+ puts ""
128
+ troubleshooting_content = ReactOnRails::Utils.default_troubleshooting_section
129
+ # Display the troubleshooting content with color formatting
130
+ troubleshooting_content.split("\n").each do |line|
131
+ case line
132
+ when /^📞/
133
+ puts Rainbow(line).magenta.bold
134
+ when /^\s*•\s*🚀/
135
+ puts Rainbow(line).yellow
136
+ when /^\s*•/
137
+ puts Rainbow(line).cyan
138
+ else
139
+ puts Rainbow(line).white unless line.strip.empty?
140
+ end
141
+ end
142
+ end
143
+
144
+ # rubocop:disable Metrics/AbcSize
145
+ def handle_standard_error(error)
146
+ puts ""
147
+ puts Rainbow("❌ UNEXPECTED ERROR").red.bold
148
+ puts Rainbow("=" * 80).red
149
+ puts Rainbow("🚨 An unexpected error occurred during pack generation:").red
150
+ puts ""
151
+ puts Rainbow("📋 ERROR DETAILS:").yellow
152
+ puts Rainbow(" Type: #{error.class.name}").white
153
+ puts Rainbow(" Message: #{error.message}").white
154
+ puts Rainbow(" Backtrace:").white
155
+ error.backtrace.first(10).each { |line| puts Rainbow(" #{line}").white }
156
+ puts Rainbow(" ... (run with --trace for full backtrace)").white
157
+ puts ""
158
+ puts Rainbow("🔧 DEBUGGING STEPS:").blue.bold
159
+ puts Rainbow(" 1. Run: rake react_on_rails:generate_packs --trace").white
160
+ puts Rainbow(" 2. Check Rails logs: tail -f log/development.log").white
161
+ puts Rainbow(" 3. Verify all dependencies are installed: bundle install && npm install").white
162
+ puts Rainbow(" 4. Clear cache: rm -rf tmp/cache").white
163
+ show_help_and_support
164
+ puts Rainbow("=" * 80).red
10
165
  end
166
+ # rubocop:enable Metrics/AbcSize
11
167
  end
168
+ # rubocop:enable Metrics/BlockLength
@@ -31,6 +31,7 @@ Gem::Specification.new do |s|
31
31
  s.add_dependency "execjs", "~> 2.5"
32
32
  s.add_dependency "rails", ">= 5.2"
33
33
  s.add_dependency "rainbow", "~> 3.0"
34
+ s.add_dependency "shakapacker", ">= 6.0"
34
35
 
35
36
  s.add_development_dependency "gem-release"
36
37
  s.post_install_message = '
@@ -0,0 +1,6 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "allowSyntheticDefaultImports": true
5
+ }
6
+ }
data/tsconfig.json CHANGED
@@ -6,12 +6,14 @@
6
6
  // needed for Jest tests even though we don't use .tsx
7
7
  "jsx": "react-jsx",
8
8
  "lib": ["dom", "es2020"],
9
- "module": "node16",
10
9
  "noImplicitAny": true,
11
10
  "outDir": "node_package/lib",
11
+ "allowImportingTsExtensions": true,
12
+ "rewriteRelativeImportExtensions": true,
12
13
  "strict": true,
13
14
  "incremental": true,
14
- "target": "es2020"
15
+ "target": "es2020",
16
+ "typeRoots": ["./node_modules/@types", "./node_package/types"]
15
17
  },
16
- "include": ["node_package/src/**/*"]
18
+ "include": ["node_package/src/**/*", "node_package/types/**/*"]
17
19
  }