react_on_rails 17.1.0 → 17.2.0.rc.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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/Gemfile.development_dependencies +2 -2
  4. data/Gemfile.lock +65 -73
  5. data/Steepfile +1 -0
  6. data/docs/agent/install-and-upgrade.md +8 -3
  7. data/docs/agent/rsc-adoption.md +3 -0
  8. data/lib/generators/react_on_rails/base_generator.rb +2 -1
  9. data/lib/generators/react_on_rails/generated_webpack_config_pair.rb +84 -0
  10. data/lib/generators/react_on_rails/generator_helper.rb +2 -4
  11. data/lib/generators/react_on_rails/generator_messages.rb +1 -1
  12. data/lib/generators/react_on_rails/install_generator.rb +2 -1
  13. data/lib/generators/react_on_rails/js_dependency_manager.rb +14 -14
  14. data/lib/generators/react_on_rails/pro/USAGE +14 -3
  15. data/lib/generators/react_on_rails/pro_generator.rb +1 -1
  16. data/lib/generators/react_on_rails/pro_setup.rb +41 -213
  17. data/lib/generators/react_on_rails/rsc_setup/client_references.rb +100 -30
  18. data/lib/generators/react_on_rails/rsc_setup.rb +7 -16
  19. data/lib/generators/react_on_rails/templates/pro/base/config/initializers/react_on_rails_pro.rb.tt +1 -1
  20. data/lib/react_on_rails/agent_guardrails.rb +217 -39
  21. data/lib/react_on_rails/dev/process_manager.rb +14 -0
  22. data/lib/react_on_rails/dev/server_manager.rb +1727 -103
  23. data/lib/react_on_rails/doctor.rb +875 -44
  24. data/lib/react_on_rails/helper.rb +3 -3
  25. data/lib/react_on_rails/react_component/render_options.rb +5 -7
  26. data/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb +27 -116
  27. data/lib/react_on_rails/utils.rb +65 -0
  28. data/lib/react_on_rails/version.rb +1 -1
  29. data/lib/react_on_rails/version_checker/lockfile_resolution.rb +170 -0
  30. data/lib/react_on_rails/version_checker.rb +43 -22
  31. data/rakelib/run_rspec.rake +6 -1
  32. data/sig/react_on_rails/configuration.rbs +28 -26
  33. data/sig/react_on_rails/controller.rbs +2 -1
  34. data/sig/react_on_rails/dev/process_manager.rbs +1 -0
  35. data/sig/react_on_rails/dev/server_manager.rbs +150 -10
  36. data/sig/react_on_rails/git_utils.rbs +5 -1
  37. data/sig/react_on_rails/locales.rbs +4 -4
  38. data/sig/react_on_rails/prerender_error.rbs +2 -2
  39. data/sig/react_on_rails/test_helper.rbs +15 -1
  40. data/sig/react_on_rails/utils.rbs +2 -2
  41. data/sig/react_on_rails/version_checker/lockfile_resolution.rbs +31 -0
  42. data/sig/react_on_rails/version_checker.rbs +30 -7
  43. metadata +4 -1
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "securerandom"
4
4
  require_relative "generator_messages"
5
+ require_relative "generated_webpack_config_pair"
5
6
  require "react_on_rails/node_renderer_procfile"
6
7
  require "react_on_rails/pro_migration"
7
8
 
@@ -36,9 +37,8 @@ module ReactOnRails
36
37
  # Loader helpers emitted by
37
38
  # templates/base/base/config/webpack/serverWebpackConfig.js.tt.
38
39
  #
39
- # Keep both blocks byte-identical to that template: a fresh `--pro` install renders
40
- # the template while a standalone Pro upgrade patches an existing base config, and
41
- # the two must produce the same source text so drift is detectable (issue #4786).
40
+ # Keep these parity-test anchors byte-identical to the template (issue #4786).
41
+ # Migration recognition uses the entire rendered pair, never these snippets.
42
42
  GET_LOADER_PATH_JS = <<~JS
43
43
  // Normalizes an entry of a webpack/rspack `rule.use` array to its loader path.
44
44
  // Entries may be a bare string, a `{ loader, options }` object, or null.
@@ -56,15 +56,6 @@ module ReactOnRails
56
56
  }
57
57
  JS
58
58
 
59
- BUNDLER_REQUIRE_PATTERN =
60
- %r{(const bundler = config\.assets_bundler.*\n.*require\('@rspack/core'\).*\n.*: require\('webpack'\);)}
61
-
62
- # Matches any declaration of the getLoaderPath symbol, however it is written. The
63
- # emitted extractLoader calls getLoaderPath, so we must never add a second
64
- # declaration: `function` next to an existing `const` is a SyntaxError, not a
65
- # silent shadow, and the generated config would fail to parse in Node.
66
- GET_LOADER_PATH_DECLARATION = /(?:function\s+getLoaderPath\s*\(|(?:const|let|var)\s+getLoaderPath\s*=)/
67
-
68
59
  # Main entry point for Pro setup.
69
60
  # Orchestrates creation of all Pro-related files and configuration.
70
61
  #
@@ -157,8 +148,8 @@ module ReactOnRails
157
148
 
158
149
  Then run: bundle install
159
150
 
160
- No license needed for evaluation or non-production use.
161
- Free or low-cost production licenses available for startups and small companies.
151
+ No license key is needed to run Pro. Production is free for small organizations, charities, schools, and hospitals;
152
+ larger organizations subscribe: https://reactonrails.com/pricing/
162
153
  See the upgrade guide: https://reactonrails.com/docs/pro/upgrading-to-pro/
163
154
  MSG
164
155
  true
@@ -439,228 +430,65 @@ module ReactOnRails
439
430
  say "✅ Added Node Renderer to #{procfile}", :green
440
431
  end
441
432
 
442
- # Update webpack configs to enable Pro settings.
443
- # This is needed for standalone Pro upgrades where the base install
444
- # created webpack configs without Pro settings enabled.
445
- #
446
- # Updates serverWebpackConfig.js:
447
- # - Adds extractLoader helper function (required by rscWebpackConfig.js)
448
- # - Adds Babel SSR caller setup (required for correct SSR compilation)
449
- # - Enables libraryTarget: 'commonjs2' (required for Node Renderer)
450
- # - Enables serverWebpackConfig.target = 'node' (required for Node.js modules)
451
- # - Disables Node.js polyfills via node = false (required for real __dirname)
452
- # - Changes module.exports to Pro style (required by rscWebpackConfig.js)
453
- #
454
- # Updates ServerClientOrBoth.js:
455
- # - Changes import to destructured style (required for Pro object export)
433
+ # Only replace complete, unchanged generated pairs. Inspecting declarations or
434
+ # individual markers cannot establish that customized JavaScript is safe to rewrite.
456
435
  def update_webpack_config_for_pro
457
- webpack_config, webpack_config_path = webpack_config_paths
458
-
459
- unless File.exist?(webpack_config_path)
460
- say "ℹ️ serverWebpackConfig.js not found, skipping webpack update", :yellow
436
+ paths = generated_webpack_pair_paths
437
+ contents = read_generated_webpack_pair(paths) if paths
438
+ replacement = GeneratedWebpackConfigPair.pro_upgrade(contents) if contents
439
+ unless replacement
440
+ warn_unknown_webpack_pair
461
441
  return
462
442
  end
463
443
 
464
- content = File.read(webpack_config_path)
465
- server_config_ready = pro_server_config_ready?(content)
466
- import_ready = server_client_import_ready?
467
-
468
- # Skip only when both server config and import style are already updated.
469
- if server_config_ready && import_ready
444
+ if replacement == contents
470
445
  say "ℹ️ Webpack config already has Pro settings enabled, skipping", :yellow
471
446
  return
472
447
  end
473
448
 
474
- say "📝 Updating serverWebpackConfig.js for Pro...", :yellow
475
-
476
- unless server_config_ready
477
- # Add extractLoader helper function after bundler require
478
- add_extract_loader_to_server_config(webpack_config, content)
479
-
480
- # Add Babel SSR caller setup (uses extractLoader, so must come after)
481
- add_babel_ssr_caller_to_server_config(webpack_config, content)
482
-
483
- # Uncomment libraryTarget: 'commonjs2'
484
- library_target_pattern = %r{// If using the React on Rails Pro.*\n\s*// libraryTarget: 'commonjs2',}
485
- library_target_replacement = "// Required for React on Rails Pro Node Renderer\n " \
486
- "libraryTarget: 'commonjs2',"
487
- gsub_file(webpack_config, library_target_pattern, library_target_replacement)
488
-
489
- # Replace stale comments and uncomment target = 'node', add node = false
490
- # The base template has 4 lines: 2 explanatory comments + "uncomment" hint + commented code
491
- # Replace with clean Pro output matching the template's use_pro? branch
492
- # rubocop:disable Layout/LineLength
493
- target_node_pattern = %r{\s*// If using the default 'web',.*\n\s*// break with SSR\..*\n\s*// If using the React on Rails Pro.*\n\s*// serverWebpackConfig\.target = 'node'}
494
- # rubocop:enable Layout/LineLength
495
- target_node_replacement = "\n\n " \
496
- "// React on Rails Pro uses Node renderer, so target must be 'node'\n " \
497
- "// This fixes issues with libraries like Emotion and loadable-components\n " \
498
- "serverWebpackConfig.target = 'node';\n\n " \
499
- "// Disable Node.js polyfills - not needed when targeting Node\n " \
500
- "serverWebpackConfig.node = false;"
501
- gsub_file(webpack_config, target_node_pattern, target_node_replacement)
502
-
503
- # Change module.exports to Pro style (exports object with default and extractLoader)
504
- update_server_config_exports(webpack_config)
449
+ paths.zip(replacement).each do |path, content|
450
+ create_file(path, content, force: true)
505
451
  end
506
-
507
- # Update ServerClientOrBoth.js import style
508
- update_server_client_or_both_import
509
-
510
- verify_pro_webpack_transforms(webpack_config)
511
452
  say "✅ Updated webpack configs for Pro", :green
512
453
  end
513
454
 
514
- def webpack_config_paths
515
- config = destination_config_path("config/webpack/serverWebpackConfig.js")
516
- [config, File.join(destination_root, config)]
517
- end
518
-
519
- def add_extract_loader_to_server_config(webpack_config, content)
520
- # Skip if extractLoader already exists
521
- return if content.include?("function extractLoader")
522
-
523
- if content.include?(GET_LOADER_PATH_JS)
524
- # Config rendered by the current base template: append extractLoader directly after
525
- # the shared getLoaderPath helper so the result matches the template's Pro output.
526
- gsub_file(webpack_config, GET_LOADER_PATH_JS, "#{GET_LOADER_PATH_JS}\n#{EXTRACT_LOADER_JS}")
527
- elsif content.match?(GET_LOADER_PATH_DECLARATION)
528
- # The app already declares getLoaderPath but has customized it (reformatted, recommented,
529
- # or rewritten as an arrow function). Reuse whatever is there and emit extractLoader only.
530
- # Emitting our own copy would redeclare the identifier, which is a SyntaxError next to an
531
- # existing const/let and silent shadowing next to another function declaration.
532
- gsub_file(webpack_config, BUNDLER_REQUIRE_PATTERN, "\\1\n\n#{EXTRACT_LOADER_JS}".chomp)
533
- else
534
- # Base config generated before getLoaderPath existed: emit both helpers after the
535
- # bundler require so extractLoader's dependency is present.
536
- gsub_file(
537
- webpack_config,
538
- BUNDLER_REQUIRE_PATTERN,
539
- "\\1\n\n#{GET_LOADER_PATH_JS}\n#{EXTRACT_LOADER_JS}".chomp
540
- )
455
+ # Unlike resolve_server_client_or_both_path, discovery must not rename files
456
+ # or edit environment imports before the entire pair has been recognized.
457
+ def generated_webpack_pair_paths
458
+ server = destination_config_path("config/webpack/serverWebpackConfig.js")
459
+ companions = %w[ServerClientOrBoth.js generateWebpackConfigs.js].map do |name|
460
+ destination_config_path("config/webpack/#{name}")
541
461
  end
542
- end
462
+ existing = companions.select do |path|
463
+ absolute = File.join(destination_root, path)
464
+ File.exist?(absolute) || File.symlink?(absolute)
465
+ end
466
+ return unless existing.one?
543
467
 
544
- def add_babel_ssr_caller_to_server_config(webpack_config, content)
545
- return if content.include?("babelLoader.options.caller")
546
-
547
- babel_ssr_code = "\n\n " \
548
- "// Set SSR caller for Babel (if using Babel instead of SWC)\n " \
549
- "const babelLoader = extractLoader(rule, 'babel-loader');\n " \
550
- "if (babelLoader && babelLoader.options) {\n " \
551
- "babelLoader.options.caller = { ssr: true };\n " \
552
- "}"
553
-
554
- # Insert after cssLoader.options.modules; [\s\S]*? covers both single-line and spread syntax patterns.
555
- gsub_file(
556
- webpack_config,
557
- /(cssLoader\.options\.modules = \{[\s\S]*?exportOnlyLocals: true[\s\S]*?\};\s*\n\s*\})/,
558
- "\\1#{babel_ssr_code}"
559
- )
560
- new_content = File.read(File.join(destination_root, webpack_config))
561
- return if new_content.include?("babelLoader.options.caller")
468
+ paths = [server, existing.first]
469
+ return unless paths.all? do |path|
470
+ absolute = File.join(destination_root, path)
471
+ File.file?(absolute) && !File.symlink?(absolute)
472
+ end
562
473
 
563
- say_status :warning, "Babel SSR caller insertion failed in #{webpack_config}; manual edit required.", :yellow
474
+ paths
564
475
  end
565
476
 
566
- def update_server_config_exports(webpack_config)
567
- # Change from: module.exports = configureServer;
568
- # To: module.exports = { default: configureServer, extractLoader };
569
- gsub_file(
570
- webpack_config,
571
- /^module\.exports = configureServer;\s*$/,
572
- "module.exports = {\n default: configureServer,\n extractLoader,\n};\n"
573
- )
477
+ def read_generated_webpack_pair(paths)
478
+ paths.map { |path| File.binread(File.join(destination_root, path)) }
479
+ rescue SystemCallError, IOError
480
+ nil
574
481
  end
575
482
 
576
- def verify_pro_webpack_transforms(webpack_config)
577
- content = File.read(File.join(destination_root, webpack_config))
578
- missing = missing_server_config_transforms(content)
579
- missing.concat(missing_server_client_import_transform)
580
- return if missing.empty?
581
-
483
+ def warn_unknown_webpack_pair
582
484
  GeneratorMessages.add_warning(<<~MSG.strip)
583
- ⚠️ Some Pro webpack transforms may not have applied correctly.
584
-
585
- The following expected patterns were not found in #{webpack_config}:
586
- #{missing.map { |m| " - #{m}" }.join("\n")}
587
-
588
- This can happen if your webpack config has been customized.
589
- Please verify #{webpack_config} manually.
485
+ Skipped Pro webpack updates: the config pair is missing, ambiguous, customized,
486
+ or does not match a supported current generated pair.
487
+ Both webpack configs and their paths were left unchanged. Update them manually using the Pro setup guide:
488
+ https://reactonrails.com/docs/pro/upgrading-to-pro/
590
489
  MSG
591
490
  end
592
491
 
593
- def missing_server_config_transforms(content)
594
- checks = [
595
- "libraryTarget: 'commonjs2',",
596
- "function extractLoader",
597
- "babelLoader.options.caller = { ssr: true }",
598
- "serverWebpackConfig.target = 'node'",
599
- "serverWebpackConfig.node = false",
600
- "default: configureServer",
601
- "extractLoader,"
602
- ]
603
-
604
- checks.reject { |pattern| content.include?(pattern) }
605
- end
606
-
607
- def missing_server_client_import_transform
608
- server_client_path = resolve_server_client_or_both_path
609
- return [] unless server_client_path
610
-
611
- content = File.read(File.join(destination_root, server_client_path))
612
- return [] if content.include?("{ default: serverWebpackConfig }")
613
-
614
- ["{ default: serverWebpackConfig }"]
615
- end
616
-
617
- def update_server_client_or_both_import
618
- server_client_path = resolve_server_client_or_both_path
619
- return unless server_client_path
620
-
621
- content = File.read(File.join(destination_root, server_client_path))
622
-
623
- # Skip if already using destructured import
624
- return if content.include?("{ default: serverWebpackConfig }")
625
-
626
- # Change from: const serverWebpackConfig = require('./serverWebpackConfig');
627
- # To: const { default: serverWebpackConfig } = require('./serverWebpackConfig');
628
- gsub_file(
629
- server_client_path,
630
- %r{^const serverWebpackConfig = require\('\./serverWebpackConfig'\);$},
631
- "const { default: serverWebpackConfig } = require('./serverWebpackConfig');"
632
- )
633
-
634
- new_content = File.read(File.join(destination_root, server_client_path))
635
- return if new_content.include?("{ default: serverWebpackConfig }")
636
-
637
- say_status(
638
- :warning,
639
- "ServerClientOrBoth import update failed in #{server_client_path}; manual edit required.",
640
- :yellow
641
- )
642
- end
643
-
644
- def pro_server_config_ready?(content)
645
- # Check for the Pro-specific comment marker (written by the transform) to avoid
646
- # false-negatives when commented-out lines also contain the pattern string.
647
- content.include?("// Required for React on Rails Pro Node Renderer") &&
648
- content.include?("function extractLoader") &&
649
- content.include?("babelLoader.options.caller = { ssr: true }") &&
650
- content.include?("serverWebpackConfig.target = 'node'") &&
651
- content.include?("serverWebpackConfig.node = false") &&
652
- content.include?("default: configureServer") &&
653
- content.include?("extractLoader,")
654
- end
655
-
656
- def server_client_import_ready?
657
- server_client_path = resolve_server_client_or_both_path
658
- return true unless server_client_path
659
-
660
- content = File.read(File.join(destination_root, server_client_path))
661
- content.include?("{ default: serverWebpackConfig }")
662
- end
663
-
664
492
  def pro_gem_auto_install_command
665
493
  "bundle add #{PRO_GEM_NAME} --version='#{pro_gem_version_requirement}' --strict"
666
494
  end
@@ -43,8 +43,10 @@ module ReactOnRails
43
43
  // (which re-runs the hook). Running bin/shakapacker directly after changing server
44
44
  // components is covered by the best-effort staleness warning below.
45
45
  //
46
- // The resolution cascade below is mirrored, branch for branch, by the Pro dummy's
47
- // hand-written rscManifestClientReferences.js and pinned on both sides by contract tests.
46
+ // The resolution cascade below — including the react-on-rails-pro client-component
47
+ // registrations appended to every branch's result (issue #5079) — is mirrored, branch
48
+ // for branch, by the Pro dummy's hand-written rscManifestClientReferences.js and pinned
49
+ // on both sides by contract tests.
48
50
  const rscClientReferences = (() => {
49
51
  // Required inside the IIFE rather than at the top of the file because the module-scope
50
52
  // bindings 'resolve' (from 'path') and 'config' (from 'shakapacker') are already
@@ -168,40 +170,73 @@ module ReactOnRails
168
170
  );
169
171
  };
170
172
 
171
- if (configuredRefsJson) {
172
- const resolvedRefsJson = resolve(configuredRefsJson);
173
- if (!existsSync(resolvedRefsJson)) {
174
- throw new Error(
175
- `RSC_MANIFEST_CLIENT_REFERENCES_JSON is set but the file does not exist: ${resolvedRefsJson}`,
176
- );
173
+ const resolveDiscoveredClientReferences = () => {
174
+ if (configuredRefsJson) {
175
+ const resolvedRefsJson = resolve(configuredRefsJson);
176
+ if (!existsSync(resolvedRefsJson)) {
177
+ throw new Error(
178
+ `RSC_MANIFEST_CLIENT_REFERENCES_JSON is set but the file does not exist: ${resolvedRefsJson}`,
179
+ );
180
+ }
181
+ warnIfManifestStale(resolvedRefsJson);
182
+ return readManifestReferences(resolvedRefsJson);
177
183
  }
178
- warnIfManifestStale(resolvedRefsJson);
179
- return readManifestReferences(resolvedRefsJson);
180
- }
181
184
 
182
- if (process.env.RSC_REFERENCE_DISCOVERY_BUILD === 'true' || process.env.RSC_BUNDLE_ONLY === 'true') {
183
- return [fallbackRscClientReferences];
184
- }
185
-
186
- if (existsSync(defaultRefsJson)) {
187
- warnIfManifestStale(defaultRefsJson);
188
- return readManifestReferences(defaultRefsJson);
189
- }
190
-
191
- if (existsSync(serverComponentRegistrationEntry)) {
192
- if (!rscConfigSupportsDiscovery()) {
193
- console.warn(
194
- `[react_on_rails] Missing ${defaultRefsJson}, but this app's RSC webpack config ` +
195
- 'or precompile hook does not support manifest discovery yet; falling back to broad client ' +
196
- 'reference scan. Re-run rails g react_on_rails:rsc to update generated configs.',
197
- );
185
+ if (process.env.RSC_REFERENCE_DISCOVERY_BUILD === 'true' || process.env.RSC_BUNDLE_ONLY === 'true') {
198
186
  return [fallbackRscClientReferences];
199
187
  }
200
188
 
201
- throw new Error(`Missing ${defaultRefsJson}. Run bin/shakapacker-precompile-hook before bin/shakapacker.`);
202
- }
189
+ if (existsSync(defaultRefsJson)) {
190
+ warnIfManifestStale(defaultRefsJson);
191
+ return readManifestReferences(defaultRefsJson);
192
+ }
193
+
194
+ if (existsSync(serverComponentRegistrationEntry)) {
195
+ if (!rscConfigSupportsDiscovery()) {
196
+ console.warn(
197
+ `[react_on_rails] Missing ${defaultRefsJson}, but this app's RSC webpack config ` +
198
+ 'or precompile hook does not support manifest discovery yet; falling back to broad client ' +
199
+ 'reference scan. Re-run rails g react_on_rails:rsc to update generated configs.',
200
+ );
201
+ return [fallbackRscClientReferences];
202
+ }
203
+
204
+ throw new Error(`Missing ${defaultRefsJson}. Run bin/shakapacker-precompile-hook before bin/shakapacker.`);
205
+ }
206
+
207
+ return [fallbackRscClientReferences];
208
+ };
203
209
 
204
- return [fallbackRscClientReferences];
210
+ // react-on-rails-pro ships its own 'use client' components (RSCRoute, RSCProvider, and
211
+ // the default RSC provider registration). Both the discovery build and the fallback
212
+ // directory scan only cover app source — node_modules is never scanned — so these can
213
+ // never be discovered and, without explicit registration, never reach
214
+ // react-client-manifest.json. Server components rendering them (e.g. a nested
215
+ // <RSCRoute> for section-level refetching) then fail the render-time manifest lookup
216
+ // even when the RSC bundle compiles (issue #5079). require.resolve returns the
217
+ // absolute realpath, which matches the module resource webpack records
218
+ // (resolve.symlinks defaults to true), so the manifest keys line up with the file URLs
219
+ // the RSC bundle's client references carry even when react-on-rails-pro is installed
220
+ // through a symlink (pnpm, workspaces). A function (invoked in the return below, after
221
+ // the discovered references resolve) so both this resolver and its Pro dummy mirror
222
+ // surface the same first error when both steps fail.
223
+ const reactOnRailsProClientReferences = () =>
224
+ [
225
+ 'react-on-rails-pro/RSCRoute',
226
+ 'react-on-rails-pro/RSCProvider',
227
+ 'react-on-rails-pro/registerDefaultRSCProvider/client',
228
+ ].map((subpath) => {
229
+ try {
230
+ return require.resolve(subpath);
231
+ } catch (err) {
232
+ throw new Error(
233
+ `Failed to resolve the react-on-rails-pro client component "${subpath}" for RSC ` +
234
+ `client-reference registration: ${err.message}`,
235
+ );
236
+ }
237
+ });
238
+
239
+ return [...new Set([...resolveDiscoveredClientReferences(), ...reactOnRailsProClientReferences()])];
205
240
  })();
206
241
  JS
207
242
  end
@@ -277,6 +312,10 @@ module ReactOnRails
277
312
 
278
313
  content = read_current_rsc_config(config_path, fallback: content)
279
314
  end
315
+ # A previously generated IIFE (no react-on-rails-pro registrations — issue #5079) also
316
+ # counts as generated below; warn so the user registers the components manually.
317
+ warn_outdated_rsc_client_references_helper(config_path) if
318
+ outdated_generated_rsc_client_references_defined?(content)
280
319
  return :scoped if generated_rsc_client_references_defined?(content)
281
320
 
282
321
  warn_unscoped_rsc_client_references_helper(config_path)
@@ -312,6 +351,12 @@ module ReactOnRails
312
351
  content = normalized_content
313
352
  end
314
353
 
354
+ # A resolver emitted by a previous generator version (no react-on-rails-pro
355
+ # client-component registrations — issue #5079) is deliberately not rewritten; warn
356
+ # before the scoped-references short-circuit below treats it as already current.
357
+ warn_outdated_rsc_client_references_helper(config_path) if
358
+ outdated_generated_rsc_client_references_defined?(content)
359
+
315
360
  return unless rsc_plugin_sections_safe_to_rewrite?(config_path, content, is_server:)
316
361
 
317
362
  return if rsc_plugin_uses_scoped_client_references?(content, is_server:)
@@ -1405,6 +1450,31 @@ module ReactOnRails
1405
1450
  write_existing_rsc_config(config_path, updated_content, action: :rewrite)
1406
1451
  end
1407
1452
 
1453
+ # True when the file carries a manifest-backed generated resolver IIFE that does not
1454
+ # register the react-on-rails-pro client components (issue #5079) — either the resolver a
1455
+ # previous generator version emitted, or a user customization of one. The generator never
1456
+ # rewrites such a resolver; it warns with manual registration instructions instead.
1457
+ def outdated_generated_rsc_client_references_defined?(content)
1458
+ return false unless generated_rsc_client_references_defined?(content)
1459
+
1460
+ iife_body = generated_rsc_client_references_iife_body(content)
1461
+ return false unless iife_body
1462
+
1463
+ !js_code_matches?(iife_body, /\breactOnRailsProClientReferences\b/)
1464
+ end
1465
+
1466
+ def warn_outdated_rsc_client_references_helper(config_path)
1467
+ GeneratorMessages.add_warning(
1468
+ "#{config_path} contains a rscClientReferences resolver from a previous generator " \
1469
+ "version: it does not register the react-on-rails-pro package's own client components " \
1470
+ "(RSCRoute, RSCProvider, registerDefaultRSCProvider/client — issue #5079). The file was " \
1471
+ "left unchanged. Add require.resolve entries for those components to your " \
1472
+ "clientReferences (see the RSC setup docs at " \
1473
+ "https://reactonrails.com/docs/pro/react-server-components/create-without-ssr), or " \
1474
+ "remove the generated resolver block and re-run this generator to emit the current one."
1475
+ )
1476
+ end
1477
+
1408
1478
  def read_current_rsc_config(config_path, fallback:)
1409
1479
  return fallback if options[:pretend] || options[:skip]
1410
1480
 
@@ -82,17 +82,9 @@ module ReactOnRails
82
82
  react_version = detect_react_version
83
83
  return if react_version.nil? # React not installed yet, will be installed by generator
84
84
 
85
- version = Gem::Version.new(react_version)
86
- major, minor, patch = version.segments
85
+ major, minor, patch = react_version.split(".").map(&:to_i)
87
86
 
88
- if version.prerelease?
89
- GeneratorMessages.add_warning(<<~MSG.strip)
90
- ⚠️ RSC requires stable React (detected: #{react_version}).
91
-
92
- React prereleases are not supported. Install matching stable React/React DOM versions:
93
- #{manual_add_packages_command(["react@#{RSC_REACT_VERSION_RANGE}", "react-dom@#{RSC_REACT_VERSION_RANGE}"])}
94
- MSG
95
- elsif major != RSC_SUPPORTED_REACT_MAJOR || minor != RSC_SUPPORTED_REACT_MINOR
87
+ if major != RSC_SUPPORTED_REACT_MAJOR || minor != RSC_SUPPORTED_REACT_MINOR
96
88
  GeneratorMessages.add_warning(<<~MSG.strip)
97
89
  ⚠️ RSC requires React #{RSC_SUPPORTED_REACT_LINE} (detected: #{react_version})
98
90
 
@@ -105,14 +97,13 @@ module ReactOnRails
105
97
  MSG
106
98
  elsif patch < RSC_MINIMUM_REACT_PATCH
107
99
  GeneratorMessages.add_warning(<<~MSG.strip)
108
- ⚠️ React #{react_version} is below the required minimum for RSC.
100
+ ⚠️ React #{react_version} is below the recommended minimum for RSC.
109
101
 
110
- react-on-rails-rsc #{JsDependencyManager::RSC_PACKAGE_VERSION_PIN} requires
111
- matching stable React/React DOM #{RSC_MINIMUM_REACT_VERSION}+ on #{RSC_SUPPORTED_REACT_LINE}.
112
- The Node Renderer refuses startup until React and React DOM are upgraded together:
113
- #{manual_add_packages_command(["react@#{RSC_REACT_VERSION_RANGE}", "react-dom@#{RSC_REACT_VERSION_RANGE}"])}
102
+ Please upgrade to at least React #{RSC_MINIMUM_REACT_VERSION}:
103
+ #{manual_add_packages_command(["react@#{RSC_MINIMUM_REACT_VERSION}", "react-dom@#{RSC_MINIMUM_REACT_VERSION}"])}
114
104
 
115
- The standalone RSC generator does not upgrade your React or React DOM dependencies.
105
+ react-on-rails-rsc 19.2.x with patch >= 19.2.1 is coordinated with
106
+ React/React DOM #{RSC_MINIMUM_REACT_VERSION}+ for the React on Rails Pro 17 RSC runtime.
116
107
  MSG
117
108
  end
118
109
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # See https://reactonrails.com/docs/configuration/configuration-pro
4
- # License: Set REACT_ON_RAILS_PRO_LICENSE environment variable
4
+ # Optional license key from your subscription: set REACT_ON_RAILS_PRO_LICENSE to mark your pages Licensed.
5
5
  ReactOnRailsPro.configure do |config|
6
6
  config.server_renderer = "NodeRenderer"
7
7
  config.renderer_url = ENV.fetch("REACT_RENDERER_URL", "http://localhost:3800")