react_on_rails 16.1.2 → 16.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rspec +2 -0
- data/.rubocop.yml +85 -0
- data/Gemfile.development_dependencies +8 -7
- data/Gemfile.lock +158 -119
- data/Steepfile +56 -0
- data/lib/generators/react_on_rails/base_generator.rb +43 -120
- data/lib/generators/react_on_rails/dev_tests_generator.rb +2 -1
- data/lib/generators/react_on_rails/generator_helper.rb +102 -2
- data/lib/generators/react_on_rails/install_generator.rb +36 -156
- data/lib/generators/react_on_rails/js_dependency_manager.rb +383 -0
- data/lib/generators/react_on_rails/templates/base/base/.dev-services.yml.example +76 -0
- data/lib/generators/react_on_rails/templates/base/base/bin/shakapacker-precompile-hook +30 -0
- data/lib/generators/react_on_rails/templates/base/base/bin/switch-bundler +141 -0
- data/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt +44 -45
- data/lib/generators/react_on_rails/templates/base/base/config/{shakapacker.yml → shakapacker.yml.tt} +28 -3
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/development.js.tt +15 -9
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/serverWebpackConfig.js.tt +42 -6
- data/lib/react_on_rails/configuration.rb +149 -32
- data/lib/react_on_rails/controller.rb +3 -3
- data/lib/react_on_rails/dev/pack_generator.rb +168 -2
- data/lib/react_on_rails/dev/process_manager.rb +136 -14
- data/lib/react_on_rails/dev/server_manager.rb +194 -26
- data/lib/react_on_rails/dev/service_checker.rb +200 -0
- data/lib/react_on_rails/doctor.rb +341 -12
- data/lib/react_on_rails/engine.rb +75 -1
- data/lib/react_on_rails/git_utils.rb +3 -1
- data/lib/react_on_rails/helper.rb +70 -192
- data/lib/react_on_rails/locales/base.rb +17 -5
- data/lib/react_on_rails/packer_utils.rb +79 -2
- data/lib/react_on_rails/packs_generator.rb +57 -39
- data/lib/react_on_rails/prerender_error.rb +74 -17
- data/lib/react_on_rails/pro_helper.rb +64 -0
- data/lib/react_on_rails/react_component/render_options.rb +7 -7
- data/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb +2 -5
- data/lib/react_on_rails/smart_error.rb +326 -0
- data/lib/react_on_rails/system_checker.rb +8 -9
- data/lib/react_on_rails/test_helper/webpack_assets_status_checker.rb +16 -7
- data/lib/react_on_rails/utils.rb +241 -55
- data/lib/react_on_rails/version.rb +1 -1
- data/lib/react_on_rails/version_checker.rb +383 -35
- data/lib/tasks/generate_packs.rake +12 -6
- data/lib/tasks/locale.rake +6 -1
- data/rakelib/docker.rake +26 -0
- data/rakelib/dummy_apps.rake +30 -0
- data/rakelib/example_type.rb +121 -0
- data/rakelib/examples_config.yml +52 -0
- data/rakelib/lint.rake +52 -0
- data/rakelib/node_package.rake +15 -0
- data/rakelib/rbs.rake +70 -0
- data/rakelib/run_rspec.rake +223 -0
- data/rakelib/shakapacker_examples.rake +171 -0
- data/rakelib/task_helpers.rb +134 -0
- data/rakelib/update_changelog.rake +73 -0
- data/react_on_rails.gemspec +4 -3
- data/sig/README.md +52 -0
- data/sig/react_on_rails/configuration.rbs +96 -0
- data/sig/react_on_rails/controller.rbs +15 -0
- data/sig/react_on_rails/dev/file_manager.rbs +15 -0
- data/sig/react_on_rails/dev/pack_generator.rbs +19 -0
- data/sig/react_on_rails/dev/process_manager.rbs +22 -0
- data/sig/react_on_rails/dev/server_manager.rbs +39 -0
- data/sig/react_on_rails/dev/service_checker.rbs +22 -0
- data/sig/react_on_rails/error.rbs +4 -0
- data/sig/react_on_rails/generators/js_dependency_manager.rbs +123 -0
- data/sig/react_on_rails/git_utils.rbs +8 -0
- data/sig/react_on_rails/helper.rbs +65 -0
- data/sig/react_on_rails/json_parse_error.rbs +10 -0
- data/sig/react_on_rails/locales.rbs +46 -0
- data/sig/react_on_rails/packer_utils.rbs +15 -0
- data/sig/react_on_rails/prerender_error.rbs +21 -0
- data/sig/react_on_rails/server_rendering_pool.rbs +12 -0
- data/sig/react_on_rails/smart_error.rbs +28 -0
- data/sig/react_on_rails/test_helper.rbs +11 -0
- data/sig/react_on_rails/utils.rbs +34 -0
- data/sig/react_on_rails/version_checker.rbs +12 -0
- data/sig/react_on_rails.rbs +17 -0
- metadata +49 -32
- data/AI_AGENT_INSTRUCTIONS.md +0 -63
- data/CHANGELOG.md +0 -1836
- data/CLAUDE.md +0 -135
- data/CODING_AGENTS.md +0 -313
- data/CONTRIBUTING.md +0 -668
- data/Dockerfile_tests +0 -12
- data/KUDOS.md +0 -114
- data/LICENSE.md +0 -47
- data/LICENSES/README.md +0 -14
- data/NEWS.md +0 -62
- data/PROJECTS.md +0 -63
- data/REACT-ON-RAILS-PRO-LICENSE.md +0 -129
- data/README.md +0 -217
- data/SUMMARY.md +0 -88
- data/TODO.md +0 -135
- data/bin/lefthook/check-trailing-newlines +0 -38
- data/bin/lefthook/get-changed-files +0 -26
- data/bin/lefthook/prettier-format +0 -26
- data/bin/lefthook/ruby-autofix +0 -26
- data/bin/lefthook/ruby-lint +0 -27
- data/docker-compose.yml +0 -11
- data/eslint.config.ts +0 -232
- data/knip.ts +0 -114
- data/lib/react_on_rails/pro/NOTICE +0 -21
- data/lib/react_on_rails/pro/helper.rb +0 -122
- data/lib/react_on_rails/pro/utils.rb +0 -53
- data/tsconfig.eslint.json +0 -6
- data/tsconfig.json +0 -19
data/lib/react_on_rails/utils.rb
CHANGED
|
@@ -5,6 +5,7 @@ require "open3"
|
|
|
5
5
|
require "rainbow"
|
|
6
6
|
require "active_support"
|
|
7
7
|
require "active_support/core_ext/string"
|
|
8
|
+
require "shellwords"
|
|
8
9
|
|
|
9
10
|
# rubocop:disable Metrics/ModuleLength
|
|
10
11
|
module ReactOnRails
|
|
@@ -13,6 +14,46 @@ module ReactOnRails
|
|
|
13
14
|
Rainbow('To see the full output, set FULL_TEXT_ERRORS=true.').red
|
|
14
15
|
} ...\n".freeze
|
|
15
16
|
|
|
17
|
+
def self.immediate_hydration_pro_license_warning(name, type = "Component")
|
|
18
|
+
"[REACT ON RAILS] Warning: immediate_hydration: true requires a React on Rails Pro license.\n" \
|
|
19
|
+
"#{type} '#{name}' will fall back to standard hydration behavior.\n" \
|
|
20
|
+
"Visit https://www.shakacode.com/react-on-rails-pro/ for licensing information."
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Normalizes the immediate_hydration option value, enforcing Pro license requirements.
|
|
24
|
+
# Returns the normalized boolean value for immediate_hydration.
|
|
25
|
+
#
|
|
26
|
+
# @param value [Boolean, nil] The immediate_hydration option value
|
|
27
|
+
# @param name [String] The name of the component/store (for warning messages)
|
|
28
|
+
# @param type [String] The type ("Component" or "Store") for warning messages
|
|
29
|
+
# @return [Boolean] The normalized immediate_hydration value
|
|
30
|
+
# @raise [ArgumentError] If value is not a boolean or nil
|
|
31
|
+
#
|
|
32
|
+
# Logic:
|
|
33
|
+
# - Validates that value is true, false, or nil
|
|
34
|
+
# - If value is explicitly true (boolean) and no Pro license: warn and return false
|
|
35
|
+
# - If value is nil: return true for Pro users, false for non-Pro users
|
|
36
|
+
# - Otherwise: return the value as-is (allows explicit false to work)
|
|
37
|
+
def self.normalize_immediate_hydration(value, name, type = "Component")
|
|
38
|
+
# Type validation: only accept boolean or nil
|
|
39
|
+
unless [true, false, nil].include?(value)
|
|
40
|
+
raise ArgumentError,
|
|
41
|
+
"[REACT ON RAILS] immediate_hydration must be true, false, or nil. Got: #{value.inspect} (#{value.class})"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Strict equality check: only trigger warning for explicit boolean true
|
|
45
|
+
if value == true && !react_on_rails_pro?
|
|
46
|
+
Rails.logger.warn immediate_hydration_pro_license_warning(name, type)
|
|
47
|
+
return false
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# If nil, default based on Pro license status
|
|
51
|
+
return react_on_rails_pro? if value.nil?
|
|
52
|
+
|
|
53
|
+
# Return explicit value (including false)
|
|
54
|
+
value
|
|
55
|
+
end
|
|
56
|
+
|
|
16
57
|
# https://forum.shakacode.com/t/yak-of-the-week-ruby-2-4-pathname-empty-changed-to-look-at-file-size/901
|
|
17
58
|
# return object if truthy, else return nil
|
|
18
59
|
def self.truthy_presence(obj)
|
|
@@ -57,9 +98,12 @@ module ReactOnRails
|
|
|
57
98
|
exitstatus: #{status.exitstatus}#{stdout_msg}#{stderr_msg}
|
|
58
99
|
MSG
|
|
59
100
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
101
|
+
# Use warn to ensure output is visible in CI logs (goes to stderr)
|
|
102
|
+
# and flush immediately before calling exit!
|
|
103
|
+
warn wrap_message(msg)
|
|
104
|
+
warn ""
|
|
105
|
+
warn default_troubleshooting_section
|
|
106
|
+
$stderr.flush
|
|
63
107
|
|
|
64
108
|
# Rspec catches exit without! in the exit callbacks
|
|
65
109
|
exit!(1)
|
|
@@ -111,9 +155,16 @@ module ReactOnRails
|
|
|
111
155
|
|
|
112
156
|
private_class_method def self.server_bundle?(bundle_name)
|
|
113
157
|
config = ReactOnRails.configuration
|
|
114
|
-
bundle_name == config.server_bundle_js_file
|
|
115
|
-
|
|
116
|
-
|
|
158
|
+
return true if bundle_name == config.server_bundle_js_file
|
|
159
|
+
|
|
160
|
+
# Check Pro configurations if Pro is available
|
|
161
|
+
if react_on_rails_pro?
|
|
162
|
+
pro_config = ReactOnRailsPro.configuration
|
|
163
|
+
return true if bundle_name == pro_config.rsc_bundle_js_file ||
|
|
164
|
+
bundle_name == pro_config.react_server_client_manifest_file
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
false
|
|
117
168
|
end
|
|
118
169
|
|
|
119
170
|
private_class_method def self.handle_missing_manifest_entry(bundle_name, is_server_bundle)
|
|
@@ -146,34 +197,6 @@ module ReactOnRails
|
|
|
146
197
|
@server_bundle_path = bundle_js_file_path(bundle_name)
|
|
147
198
|
end
|
|
148
199
|
|
|
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"
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
@react_server_manifest_path = bundle_js_file_path(asset_name)
|
|
175
|
-
end
|
|
176
|
-
|
|
177
200
|
def self.running_on_windows?
|
|
178
201
|
(/cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM) != nil
|
|
179
202
|
end
|
|
@@ -228,11 +251,19 @@ module ReactOnRails
|
|
|
228
251
|
end
|
|
229
252
|
end
|
|
230
253
|
|
|
231
|
-
#
|
|
254
|
+
# Checks if React on Rails Pro is installed and licensed.
|
|
255
|
+
# This method validates the license and will raise an exception if invalid.
|
|
256
|
+
#
|
|
257
|
+
# @return [Boolean] true if Pro is available with valid license
|
|
258
|
+
# @raise [ReactOnRailsPro::Error] if license is invalid
|
|
232
259
|
def self.react_on_rails_pro?
|
|
233
260
|
return @react_on_rails_pro if defined?(@react_on_rails_pro)
|
|
234
261
|
|
|
235
|
-
@react_on_rails_pro =
|
|
262
|
+
@react_on_rails_pro = begin
|
|
263
|
+
return false unless gem_available?("react_on_rails_pro")
|
|
264
|
+
|
|
265
|
+
ReactOnRailsPro::Utils.validated_license_data!.present?
|
|
266
|
+
end
|
|
236
267
|
end
|
|
237
268
|
|
|
238
269
|
# Return an empty string if React on Rails Pro is not installed
|
|
@@ -246,28 +277,12 @@ module ReactOnRails
|
|
|
246
277
|
end
|
|
247
278
|
end
|
|
248
279
|
|
|
249
|
-
|
|
250
|
-
|
|
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
|
-
|
|
280
|
+
# RSC support detection has been moved to React on Rails Pro
|
|
281
|
+
# See react_on_rails_pro/lib/react_on_rails_pro/utils.rb
|
|
264
282
|
def self.rsc_support_enabled?
|
|
265
283
|
return false unless react_on_rails_pro?
|
|
266
284
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
rorp_config = ReactOnRailsPro.configuration
|
|
270
|
-
@rsc_support_enabled = rorp_config.respond_to?(:enable_rsc_support) && rorp_config.enable_rsc_support
|
|
285
|
+
ReactOnRailsPro::Utils.rsc_support_enabled?
|
|
271
286
|
end
|
|
272
287
|
|
|
273
288
|
def self.full_text_errors_enabled?
|
|
@@ -312,6 +327,177 @@ module ReactOnRails
|
|
|
312
327
|
puts "Prepended\n#{text_to_prepend}to #{file}."
|
|
313
328
|
end
|
|
314
329
|
|
|
330
|
+
# Detects which package manager is being used.
|
|
331
|
+
# First checks the packageManager field in package.json (Node.js Corepack standard),
|
|
332
|
+
# then falls back to checking for lock files.
|
|
333
|
+
#
|
|
334
|
+
# @return [Symbol] The package manager symbol (:npm, :yarn, :pnpm, :bun)
|
|
335
|
+
def self.detect_package_manager
|
|
336
|
+
manager = detect_package_manager_from_package_json || detect_package_manager_from_lock_files
|
|
337
|
+
manager || :yarn # Default to yarn if no detection succeeds
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# Validates package_name input to prevent command injection
|
|
341
|
+
#
|
|
342
|
+
# @param package_name [String] The package name to validate
|
|
343
|
+
# @raise [ReactOnRails::Error] if package_name contains potentially unsafe characters
|
|
344
|
+
private_class_method def self.validate_package_name!(package_name)
|
|
345
|
+
raise ReactOnRails::Error, "package_name cannot be nil" if package_name.nil?
|
|
346
|
+
raise ReactOnRails::Error, "package_name cannot be empty" if package_name.to_s.strip.empty?
|
|
347
|
+
|
|
348
|
+
# Allow valid npm package names: alphanumeric, hyphens, underscores, dots, slashes (for scoped packages)
|
|
349
|
+
# See: https://github.com/npm/validate-npm-package-name
|
|
350
|
+
return if package_name.match?(%r{\A[@a-z0-9][a-z0-9._/-]*\z}i)
|
|
351
|
+
|
|
352
|
+
raise ReactOnRails::Error, "Invalid package name: #{package_name.inspect}. " \
|
|
353
|
+
"Package names must contain only alphanumeric characters, " \
|
|
354
|
+
"hyphens, underscores, dots, and slashes (for scoped packages)."
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
# Validates package_name and version inputs to prevent command injection
|
|
358
|
+
#
|
|
359
|
+
# @param package_name [String] The package name to validate
|
|
360
|
+
# @param version [String] The version to validate
|
|
361
|
+
# @raise [ReactOnRails::Error] if inputs contain potentially unsafe characters
|
|
362
|
+
private_class_method def self.validate_package_command_inputs!(package_name, version)
|
|
363
|
+
validate_package_name!(package_name)
|
|
364
|
+
|
|
365
|
+
raise ReactOnRails::Error, "version cannot be nil" if version.nil?
|
|
366
|
+
raise ReactOnRails::Error, "version cannot be empty" if version.to_s.strip.empty?
|
|
367
|
+
|
|
368
|
+
# Allow valid semver versions and common npm version patterns
|
|
369
|
+
# This allows: 1.2.3, 1.2.3-beta.1, 1.2.3-alpha, etc.
|
|
370
|
+
return if version.match?(/\A[a-z0-9][a-z0-9._-]*\z/i)
|
|
371
|
+
|
|
372
|
+
raise ReactOnRails::Error, "Invalid version: #{version.inspect}. " \
|
|
373
|
+
"Versions must contain only alphanumeric characters, dots, hyphens, and underscores."
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
private_class_method def self.detect_package_manager_from_package_json
|
|
377
|
+
package_json_path = File.join(Rails.root, ReactOnRails.configuration.node_modules_location, "package.json")
|
|
378
|
+
return nil unless File.exist?(package_json_path)
|
|
379
|
+
|
|
380
|
+
package_json_data = JSON.parse(File.read(package_json_path))
|
|
381
|
+
return nil unless package_json_data["packageManager"]
|
|
382
|
+
|
|
383
|
+
manager_string = package_json_data["packageManager"]
|
|
384
|
+
# Extract manager name from strings like "yarn@3.6.0" or "pnpm@8.0.0"
|
|
385
|
+
manager_name = manager_string.split("@").first
|
|
386
|
+
manager_name.to_sym if %w[npm yarn pnpm bun].include?(manager_name)
|
|
387
|
+
rescue StandardError
|
|
388
|
+
nil
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
private_class_method def self.detect_package_manager_from_lock_files
|
|
392
|
+
root = Rails.root
|
|
393
|
+
return :yarn if File.exist?(File.join(root, "yarn.lock"))
|
|
394
|
+
return :pnpm if File.exist?(File.join(root, "pnpm-lock.yaml"))
|
|
395
|
+
return :bun if File.exist?(File.join(root, "bun.lockb"))
|
|
396
|
+
return :npm if File.exist?(File.join(root, "package-lock.json"))
|
|
397
|
+
|
|
398
|
+
nil
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
# Returns the appropriate install command for the detected package manager.
|
|
402
|
+
# Generates the correct command with exact version syntax.
|
|
403
|
+
#
|
|
404
|
+
# @param package_name [String] The name of the package to install
|
|
405
|
+
# @param version [String] The exact version to install
|
|
406
|
+
# @return [String] The command to run (e.g., "yarn add react-on-rails@16.0.0 --exact")
|
|
407
|
+
def self.package_manager_install_exact_command(package_name, version)
|
|
408
|
+
validate_package_command_inputs!(package_name, version)
|
|
409
|
+
|
|
410
|
+
manager = detect_package_manager
|
|
411
|
+
# Escape shell arguments to prevent command injection
|
|
412
|
+
safe_package = Shellwords.escape("#{package_name}@#{version}")
|
|
413
|
+
|
|
414
|
+
case manager
|
|
415
|
+
when :pnpm
|
|
416
|
+
"pnpm add #{safe_package} --save-exact"
|
|
417
|
+
when :bun
|
|
418
|
+
"bun add #{safe_package} --exact"
|
|
419
|
+
when :npm
|
|
420
|
+
"npm install #{safe_package} --save-exact"
|
|
421
|
+
else # :yarn or unknown, default to yarn
|
|
422
|
+
"yarn add #{safe_package} --exact"
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
# Returns the appropriate remove command for the detected package manager.
|
|
427
|
+
#
|
|
428
|
+
# @param package_name [String] The name of the package to remove
|
|
429
|
+
# @return [String] The command to run (e.g., "yarn remove react-on-rails")
|
|
430
|
+
def self.package_manager_remove_command(package_name)
|
|
431
|
+
validate_package_name!(package_name)
|
|
432
|
+
|
|
433
|
+
manager = detect_package_manager
|
|
434
|
+
# Escape shell arguments to prevent command injection
|
|
435
|
+
safe_package = Shellwords.escape(package_name)
|
|
436
|
+
|
|
437
|
+
case manager
|
|
438
|
+
when :pnpm
|
|
439
|
+
"pnpm remove #{safe_package}"
|
|
440
|
+
when :bun
|
|
441
|
+
"bun remove #{safe_package}"
|
|
442
|
+
when :npm
|
|
443
|
+
"npm uninstall #{safe_package}"
|
|
444
|
+
else # :yarn or unknown, default to yarn
|
|
445
|
+
"yarn remove #{safe_package}"
|
|
446
|
+
end
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
# Converts an absolute path (String or Pathname) to a path relative to Rails.root.
|
|
450
|
+
# If the path is already relative or doesn't contain Rails.root, returns it as-is.
|
|
451
|
+
#
|
|
452
|
+
# This method is used to normalize paths from Shakapacker's privateOutputPath (which is
|
|
453
|
+
# absolute) to relative paths suitable for React on Rails configuration.
|
|
454
|
+
#
|
|
455
|
+
# Note: Absolute paths that don't start with Rails.root are intentionally passed through
|
|
456
|
+
# unchanged. While there's no known use case for server bundles outside Rails.root,
|
|
457
|
+
# this behavior preserves the original path for debugging and error messages.
|
|
458
|
+
#
|
|
459
|
+
# @param path [String, Pathname] The path to normalize
|
|
460
|
+
# @return [String, nil] The relative path as a string, or nil if path is nil
|
|
461
|
+
#
|
|
462
|
+
# @example Converting absolute paths within Rails.root
|
|
463
|
+
# # Assuming Rails.root is "/app"
|
|
464
|
+
# normalize_to_relative_path("/app/ssr-generated") # => "ssr-generated"
|
|
465
|
+
# normalize_to_relative_path("/app/foo/bar") # => "foo/bar"
|
|
466
|
+
#
|
|
467
|
+
# @example Already relative paths pass through
|
|
468
|
+
# normalize_to_relative_path("ssr-generated") # => "ssr-generated"
|
|
469
|
+
# normalize_to_relative_path("./ssr-generated") # => "./ssr-generated"
|
|
470
|
+
#
|
|
471
|
+
# @example Absolute paths outside Rails.root (edge case)
|
|
472
|
+
# normalize_to_relative_path("/other/path/bundles") # => "/other/path/bundles"
|
|
473
|
+
def self.normalize_to_relative_path(path)
|
|
474
|
+
return nil if path.nil?
|
|
475
|
+
|
|
476
|
+
path_str = path.to_s
|
|
477
|
+
rails_root_str = Rails.root.to_s.chomp("/")
|
|
478
|
+
|
|
479
|
+
# Treat as "inside Rails.root" only for exact match or a subdirectory
|
|
480
|
+
inside_rails_root = rails_root_str.present? &&
|
|
481
|
+
(path_str == rails_root_str || path_str.start_with?("#{rails_root_str}/"))
|
|
482
|
+
|
|
483
|
+
# If path is within Rails.root, remove that prefix
|
|
484
|
+
if inside_rails_root
|
|
485
|
+
# Remove Rails.root and any leading slash
|
|
486
|
+
path_str.sub(%r{^#{Regexp.escape(rails_root_str)}/?}, "")
|
|
487
|
+
else
|
|
488
|
+
# Path is already relative or outside Rails.root
|
|
489
|
+
# Warn if it's an absolute path outside Rails.root (edge case)
|
|
490
|
+
if path_str.start_with?("/") && !inside_rails_root
|
|
491
|
+
Rails.logger&.warn(
|
|
492
|
+
"ReactOnRails: Detected absolute path outside Rails.root: '#{path_str}'. " \
|
|
493
|
+
"Server bundles are typically stored within Rails.root. " \
|
|
494
|
+
"Verify this is intentional."
|
|
495
|
+
)
|
|
496
|
+
end
|
|
497
|
+
path_str
|
|
498
|
+
end
|
|
499
|
+
end
|
|
500
|
+
|
|
315
501
|
def self.default_troubleshooting_section
|
|
316
502
|
<<~DEFAULT
|
|
317
503
|
📞 Get Help & Support:
|