Skip to content

Commit d39a95c

Browse files
authored
Polish RSC generator install follow-ups
## Summary - Add install-time info that every `--rsc` install uses `react-on-rails-rsc@19.0.5-rc.6` until stable `19.0.5` is published and tagged latest. - Make manual dependency recovery commands package-manager-aware instead of always showing npm. - Simplify active-bundler RSC plugin import normalization and clarify the multiline CommonJS destructuring regex comment. - Add mixed active/inactive RSC plugin verification for real JS imports and invocations. ## Design Calls - All `--rsc` installs keep the prerelease `react-on-rails-rsc` pin for now, including Webpack projects. The generator no longer retries an unversioned `latest` fallback because npm latest may not include Rspack support yet. - Inactive plugin symbols are reported when they appear as real CommonJS or ESM imports, or real `new InactivePlugin(...)` invocations, alongside the active plugin. - Inactive plugin names inside comments or string literals are intentionally ignored during stale-symbol verification. - `normalize_rsc_plugin_import_for_active_bundler` now derives inactive plugin class and import path from the module-level helper methods instead of caller-supplied parameters. ## Tests - `git diff --check` - `bundle exec rspec spec/react_on_rails/generators/install_generator_spec.rb:2000 spec/react_on_rails/generators/rsc_generator_spec.rb:2559` - `bundle exec rspec spec/react_on_rails/generators/js_dependency_manager_spec.rb spec/react_on_rails/generators/generator_helper_spec.rb` - `bundle exec rspec spec/react_on_rails/generators/install_generator_spec.rb:2013 spec/react_on_rails/generators/install_generator_spec.rb:2025` - `bundle exec rspec spec/react_on_rails/generators/rsc_generator_spec.rb:2581 spec/react_on_rails/generators/rsc_generator_spec.rb:2604` - `bundle exec rspec spec/react_on_rails/generators/rsc_generator_spec.rb:1114` - `bundle exec rubocop lib/generators/react_on_rails/generator_helper.rb lib/generators/react_on_rails/js_dependency_manager.rb lib/generators/react_on_rails/rsc_setup.rb lib/generators/react_on_rails/rsc_setup/client_references.rb spec/react_on_rails/generators/generator_helper_spec.rb spec/react_on_rails/generators/install_generator_spec.rb spec/react_on_rails/generators/js_dependency_manager_spec.rb spec/react_on_rails/generators/rsc_generator_spec.rb` - Pre-commit and pre-push hooks reran trailing newline and RuboCop checks for the changed Ruby files. Closes #3640 Closes #3646 <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Changes are limited to install-time generators and warning text; the main behavioral shift is refusing an unversioned RSC fallback, which is intentional and covered by updated specs. > > **Overview** > **Generator install UX** now surfaces **package-manager-aware** manual recovery commands (npm, yarn, pnpm, bun) for add/remove/install failures instead of always showing `npm install`. Fallback installs **drop `--save-exact` / `--exact`** when specs use semver ranges (`~`, `^`) so manual and CLI recovery match real constraints. > > **RSC JS dependencies:** every `--rsc` run **logs why** `react-on-rails-rsc` is pinned to the prerelease (`19.0.5-rc.6`). On install failure the generator **keeps the pin** and **stops retrying unversioned `latest`** (avoids replacing a good pin with an incompatible package). Pin-failure copy is unified via `rsc_dependency_failure_message` and applies to Webpack and Rspack. > > **RSC webpack migration/verification:** inactive bundler plugin detection uses **real JS** (CommonJS/ESM imports and `new InactivePlugin(...)`) and **ignores comments/strings**. Config checks can report **stale inactive plugins** when the active plugin is already present. Import normalization no longer takes inactive class/path from callers; multiline CommonJS `require` destructuring is documented as supported. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit f17926a. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Generator messages now use package-manager-aware install/remove commands and exact-pinned install guidance for recoveries; fallback warnings reference "direct package manager commands" rather than only npm. * RSC setup emits clearer, consolidated warnings for React-version compatibility and stale/inactive RSC bundler plugins in webpack/client/server configs. * **Tests** * Specs updated to validate package-manager-aware messaging, pinned-install recovery commands, and stale-plugin detection. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 211257c commit d39a95c

8 files changed

Lines changed: 354 additions & 112 deletions

File tree

react_on_rails/lib/generators/react_on_rails/generator_helper.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def add_npm_dependencies(packages, dev: false)
4141
result != false
4242
rescue StandardError => e
4343
say_status :warning, "Could not add packages via package_json gem: #{e.message}", :yellow
44-
say_status :warning, "Will fall back to direct npm commands.", :yellow
44+
say_status :warning, "Will fall back to direct package manager commands.", :yellow
4545
false
4646
end
4747
end

react_on_rails/lib/generators/react_on_rails/js_dependency_manager.rb

Lines changed: 131 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -226,14 +226,14 @@ def add_react_on_rails_package
226226
⚠️ Failed to add react-on-rails package.
227227
228228
You can install it manually by running:
229-
npm install #{react_on_rails_pkg}
229+
#{manual_add_packages_command([react_on_rails_pkg])}
230230
MSG
231231
rescue StandardError => e
232232
GeneratorMessages.add_warning(<<~MSG.strip)
233233
⚠️ Error adding react-on-rails package: #{e.message}
234234
235235
You can install it manually by running:
236-
npm install #{react_on_rails_pkg}
236+
#{manual_add_packages_command([react_on_rails_pkg])}
237237
MSG
238238
end
239239

@@ -255,14 +255,14 @@ def add_react_dependencies
255255
⚠️ Failed to add React dependencies.
256256
257257
You can install them manually by running:
258-
npm install #{react_deps.join(' ')}
258+
#{manual_add_packages_command(react_deps)}
259259
MSG
260260
rescue StandardError => e
261261
GeneratorMessages.add_warning(<<~MSG.strip)
262262
⚠️ Error adding React dependencies: #{e.message}
263263
264264
You can install them manually by running:
265-
npm install #{react_deps.join(' ')}
265+
#{manual_add_packages_command(react_deps)}
266266
MSG
267267
end
268268

@@ -274,14 +274,14 @@ def add_css_dependencies
274274
⚠️ Failed to add CSS dependencies.
275275
276276
You can install them manually by running:
277-
npm install #{CSS_DEPENDENCIES.join(' ')}
277+
#{manual_add_packages_command(CSS_DEPENDENCIES)}
278278
MSG
279279
rescue StandardError => e
280280
GeneratorMessages.add_warning(<<~MSG.strip)
281281
⚠️ Error adding CSS dependencies: #{e.message}
282282
283283
You can install them manually by running:
284-
npm install #{CSS_DEPENDENCIES.join(' ')}
284+
#{manual_add_packages_command(CSS_DEPENDENCIES)}
285285
MSG
286286
end
287287

@@ -293,14 +293,14 @@ def add_rspack_dependencies
293293
⚠️ Failed to add Rspack dependencies.
294294
295295
You can install them manually by running:
296-
npm install #{RSPACK_DEPENDENCIES.join(' ')}
296+
#{manual_add_packages_command(RSPACK_DEPENDENCIES)}
297297
MSG
298298
rescue StandardError => e
299299
GeneratorMessages.add_warning(<<~MSG.strip)
300300
⚠️ Error adding Rspack dependencies: #{e.message}
301301
302302
You can install them manually by running:
303-
npm install #{RSPACK_DEPENDENCIES.join(' ')}
303+
#{manual_add_packages_command(RSPACK_DEPENDENCIES)}
304304
MSG
305305
end
306306

@@ -313,14 +313,14 @@ def add_swc_dependencies
313313
314314
SWC is the default JavaScript transpiler for Shakapacker 9.3.0+.
315315
You can install them manually by running:
316-
npm install --save-dev #{SWC_DEPENDENCIES.join(' ')}
316+
#{manual_add_packages_command(SWC_DEPENDENCIES, dev: true)}
317317
MSG
318318
rescue StandardError => e
319319
GeneratorMessages.add_warning(<<~MSG.strip)
320320
⚠️ Error adding SWC dependencies: #{e.message}
321321
322322
You can install them manually by running:
323-
npm install --save-dev #{SWC_DEPENDENCIES.join(' ')}
323+
#{manual_add_packages_command(SWC_DEPENDENCIES, dev: true)}
324324
MSG
325325
end
326326

@@ -332,15 +332,15 @@ def add_babel_react_dependencies
332332
⚠️ Failed to add Babel React preset dependency.
333333
334334
You can install it manually by running:
335-
npm install --save-dev #{BABEL_REACT_DEPENDENCIES.join(' ')}
335+
#{manual_add_packages_command(BABEL_REACT_DEPENDENCIES, dev: true)}
336336
MSG
337337
false
338338
rescue StandardError => e
339339
GeneratorMessages.add_warning(<<~MSG.strip)
340340
⚠️ Error adding Babel React preset dependency: #{e.message}
341341
342342
You can install it manually by running:
343-
npm install --save-dev #{BABEL_REACT_DEPENDENCIES.join(' ')}
343+
#{manual_add_packages_command(BABEL_REACT_DEPENDENCIES, dev: true)}
344344
MSG
345345
false
346346
end
@@ -353,14 +353,14 @@ def add_typescript_dependencies
353353
⚠️ Failed to add TypeScript dependencies.
354354
355355
You can install them manually by running:
356-
npm install --save-dev #{TYPESCRIPT_DEPENDENCIES.join(' ')}
356+
#{manual_add_packages_command(TYPESCRIPT_DEPENDENCIES, dev: true)}
357357
MSG
358358
rescue StandardError => e
359359
GeneratorMessages.add_warning(<<~MSG.strip)
360360
⚠️ Error adding TypeScript dependencies: #{e.message}
361361
362362
You can install them manually by running:
363-
npm install --save-dev #{TYPESCRIPT_DEPENDENCIES.join(' ')}
363+
#{manual_add_packages_command(TYPESCRIPT_DEPENDENCIES, dev: true)}
364364
MSG
365365
end
366366

@@ -381,14 +381,14 @@ def add_pro_dependencies
381381
⚠️ Failed to add React on Rails Pro dependencies.
382382
383383
You can install them manually by running:
384-
npm install #{pro_packages.join(' ')}
384+
#{manual_add_packages_command(pro_packages)}
385385
MSG
386386
rescue StandardError => e
387387
GeneratorMessages.add_warning(<<~MSG.strip)
388388
⚠️ Error adding React on Rails Pro dependencies: #{e.message}
389389
390390
You can install them manually by running:
391-
npm install #{PRO_DEPENDENCIES.join(' ')}
391+
#{manual_add_packages_command(PRO_DEPENDENCIES)}
392392
MSG
393393
end
394394

@@ -408,45 +408,28 @@ def pro_packages_with_version
408408
end
409409

410410
def add_rsc_dependencies
411+
rsc_packages = RSC_DEPENDENCIES
412+
used_version_pins = false
411413
say "Installing React Server Components dependencies..."
412414
rsc_packages, used_version_pins = rsc_packages_with_version
415+
GeneratorMessages.add_info(rsc_dependency_pin_info) if used_version_pins
413416
return if add_packages(rsc_packages)
414417

415-
manual_install_packages = rsc_packages
416-
if used_version_pins && using_rspack?
417-
# Do NOT retry unversioned for rspack: the `latest` tag (currently 19.0.4) does not export
418-
# react-on-rails-rsc/RspackPlugin, so an unversioned install would replace the pin already
419-
# written to package.json with a known-incompatible version and silently break the build.
420-
# Keep the pin and tell the user to finish the install manually.
421-
GeneratorMessages.add_warning(rspack_rsc_dependency_pin_failed_warning)
422-
elsif used_version_pins
423-
warning_msg = "Could not install version-pinned RSC dependency. Retrying latest available package."
424-
say_status :warning,
425-
warning_msg,
426-
:yellow
427-
GeneratorMessages.add_warning(
428-
"Warning: #{warning_msg} " \
429-
"The installed react-on-rails-rsc version may not match the expected compatibility pin."
418+
GeneratorMessages.add_warning(
419+
rsc_dependency_failure_message(
420+
"⚠️ Failed to add React Server Components dependencies.",
421+
used_version_pins,
422+
rsc_packages
430423
)
431-
return if add_packages(RSC_DEPENDENCIES)
432-
433-
manual_install_packages = RSC_DEPENDENCIES
434-
end
435-
436-
GeneratorMessages.add_warning(<<~MSG.strip)
437-
⚠️ Failed to add React Server Components dependencies.
438-
439-
You can install them manually by running:
440-
npm install #{manual_install_packages.join(' ')}
441-
MSG
424+
)
442425
rescue StandardError => e
443-
manual_install_packages = using_rspack? ? rsc_packages_with_pin : RSC_DEPENDENCIES
444-
GeneratorMessages.add_warning(<<~MSG.strip)
445-
⚠️ Error adding React Server Components dependencies: #{e.message}
446-
447-
You can install them manually by running:
448-
npm install #{manual_install_packages.join(' ')}
449-
MSG
426+
GeneratorMessages.add_warning(
427+
rsc_dependency_failure_message(
428+
"⚠️ Error adding React Server Components dependencies: #{e.message}",
429+
used_version_pins,
430+
rsc_packages
431+
)
432+
)
450433
end
451434

452435
# Returns [pinned_packages, used_version_pins]. used_version_pins is always true here;
@@ -459,12 +442,42 @@ def rsc_packages_with_pin
459442
RSC_DEPENDENCIES.map { |pkg| "#{pkg}@#{RSC_PACKAGE_VERSION_PIN}" }
460443
end
461444

462-
def rspack_rsc_dependency_pin_failed_warning
445+
def rsc_stable_package_version_target
446+
RSC_PACKAGE_VERSION_PIN.split("-", 2).first
447+
end
448+
449+
def rsc_dependency_pin_info
450+
"React Server Components package pin: all --rsc installs temporarily use " \
451+
"react-on-rails-rsc@#{RSC_PACKAGE_VERSION_PIN}, including Webpack projects. " \
452+
"This prerelease keeps react-on-rails-rsc/WebpackPlugin compatible while adding " \
453+
"react-on-rails-rsc/RspackPlugin. Keep the pin until stable " \
454+
"react-on-rails-rsc@#{rsc_stable_package_version_target} " \
455+
"is published and tagged latest."
456+
end
457+
458+
def rsc_dependency_pin_failed_warning
463459
"Warning: Could not install the pinned react-on-rails-rsc@#{RSC_PACKAGE_VERSION_PIN}. " \
464-
"Rspack RSC projects require that version (or newer) for react-on-rails-rsc/RspackPlugin, " \
465-
"and the unversioned `latest` tag does not export it, so the generator left the pin in " \
466-
"package.json rather than install an incompatible version. " \
467-
"Run npm install react-on-rails-rsc@#{RSC_PACKAGE_VERSION_PIN} to finish setup."
460+
"All RSC projects are temporarily pinned to that version: the prerelease keeps " \
461+
"react-on-rails-rsc/WebpackPlugin compatible while adding react-on-rails-rsc/RspackPlugin, " \
462+
"and the unversioned `latest` tag may not include both until stable " \
463+
"#{rsc_stable_package_version_target} " \
464+
"is published, so the generator left the version pin in package.json rather than " \
465+
"install a potentially incompatible version."
466+
end
467+
468+
def rsc_dependency_pin_failure_details(used_version_pins)
469+
return unless used_version_pins
470+
471+
rsc_dependency_pin_failed_warning
472+
end
473+
474+
def rsc_dependency_failure_message(summary, used_version_pins, rsc_packages)
475+
[
476+
summary,
477+
rsc_dependency_pin_failure_details(used_version_pins),
478+
"You can install them manually by running:",
479+
" #{manual_add_packages_command(rsc_packages)}"
480+
].compact.join("\n")
468481
end
469482

470483
def remove_base_package_if_present
@@ -482,7 +495,7 @@ def remove_base_package_if_present
482495
⚠️ Could not remove base 'react-on-rails' package: #{e.message}
483496
484497
Please remove it manually:
485-
npm uninstall react-on-rails
498+
#{manual_remove_packages_command(['react-on-rails'])}
486499
MSG
487500
end
488501

@@ -498,14 +511,14 @@ def add_dev_dependencies
498511
⚠️ Failed to add development dependencies.
499512
500513
You can install them manually by running:
501-
npm install --save-dev #{dev_deps.join(' ')}
514+
#{manual_add_packages_command(dev_deps, dev: true)}
502515
MSG
503516
rescue StandardError => e
504517
GeneratorMessages.add_warning(<<~MSG.strip)
505518
⚠️ Error adding development dependencies: #{e.message}
506519
507520
You can install them manually by running:
508-
npm install --save-dev #{dev_deps.join(' ')}
521+
#{manual_add_packages_command(dev_deps, dev: true)}
509522
MSG
510523
end
511524

@@ -566,9 +579,7 @@ def install_js_dependencies
566579
567580
This could be due to network issues or package manager problems.
568581
You can install dependencies manually later by running:
569-
• npm install (if using npm)
570-
• yarn install (if using yarn)
571-
• pnpm install (if using pnpm)
582+
#{manual_install_dependencies_command}
572583
MSG
573584
false
574585
end
@@ -624,18 +635,49 @@ def fallback_package_manager
624635
end
625636

626637
def build_install_args(package_manager, dev, packages)
627-
base_commands = {
628-
"npm" => %w[npm install --save-exact],
629-
"yarn" => %w[yarn add --exact],
630-
"pnpm" => %w[pnpm add --save-exact],
631-
"bun" => %w[bun add --exact]
632-
}
633-
634-
base_args = base_commands.fetch(package_manager).dup
638+
base_args = package_manager_commands(package_manager).fetch(:install).dup
639+
base_args -= exact_install_flags_for(package_manager) if packages_include_semver_ranges?(packages)
635640
base_args << dev_flag_for(package_manager) if dev
636641
base_args + packages
637642
end
638643

644+
def build_remove_args(package_manager, packages)
645+
package_manager_commands(package_manager).fetch(:remove) + packages
646+
end
647+
648+
def manual_add_packages_command(packages, dev: false)
649+
build_install_args(fallback_package_manager, dev, packages).join(" ")
650+
end
651+
652+
def manual_install_dependencies_command
653+
"#{fallback_package_manager} install"
654+
end
655+
656+
def manual_remove_packages_command(packages)
657+
build_remove_args(fallback_package_manager, packages).join(" ")
658+
end
659+
660+
def package_manager_commands(package_manager)
661+
{
662+
"npm" => {
663+
install: %w[npm install --save-exact],
664+
remove: %w[npm uninstall]
665+
},
666+
"yarn" => {
667+
install: %w[yarn add --exact],
668+
remove: %w[yarn remove]
669+
},
670+
"pnpm" => {
671+
install: %w[pnpm add --save-exact],
672+
remove: %w[pnpm remove]
673+
},
674+
"bun" => {
675+
install: %w[bun add --exact],
676+
remove: %w[bun remove]
677+
}
678+
}.fetch(package_manager)
679+
end
680+
639681
def dev_flag_for(package_manager)
640682
case package_manager
641683
when "npm", "pnpm" then "--save-dev"
@@ -645,6 +687,27 @@ def dev_flag_for(package_manager)
645687
end
646688
end
647689

690+
def exact_install_flags_for(package_manager)
691+
case package_manager
692+
when "npm", "pnpm" then ["--save-exact"]
693+
when "yarn", "bun" then ["--exact"]
694+
else
695+
raise ArgumentError, "Unknown package manager for exact install flag: #{package_manager}"
696+
end
697+
end
698+
699+
def packages_include_semver_ranges?(packages)
700+
packages.any? { |package_spec| package_uses_semver_range?(package_spec) }
701+
end
702+
703+
def package_uses_semver_range?(package_spec)
704+
package_name_and_version = package_name_and_version_from_spec(package_spec)
705+
return false unless package_name_and_version
706+
707+
_package_name, package_version = package_name_and_version
708+
package_version.start_with?("~", "^")
709+
end
710+
648711
def filter_missing_packages(packages)
649712
existing = existing_package_names
650713
return packages if existing.empty?

0 commit comments

Comments
 (0)