Skip to content

fix: scope RSC client reference discovery#3219

Merged
justin808 merged 48 commits into
mainfrom
codex/fix-rsc-client-reference-scope
May 27, 2026
Merged

fix: scope RSC client reference discovery#3219
justin808 merged 48 commits into
mainfrom
codex/fix-rsc-client-reference-scope

Conversation

@justin808

@justin808 justin808 commented Apr 30, 2026

Copy link
Copy Markdown
Member

Summary

  • scope generated RSCWebpackPlugin clientReferences to Shakapacker config.source_path for both client and server bundles
  • update existing-config RSC transforms to inject the same scoped references
  • document why omitted clientReferences can accidentally scan vendored gem templates in CI

Fixes #3201

Test plan

  • bundle exec rspec react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb:99 react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb:112 react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb:639 react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb:652 react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb:726 react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb:1601 react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb:1817 react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb:1824
  • bundle exec rubocop react_on_rails/lib/generators/react_on_rails/rsc_setup.rb react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb

Note

Medium Risk
Touches RSC webpack generator migrations and CI build discovery; incorrect rewrites could break builds, but scope is limited to Pro RSC webpack setup, not security-critical runtime paths.

Overview
RSC webpack configs now limit client-reference scanning to app source instead of the project root. New and migrated setups inject rscClientReferences with directory: resolve(config.source_path) and pass clientReferences: rscClientReferences into RSCWebpackPlugin on both client and server bundles, avoiding CI failures from scanning paths like vendor/bundle.

The react_on_rails:rsc generator was refactored: webpack migration logic lives in rsc_setup/client_references.rb (JS-aware parsing, scoped rewrites, rollbacks, degraded paths when anchors/ESM block automation), and Hello Server layout selection moved to rsc_setup/layouts.rb. Doctor/verify now requires scoped clientReferences when the plugin is already present. Docs explain the CI risk, ESM/manual migration, and upgrade steps for apps that previously passed without scoping.

Risk: Medium — changes only affect RSC webpack generator output and build-time file discovery, not runtime auth or data paths.

Reviewed by Cursor Bugbot for commit eff6ffa. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • Documentation

    • Made RSC webpack guidance prescriptive: always point client references at the app source and warn about CI/compilation risks if omitted.
  • Bug Fixes

    • Generated RSC webpack configs now scope clientReferences to the application source to avoid compiling vendored templates or unrelated files in CI.
  • Tests

    • Expanded specs to validate the new RSC webpack configuration, import-injection idempotency, and plugin-update behavior.

@coderabbitai

coderabbitai Bot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Generator and webpack/rspack templates now define a scoped rscClientReferences (using resolve(config.source_path), recursive, JS/TS include) and ensure RSCWebpackPlugin is configured with clientReferences: rscClientReferences; existing configs are updated when possible. Tests, docs, and CHANGELOG were updated accordingly.

Changes

RSC clientReferences scoping

Layer / File(s) Summary
Data Shape / Constant
react_on_rails/lib/generators/react_on_rails/rsc_setup.rb
Adds rscClientReferences JS object: directory: resolve(config.source_path), recursive: true, `include: /.(js
Core Generator Logic
react_on_rails/lib/generators/react_on_rails/rsc_setup.rb
Generator injects clientReferences: rscClientReferences into new RSCWebpackPlugin calls; adds update_existing_rsc_webpack_config and import-detection helpers to insert or update rscClientReferences and rewrite plugin constructor options in-place when needed.
Template Wiring
react_on_rails/lib/generators/react_on_rails/templates/.../config/webpack/clientWebpackConfig.js.tt, .../serverWebpackConfig.js.tt
Templates now declare rscClientReferences and instantiate new RSCWebpackPlugin({ isServer: <bool>, clientReferences: rscClientReferences }) for client and server bundles.
Wiring / Injection Safety
react_on_rails/lib/generators/react_on_rails/rsc_setup.rb
Injection logic conditionally adds const { config } = require('shakapacker') and const { resolve } = require('path') only when needed; detects existing imports or custom anchors and emits a warning if it cannot safely inject scoped references.
Tests / Specs
react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb, react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb
Specs updated to assert clientReferences: rscClientReferences, directory: resolve(config.source_path), and correct isServer booleans; added contexts for idempotent import injection, plugin in-place updates, and the custom-import-anchor warning behavior.
Documentation / Changelog
docs/oss/migrating/rsc-preparing-app.md, CHANGELOG.md
Docs and changelog updated to recommend scoping clientReferences to the app source and to highlight CI/compilation failures caused by scanning vendored templates (e.g., vendor/bundle).

Sequence Diagram

sequenceDiagram
  participant Generator
  participant File as "webpack config file"
  participant Shakapacker
  participant RSCPlugin as "RSCWebpackPlugin"

  Generator->>File: open/read file
  alt existing RSCWebpackPlugin found without clientReferences
    Generator->>File: rewrite plugin constructor to add clientReferences
  else no RSCWebpackPlugin
    Generator->>File: insert imports for Shakapacker & path
    Generator->>Shakapacker: use config.source_path
    Generator->>File: insert rscClientReferences + new RSCWebpackPlugin({..., clientReferences})
  end
  File->>RSCPlugin: RSCWebpackPlugin configured with scoped clientReferences
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

I’m a rabbit in the code, I hop and fix the trail,
Scoped paths keep CI happy, no more vendor derail,
I stitch imports and references with a careful, quiet tap,
Builds purr in the morning — no mysterious map,
🐇✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: scope RSC client reference discovery' directly addresses the main change: scoping RSCWebpackPlugin's clientReferences to prevent scanning vendored dependencies.
Linked Issues check ✅ Passed All objectives from issue #3201 are met: clientReferences are scoped to config.source_path, generated webpack configs include proper configuration, the generator handles in-place transforms safely, and documentation/changelog explain the fix.
Out of Scope Changes check ✅ Passed All changes directly support the primary objective of scoping RSC client references: generator updates, template modifications, documentation, changelog, and comprehensive test coverage align with issue #3201 requirements.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fix-rsc-client-reference-scope

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented Apr 30, 2026

Copy link
Copy Markdown

Greptile Summary

This PR scopes the RSCWebpackPlugin's clientReferences to config.source_path (from Shakapacker) on both the client and server webpack configs, preventing the plugin from scanning the entire project root — including vendor/bundle gem templates in CI — for 'use client' directives. The fix is applied consistently across both the new-install template files (.js.tt) and the existing-config transform paths in rsc_setup.rb, with updated specs and documentation.

Confidence Score: 5/5

Safe to merge — changes are consistent across all code paths, no logic errors or regressions identified.

All six changed files are internally consistent: both template (.tt) and existing-config transform paths inject the same scoped rscClientReferences object, config is never double-imported (server template already has it; client template adds it only in the RSC block), and specs validate the new content. No P0 or P1 findings.

No files require special attention.

Important Files Changed

Filename Overview
react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Updates both transform paths (update_server_webpack_config_for_rsc and update_client_webpack_config_for_rsc) to inject rscClientReferences scoped to config.source_path; logic is consistent with the template changes.
react_on_rails/lib/generators/react_on_rails/templates/base/base/config/webpack/clientWebpackConfig.js.tt Adds const { config } from shakapacker, const { resolve } from path, and rscClientReferences object in the RSC block; passes rscClientReferences to RSCWebpackPlugin.
react_on_rails/lib/generators/react_on_rails/templates/base/base/config/webpack/serverWebpackConfig.js.tt Adds const { resolve } from path and rscClientReferences in the RSC block; config is already imported at the top via shakapacker so no duplicate import needed.
react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb Updates spec assertions to verify clientReferences and directory: resolve(config.source_path) are present in generated configs; prior exact-string assertions replaced with more flexible checks.
react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb Extends existing RSCWebpackPlugin assertions to also verify clientReferences and directory: resolve(config.source_path) for server and client configs on both webpack and rspack paths.
docs/oss/migrating/rsc-preparing-app.md Documentation updated to explain the vendor/bundle CI scanning risk when clientReferences is omitted, and to recommend always pointing it at the application source directory.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Generator invoked with RSC] --> B{New install or existing config?}
    B -- New install --> C[Render .js.tt templates]
    B -- Existing config --> D[update_webpack_configs_for_rsc]

    C --> E[clientWebpackConfig.js.tt\nInjects: config from shakapacker\n resolve from path\n RSCWebpackPlugin\n rscClientReferences scoped to config.source_path]
    C --> F[serverWebpackConfig.js.tt\nInjects: resolve from path\n RSCWebpackPlugin\n rscClientReferences scoped to config.source_path]

    D --> G[update_client_webpack_config_for_rsc\ngsub: add RSCWebpackPlugin import +\n shakapacker config + rscClientReferences]
    D --> H[update_server_webpack_config_for_rsc\ngsub: add RSCWebpackPlugin import +\n resolve + rscClientReferences]

    E --> I[RSCWebpackPlugin with clientReferences\ndirectory: resolve config.source_path\nrecursive: true\ninclude: js,ts,jsx,tsx]
    F --> I
    G --> I
    H --> I

    I --> J[Webpack scans only source_path\nSkips vendor/bundle in CI]
Loading

Reviews (1): Last reviewed commit: "Scope RSC client references to app sourc..." | Re-trigger Greptile

@coderabbitai

coderabbitai Bot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

An error occurred during the review process. Please try again later.

Walkthrough

The PR resolves a webpack compilation issue where RSCWebpackPlugin's default recursive project root scan could inadvertently include vendored gem templates in CI environments. Changes include updating documentation, modifying webpack config generators and templates to explicitly compute and pass clientReferences scoped to the application's source directory, and updating corresponding tests.

Changes

Cohort / File(s) Summary
Documentation
docs/oss/migrating/rsc-preparing-app.md
Updated guidance for RSCWebpackPlugin's clientReferences option to be prescriptive and emphasize the CI/compilation risk when the plugin's default recursive scan inadvertently picks up vendored templates.
Generator Logic
react_on_rails/lib/generators/react_on_rails/rsc_setup.rb
Enhanced transformations for both server and client webpack configs to compute rscClientReferences from source_path and inject it into RSCWebpackPlugin instantiation alongside isServer parameter.
Webpack Config Templates
react_on_rails/lib/generators/react_on_rails/templates/base/base/config/webpack/clientWebpackConfig.js.tt, react_on_rails/lib/generators/react_on_rails/templates/base/base/config/webpack/serverWebpackConfig.js.tt
Updated templates to build rscClientReferences from config.source_path with recursive scan and pass it to RSCWebpackPlugin configuration in both client and server bundles.
Test Specs
react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb, react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb
Extended assertions to validate that generated webpack/rspack configs include both clientReferences: rscClientReferences and directory: resolve(config.source_path) in the RSC plugin configuration.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

A rabbit hops through webpack's halls,
Where vendor gems no longer call,
With source_path the scope is tight,
Client references bundled right,
No more CI surprises—what a sight! 🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: scoping RSC client reference discovery to prevent unintended scanning of vendored dependencies.
Linked Issues check ✅ Passed The PR fully addresses issue #3201 by scoping clientReferences to config.source_path in generated webpack configs, updating RSC transforms, and documenting the scoping requirement.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #3201 objectives: scoping RSCWebpackPlugin clientReferences, updating existing-config transforms, adding documentation, and updating relevant tests.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fix-rsc-client-reference-scope

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude

claude Bot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Code Review — PR #3219: Scope RSC client reference discovery

Summary

Correct fix for a real CI reliability issue. Scoping RSCWebpackPlugin's clientReferences to Shakapacker::config.source_path prevents the plugin from accidentally compiling vendored gem templates under vendor/bundle. The template, update-transformer, docs, and changelog are all updated consistently.


Issues

1. verify_rsc_webpack_transforms is not updated (medium severity)

check_rsc_server_config and check_rsc_client_config only verify that RSCWebpackPlugin and rscBundle are present. They do not check for rscClientReferences. This means apps that already have RSCWebpackPlugin set up (without scoping) will pass the self-check silently and never receive the warning that their config is incomplete.

Consider adding to both check methods:

missing << "rscClientReferences in serverWebpackConfig.js" unless content.include?("rscClientReferences")

2. Dropped isServer assertions weaken test coverage (low severity)

The updated specs replace the original isServer: false / isServer: true assertions with only clientReferences assertions. A future change that accidentally omits isServer from the plugin constructor would pass the suite undetected. Both sets of assertions should be kept.

See inline comments on specific lines.

3. Asymmetric test coverage for webpack serverWebpackConfig (low severity)

rsc_generator_spec.rb around line 99 ("adds RSCWebpackPlugin to serverWebpackConfig" for the webpack path) was not updated to include clientReferences assertions, even though the equivalent rspack test (line 726) was updated. The install-generator spec covers this case, but the rsc-generator spec for the default webpack path is now inconsistent.


Minor observations

  • In serverWebpackConfig.js.tt, config is already imported via const { merge, config } = require('shakapacker'); at the top, so resolve(config.source_path) works without a new import. In clientWebpackConfig.js.tt, a separate const { config } = require('shakapacker'); is correctly added because config wasn't imported there before. The asymmetry is intentional and correct.
  • path.resolve() applied to an already-absolute path (which source_path returns) is a no-op. Harmless, but worth noting in comments for future readers wondering why it's there.

Verdict

The fix is correct, well-targeted, and consistent across templates and the update-transformer path. The issues above are minor and shouldn't block merge, but closing the verify_rsc_webpack_transforms gap would make the self-check more useful for users upgrading existing configs.

Comment thread react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb
Comment thread react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb
Comment thread react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb
Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Outdated
Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Outdated
@claude

claude Bot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Code Review

Good, targeted fix. The root cause (unscoped clientReferences defaulting to project-root scan) is correctly addressed across both new-generate and update-existing-config paths, and the documentation update makes the risk concrete rather than abstract.

Summary

Area Assessment
Correctness ✅ Fix is accurate — resolve(config.source_path) is the right scope
Both paths covered ✅ Template files + existing-config transformers both updated
CHANGELOG / docs ✅ Clear and informative
Test coverage ⚠️ Minor gaps (see below)
Code duplication ⚠️ rscClientReferences repeated 4×

Issues

1. Test gap — webpack server config (not in diff, so inline comment not possible)

rsc_generator_spec.rb:99–103 only asserts RSCWebpackPlugin / react-on-rails-rsc/WebpackPlugin for the webpack server config. It does not verify clientReferences: rscClientReferences or directory: resolve(config.source_path), so a regression in serverWebpackConfig.js.tt would go undetected. The analogous rspack test at line 726 does assert both — these two should match.

Suggested addition to the test at line 99:

it "adds RSCWebpackPlugin to serverWebpackConfig" do
  assert_file "config/webpack/serverWebpackConfig.js" do |content|
    expect(content).to include("RSCWebpackPlugin")
    expect(content).to include("react-on-rails-rsc/WebpackPlugin")
    expect(content).to include("clientReferences: rscClientReferences")   # add
    expect(content).to include("directory: resolve(config.source_path)")  # add
  end
end

Similarly rsc_generator_spec.rb:639–643 (rspack server config, standard path) has the same gap versus the "Rspack RSC runtime compatibility" block at line 726.

2. Possible duplicate const { config } declaration (inline comment posted)

update_client_webpack_config_for_rsc unconditionally injects const { config } = require('shakapacker'); after matching commonWebpackConfig. If a user's existing clientWebpackConfig.js already imports config (not uncommon for apps that customise output paths or aliases), webpack will throw SyntaxError: Identifier 'config' has already been declared at config-load time.

A simple guard: check content.include?("require('shakapacker')") before injecting, or extend the regex to match and augment an existing shakapacker import rather than adding a second one.

3. Code duplication (inline comment posted)

The rscClientReferences object literal is copy-pasted in four places:

  • serverWebpackConfig.js.tt
  • clientWebpackConfig.js.tt
  • update_server_webpack_config_for_rsc (transformer)
  • update_client_webpack_config_for_rsc (transformer)

A private helper method on the generator class for the Ruby string would keep the transformer paths in sync and reduce the chance of partial updates in the future. The templates remain separate (ERB, not Ruby) but are easier to audit since they're the authoritative source.


Minor notes

  • The tests at lines 113 and 651 previously asserted new RSCWebpackPlugin({ isServer: false }) as a single-line string. That assertion was dropped when the call was reformatted to multiline. Neither the old nor new tests now verify isServer: false / isServer: true — low risk since those params were already present before this PR, but worth noting if someone later changes the plugin call signature.
  • resolve(config.source_path): if source_path is already absolute, resolve() is a no-op. If relative, it resolves against process.cwd() (the project root at build time). Both cases are correct. No issue here, just documenting the behavior is intentional.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d49943e2f3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Outdated
Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 08e341ea21

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Outdated
Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Server config missing duplicate resolve import guard
    • The server RSC webpack transform now skips injecting the path resolve import when it already exists and has regression coverage for that case.

Create PR

Or push these changes by commenting:

@cursor push f73f903f50
Preview (f73f903f50)
diff --git a/react_on_rails/lib/generators/react_on_rails/rsc_setup.rb b/react_on_rails/lib/generators/react_on_rails/rsc_setup.rb
--- a/react_on_rails/lib/generators/react_on_rails/rsc_setup.rb
+++ b/react_on_rails/lib/generators/react_on_rails/rsc_setup.rb
@@ -385,11 +385,11 @@
         # Add RSCWebpackPlugin import after bundler require
         server_injected_imports = [
           "\\1",
-          "const { resolve } = require('path');",
+          ("const { resolve } = require('path');" unless path_resolve_imported?(content)),
           "const { RSCWebpackPlugin } = require('react-on-rails-rsc/WebpackPlugin');",
           "",
           rsc_client_references_js
-        ].join("\n")
+        ].compact.join("\n")
 
         gsub_file(
           config_path,

diff --git a/react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb b/react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb
--- a/react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb
+++ b/react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb
@@ -199,6 +199,38 @@
     end
   end
 
+  context "when the server webpack config already imports path resolve" do
+    before(:all) do
+      prepare_destination
+      simulate_existing_rails_files(package_json: true)
+      simulate_npm_files(package_json: true)
+      simulate_existing_file("config/initializers/react_on_rails_pro.rb", <<~RUBY)
+        ReactOnRailsPro.configure do |config|
+          config.server_renderer = "NodeRenderer"
+        end
+      RUBY
+      simulate_existing_file("Procfile.dev", "rails: bin/rails s\n")
+      simulate_pro_webpack_files
+      simulate_existing_file(
+        "config/webpack/serverWebpackConfig.js",
+        "const { resolve } = require('path');\n#{pro_server_webpack_content}"
+      )
+
+      Dir.chdir(destination_root) do
+        run_generator(["--force"])
+      end
+    end
+
+    it "does not inject a duplicate resolve import" do
+      assert_file "config/webpack/serverWebpackConfig.js" do |content|
+        expect(content.scan("const { resolve } = require('path');").length).to eq(1)
+        expect(content).to include("clientReferences: rscClientReferences")
+        expect(content).to include("directory: resolve(config.source_path)")
+        expect(content).to include("isServer: true")
+      end
+    end
+  end
+
   context "when Pro is installed with a canonical legacy hello_world layout" do
     before(:all) do
       prepare_destination

You can send follow-ups to the cloud agent here.

Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Outdated
@claude

claude Bot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Code Review: PR #3219 — Scope RSC client reference discovery

Overview

This PR correctly fixes the root cause of issue #3201: RSCWebpackPlugin was previously called without explicit clientReferences, causing it to recursively scan the entire project root and accidentally compile vendored gem templates (e.g., vendor/bundle) in CI. The fix computes clientReferences from Shakapacker's source_path and passes it to every RSCWebpackPlugin call.


What's Good

  • Correct fix — scoping clientReferences to resolve(config.source_path) directly addresses the CI scanning issue.
  • Template updates are consistent — both clientWebpackConfig.js.tt and serverWebpackConfig.js.tt are updated symmetrically.
  • commonjs_named_imported? helper — nice reusable abstraction for detecting CommonJS destructure imports; correctly handles default-value bindings ({ config = default }).
  • Duplicate-import guards — the client config path now conditionally skips injecting shakapacker and path imports when they're already present.
  • Test coverage — new tests for fresh install, already-imported config, and aliased destructuring cases.

Issues & Suggestions

Bug: Server config injection lacks the resolve deduplication guard

update_server_webpack_config_for_rsc always injects const { resolve } = require('path'); unconditionally, while the client config path now guards with unless path_resolve_imported?(content). If a user has a custom serverWebpackConfig.js that already imports resolve from path (but hasn't configured RSCWebpackPlugin yet), the generator will produce a duplicate binding and a runtime SyntaxError: Identifier 'resolve' has already been declared.

# current server path — always injects, no guard:
server_injected_imports = [
  "\\1",
  "const { resolve } = require('path');",
  ...
].join("\n")

# should mirror the client path:
server_injected_imports = [
  "\\1",
  ("const { resolve } = require('path');" unless path_resolve_imported?(content)),
  ...
].compact.join("\n")

A test covering a server config with a pre-existing resolve import would catch this regression.


Aliased config import creates two require('shakapacker') calls

commonjs_named_imported? correctly returns false for const { config: shakapackerConfig } = require('shakapacker'); (the alias form), so the generator injects a second require('shakapacker'):

// before generator runs
const { config: shakapackerConfig } = require('shakapacker');

// after generator runs
const { config: shakapackerConfig } = require('shakapacker');  // original
const { config } = require('shakapacker');                      // injected

Node's module cache deduplicates this at runtime, so it's not a correctness bug. But it produces a surprising double-require visible to users. Worth a brief comment in the aliased-imports test explaining this is intentional.


Multiline destructuring silently fails detection

commonjs_named_imported? processes one line at a time via content.lines.any?. A multiline pattern:

const {
  config,
} = require('shakapacker');

won't be detected, leading to a duplicate import. Unlikely in Shakapacker-generated configs, but worth a brief comment on the method.


Minor: include pattern is missing .mjs/.cjs

include: /\.(js|ts|jsx|tsx)$/,

The doctor command already recognizes .mjs and .cjs entrypoints. RSC "use client" directives in .mjs files would be silently missed. Low priority, but aligning with the doctor command's extension set would be more future-proof.


Verdict

The fix is correct and well-tested for the common cases. The server config deduplication gap is the only item worth addressing before merge — it's a latent SyntaxError for users with a non-standard server config who run the RSC generator. The rest are minor nits.

Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Outdated
Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 43a87cc3ce

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Outdated
@claude

claude Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor

Code Review

Overview

This PR fixes a real CI bug (#3201) where the RSCWebpackPlugin, when clientReferences was omitted, scanned the entire project tree and accidentally compiled vendored gem templates under vendor/bundle. The fix scopes discovery to Shakapacker's source_path by generating a rscClientReferences object and injecting it into both the generator templates and the existing-config rewrite logic.

The approach is sound: generate the scoped references once at the module level, share them between server and client plugin calls, guard against duplicate imports when rewriting existing configs, and document the risk clearly. The new test matrix is comprehensive.


Issues & Suggestions

Bug risk: path_resolve_imported? fallback regex is too broad

def path_resolve_imported?(content)
  commonjs_named_imported?(content, "path", "resolve") ||
    content.match?(/^\s*const\s+resolve\s*=/)   # ← too broad
end

The second arm matches any const resolve = … declaration—including user-defined functions or callbacks named resolve (e.g., from a Promise, a custom helper). If a user's config happens to have const resolve = promiseResolve, the method returns true, the generator skips injecting require('path'), and resolve(config.source_path) silently calls the wrong function at build time.

The first arm (commonjs_named_imported?) already handles the standard forms:

  • const { resolve } = require('path');
  • const {\n resolve,\n} = require('path');

The only case the second arm adds is something like const resolve = require('path').resolve, which is unusual in webpack configs. Consider narrowing the fallback:

def path_resolve_imported?(content)
  commonjs_named_imported?(content, "path", "resolve") ||
    content.match?(/^\s*const\s+resolve\s*=\s*require\(['"]path['"]\)/)
end

Minor: aliased shakapacker imports produce a redundant require call

When an existing config has const { config: shakapackerConfig } = require('shakapacker');, shakapacker_config_imported? returns false (the binding config: alias is not recognized as the config binding), so the generator injects a second require('shakapacker') call:

const { config: shakapackerConfig } = require('shakapacker'); // pre-existing
// ...injected by generator:
const { config } = require('shakapacker');

This is harmless at runtime due to Node module caching and is explicitly tested—but it may surprise users who audit their generated configs. A comment in the spec or a TODO noting the known duplicate would help future readers understand the intent.

Minor: commonjs_named_imported? splits on , but does not strip inline comments

If a destructure has inline comments on a binding line, e.g.:

const {
  merge, // used for deep config merge
  config,
} = require('shakapacker');

After split(","), the token " // used for deep config merge\n config" does not strip to "config", so config would not be detected. This is an edge case unlikely to appear in generated webpack configs, but it could bite users who add comments inside their destructures before running the RSC generator.


Positive notes

  • The duplicate-import guards (shakapacker_config_imported?, path_resolve_imported?) and their test matrix (single-line, multi-line, aliased, pre-existing) are thorough and clearly written.
  • Using Regexp.escape in commonjs_named_imported? is correct and prevents any package name from being interpreted as a regex.
  • The <<~'JS'.chomp heredoc correctly prevents Ruby string interpolation in the generated JavaScript snippet.
  • Template and rewrite logic stay in sync—the same rscClientReferences shape is used in both paths, so fresh installs and existing-config upgrades produce identical output.
  • CHANGELOG and docs updates are accurate and explain the why clearly.

Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Outdated
Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Outdated
Comment thread react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb
@claude

claude Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor

Code Review

Overview

This PR scopes RSC webpack plugin's client-reference scanning to Shakapacker.config.source_path instead of the entire project root, preventing CI failures where vendored gem templates under vendor/bundle get accidentally picked up and compiled by webpack. The fix is applied to both the generator templates (fresh installs) and the existing-config transform methods.

The core approach — computing rscClientReferences from Shakapacker's source_path and passing it as clientReferences to RSCWebpackPlugin — is correct and well-targeted.


Issues Found

Existing apps with old generator output are not auto-upgraded

Both update_server_webpack_config_for_rsc and update_client_webpack_config_for_rsc bail out early when RSCWebpackPlugin is already present:

return if content.include?("RSCWebpackPlugin")

This means users who previously ran the generator (which produced new RSCWebpackPlugin({ isServer: true }) without clientReferences) won't get the scoped fix automatically — their CI will keep failing. The PR fixes new installs and re-runs from scratch, but not incremental upgrades. This should be documented clearly in the changelog and/or a migration note, since those users are the ones hitting issue #3201.

Aliased Shakapacker import causes a duplicate require('shakapacker')

When an existing client webpack config has const { config: shakapackerConfig } = require('shakapacker');, shakapacker_config_imported? correctly returns false (since config is not bound under that name), so the generator injects a fresh const { config } = require('shakapacker');. The result is two separate require('shakapacker') lines in the same file:

const { config: shakapackerConfig } = require('shakapacker'); // original
const { config } = require('shakapacker');                    // injected

The test covers this case and treats it as acceptable. Node's module cache means there's no runtime cost, but it's a confusing code smell. A comment in the test explaining why this is intentional would help future maintainers.

/m flag in commonjs_named_imported? is a no-op

The regex uses /m but contains no . — in Ruby, /m only changes whether . matches \n. Character classes like [^}]* and \s already match newlines regardless. The flag is harmless but may mislead future readers.

path_resolve_imported? misses const path = require('path')

If an existing file imports the entire path module as const path = require('path') and calls path.resolve(...), neither branch of path_resolve_imported? detects it. The generator will add a redundant const { resolve } = require('path');. The two bindings don't conflict, so this is not a bug — but it is a minor false negative worth noting.


Positive Aspects

  • Duplication-prevention logic is well-tested: four distinct contexts cover single-line named import, multi-line destructured import, aliased import, and already-imported-resolve.
  • The rsc_client_references_js helper de-duplicates the JS snippet and keeps templates and transform methods in sync.
  • Doc and changelog updates are accurate and clearly explain the risk.

Verdict

The fix is correct and addresses the root cause. The main gap is that existing apps that already have RSCWebpackPlugin configured won't be auto-upgraded and need to manually add clientReferences. This should be called out explicitly in the changelog entry or a migration note, since those users are the ones experiencing CI failures from issue #3201.

Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Outdated
Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Outdated
Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Outdated
Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 184460aa25

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Partial gsub can produce undefined variable reference
    • Guarded the plugin rewrite on successful client-reference setup and added verification/tests so customized configs warn instead of receiving undefined references.

Create PR

Or push these changes by commenting:

@cursor push 39dc680411
Preview (39dc680411)
diff --git a/react_on_rails/lib/generators/react_on_rails/rsc_setup.rb b/react_on_rails/lib/generators/react_on_rails/rsc_setup.rb
--- a/react_on_rails/lib/generators/react_on_rails/rsc_setup.rb
+++ b/react_on_rails/lib/generators/react_on_rails/rsc_setup.rb
@@ -489,6 +489,7 @@
         missing = []
         missing << "RSCWebpackPlugin in serverWebpackConfig.js" unless content.include?("RSCWebpackPlugin")
         missing << "rscBundle parameter in serverWebpackConfig.js" unless content.include?("rscBundle")
+        missing.concat(check_rsc_client_references(content, "serverWebpackConfig.js"))
         missing
       end
 
@@ -497,7 +498,10 @@
         return [] unless File.exist?(path)
 
         content = File.read(path)
-        content.include?("RSCWebpackPlugin") ? [] : ["RSCWebpackPlugin in clientWebpackConfig.js"]
+        missing = []
+        missing << "RSCWebpackPlugin in clientWebpackConfig.js" unless content.include?("RSCWebpackPlugin")
+        missing.concat(check_rsc_client_references(content, "clientWebpackConfig.js"))
+        missing
       end
 
       def check_rsc_scob_config
@@ -521,7 +525,7 @@
       def update_existing_rsc_webpack_config(config_path, content, is_server:)
         return if content.include?("clientReferences: rscClientReferences")
 
-        add_rsc_client_references_setup(config_path, content, is_server: is_server)
+        return unless add_rsc_client_references_setup(config_path, content, is_server: is_server)
 
         gsub_file(
           config_path,
@@ -549,9 +553,25 @@
                            %r{(const commonWebpackConfig = require\('\./commonWebpackConfig'\);)}
                          end
 
+        return false unless content.match?(import_pattern)
+
         gsub_file(config_path, import_pattern, injected_imports)
+        true
       end
 
+      def check_rsc_client_references(content, filename)
+        return [] unless content.include?("RSCWebpackPlugin")
+
+        missing = []
+        unless content.include?("clientReferences: rscClientReferences")
+          missing << "clientReferences option in #{filename}"
+        end
+        if content.include?("clientReferences: rscClientReferences") && !content.include?("const rscClientReferences =")
+          missing << "rscClientReferences definition in #{filename}"
+        end
+        missing
+      end
+
       def shakapacker_config_imported?(content)
         commonjs_named_imported?(content, "shakapacker", "config")
       end

diff --git a/react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb b/react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb
--- a/react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb
+++ b/react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb
@@ -303,6 +303,68 @@
     end
   end
 
+  context "when existing RSC webpack configs have customized import anchors" do
+    before(:all) do
+      prepare_destination
+      simulate_existing_rails_files(package_json: true)
+      simulate_npm_files(package_json: true)
+      simulate_existing_file("config/initializers/react_on_rails_pro.rb", <<~RUBY)
+        ReactOnRailsPro.configure do |config|
+          config.server_renderer = "NodeRenderer"
+        end
+      RUBY
+      simulate_existing_file("Procfile.dev", "rails: bin/rails s\n")
+      simulate_pro_webpack_files
+      simulate_existing_file("config/webpack/serverWebpackConfig.js", <<~JS)
+        const { RSCWebpackPlugin } = require('react-on-rails-rsc/WebpackPlugin');
+        const webpack = require('webpack');
+
+        const configureServer = (rscBundle = false) => {
+          const serverWebpackConfig = { plugins: [] };
+
+          if (!rscBundle) {
+            serverWebpackConfig.plugins.push(new RSCWebpackPlugin({ isServer: true }));
+          }
+
+          return serverWebpackConfig;
+        };
+
+        module.exports = { default: configureServer };
+      JS
+      simulate_existing_file("config/webpack/clientWebpackConfig.js", <<~JS)
+        const { RSCWebpackPlugin } = require('react-on-rails-rsc/WebpackPlugin');
+
+        const configureClient = () => {
+          const clientConfig = { plugins: [] };
+          clientConfig.plugins.push(new RSCWebpackPlugin({ isServer: false }));
+
+          return clientConfig;
+        };
+
+        module.exports = configureClient;
+      JS
+
+      Dir.chdir(destination_root) do
+        run_generator(["--force"])
+      end
+    end
+
+    it "does not add undefined rscClientReferences references" do
+      assert_file "config/webpack/serverWebpackConfig.js" do |content|
+        expect(content).not_to include("clientReferences: rscClientReferences")
+        expect(content).not_to include("const rscClientReferences =")
+      end
+      assert_file "config/webpack/clientWebpackConfig.js" do |content|
+        expect(content).not_to include("clientReferences: rscClientReferences")
+        expect(content).not_to include("const rscClientReferences =")
+      end
+
+      warning_text = GeneratorMessages.messages.join("\n")
+      expect(warning_text).to include("clientReferences option in serverWebpackConfig.js")
+      expect(warning_text).to include("clientReferences option in clientWebpackConfig.js")
+    end
+  end
+
   context "when the client webpack config uses aliased imports" do
     before(:all) do
       prepare_destination

You can send follow-ups to the cloud agent here.

Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Outdated
@claude

claude Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor

Code Review — PR #3219: Scope RSC client reference discovery

Overview

This PR fixes a real CI problem (#3201) cleanly: by scoping clientReferences to config.source_path the plugin stops accidentally compiling vendored gem templates under vendor/bundle. The helper-method extraction is a good structural improvement, the templates are updated consistently, and the test matrix covers the main new behaviours.

I found four issues worth addressing before merge — one is a silent failure path.


Issues

1. Silent no-op when user has a customised RSCWebpackPlugin call (medium — silent failure)

update_existing_rsc_webpack_config matches with:

/new RSCWebpackPlugin\(\{\s*isServer: #{is_server}\s*\}\)/

This only matches the original bare form new RSCWebpackPlugin({ isServer: true }). If the user ever added a second option (new RSCWebpackPlugin({ isServer: true, someOtherOption: … })), gsub_file finds no match and the file is left unchanged — but the method returns silently with no warning. The user would then have the new rscClientReferences constant injected into their file but it would never actually be used.

Consider broadening the pattern to tolerate trailing options:

/new RSCWebpackPlugin\(\{([^}]*?isServer: #{is_server}[^}]*?)\}\)/

…and either raise/warn on no-match, or verify the replacement happened.

2. Aliased Shakapacker import generates a duplicate require (minor — potentially confusing)

commonjs_named_imported? checks for binding == binding_name and binding.start_with?("#{binding_name} =") but not for binding_name: (rename alias). So const { config: shakapackerConfig } = require('shakapacker'); is not recognised as an import of config, causing the generator to inject a second require('shakapacker'). The test at line 334 documents this as intentional, but:

  • The generated file now contains two require('shakapacker') statements.
  • If the user intentionally aliased config to avoid a naming collision (e.g. they already have a local config variable), the injected const { config } = require('shakapacker'); silently shadows or conflicts with it.

At minimum the test description "injects a usable config import without redeclaring resolve" should call out that it does create a second Shakapacker require. Better still, recognise config: aliases in commonjs_named_imported?.

3. Idempotency guard can be tricked by a comment (low)

return if content.include?("clientReferences: rscClientReferences")

A line like // clientReferences: rscClientReferences in a comment would cause the guard to fire, skipping the migration silently. A tighter check (e.g. a regex requiring the surrounding new RSCWebpackPlugin context, or checking for the constant definition) would be more robust.

4. Superfluous /m flag on the commonjs_named_imported? regex (nit)

pattern = /^\s*const\s+\{([^}]*)\}\s*=\s*require\(['"]#{Regexp.escape(package_name)}['"]\);?/m

In Ruby the /m flag makes . match newlines — it has no effect here because the pattern uses [^}] (which already spans newlines) and doesn't use . at all. It's harmless but misleading; removing it would help future readers.


What's good

  • The root cause fix (scope directory to source_path) is correct and the generated output looks right.
  • Import-deduplication helpers (shakapacker_config_imported?, path_resolve_imported?) are well-extracted and the multi-line destructuring test is appreciated.
  • The rspack path is handled transparently via destination_config_path — no duplication needed.
  • Changelog and docs update are clear and accurate.

Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb
@claude

claude Bot commented May 26, 2026

Copy link
Copy Markdown
Contributor

Code Review - PR 3219: fix: scope RSC client reference discovery

Overview

This PR fixes a real production issue: without a scoped clientReferences, the RSCWebpackPlugin scans the entire project root in CI, hitting vendored gem templates under vendor/bundle and slowing or failing builds. The fix is correct and the generated template update is straightforward.

The bulk of the change is a new ~1,300-line lightweight JavaScript parser (client_references.rb) that migrates existing webpack configs -- injecting the scoped helper, rewriting plugin invocations, and rolling back on partial failure. The test suite is excellent: 2,700+ lines covering regex literals with braces, CRLF endings, shorthand/quoted keys, nested objects, pretend/skip modes, and rollback scenarios.

Strengths

  • Excellent safety: all three migration paths have full rollback on partial failure; unparseable sections block the entire file rather than silently skipping.
  • Well-documented limitations: O(n^3 x m) complexity, ESM/backtick limitation, and multi-character keyword gap in REGEX_LITERAL_PRECEDERS are surfaced to callers and users via warnings.
  • Good module split: extracting Layouts and ClientReferences out of rsc_setup.rb improves navigability.

Issues Found

1. Predicate with side effects in rsc_client_references_rewrite_needed?

Named as a predicate but its own comment says the predicate also prepares the rewrite -- calling ensure_rsc_client_references_setup inside it may write to disk. A _needed? method mutating the filesystem is a footgun for future maintainers. Consider renaming or splitting into an explicit setup step and a pure predicate.

2. rsc_plugin_sections_safe_to_rewrite? passes is_server: but the unparseable count ignores it

rsc_plugin_option_sections_partition counts unparseable invocations across all plugins regardless of is_server. An unparseable isServer: false block therefore blocks the is_server: true migration of the same file. The warning message explains this is intentional, but the is_server: kwarg makes the API misleading. Either remove the parameter from rsc_plugin_sections_safe_to_rewrite? or add a comment making the cross-bundle scope explicit.

3. rsc_plugin_options_without_comments name implies plugin-options-only scope

The name implies it operates only on a plugin options subsection, but commonjs_named_imported?, top_level_destructured_binding?, and destructuring_declares_binding? all call it on full file content. A name like strip_js_comments would make the general-purpose scope obvious.

4. first_significant_js_index silently passes for positional-string plugin calls

The JS_STRING_DELIMITERS.include?(prev_state) clause skips characters while exiting a string literal. For an invocation like new RSCWebpackPlugin("namespace", { isServer: false }), the method returns the , index rather than {. The caller skips the invocation with no warning -- rsc_plugin_client_references_configured? then returns true for an invocation that was never analyzed, masking a potentially unscoped plugin from the doctor check. A note on the branch in rsc_plugin_option_sections_partition that checks for { as first token, noting that positional-arg invocations silently pass verification, would help future readers.

5. Minor: rsc_plugin_uses_scoped_client_references? early-returns false when sections is empty

When no parseable isServer: bool sections exist, rsc_plugin_references_scoped_client_references? returns false even when the scoped helper is already defined. update_existing_rsc_webpack_config then falls through to warn_missing_rsc_plugin_target. The result is correct, but the early-return guard reads as "already done, skip" -- a comment noting that empty sections also falls through here would prevent confusion.

Summary

The core fix is solid and the edge-case coverage is excellent. Main actionable items: (1) rename or document the side-effecting rsc_client_references_rewrite_needed?, (2) clarify the cross-bundle scope of rsc_plugin_sections_safe_to_rewrite?, (3) rename rsc_plugin_options_without_comments. None of these block the fix.

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Bencher report chunks were too large to post as PR comments.

View the full report in the job summary.

@justin808 justin808 merged commit 9b62743 into main May 27, 2026
63 checks passed
@justin808 justin808 deleted the codex/fix-rsc-client-reference-scope branch May 27, 2026 00:42
@justin808

Copy link
Copy Markdown
Member Author

@/var/folders/4n/m5j7lmks4mx8rt8x51pgw7t80000gn/T/tmp.gI5v401yKq

justin808 added a commit that referenced this pull request May 27, 2026
Follow-up to PR #3219 cloud review. Internal cleanup, no behavior change.

- Rename `rsc_plugin_without_client_references?` to
  `any_rsc_plugin_missing_client_references?` and add a docstring spelling
  out the any-section existential semantics — the prior name read as
  universal but the implementation is existential.
- Consolidate the lightweight JS scanner's supported-surface notes onto
  `advance_js_scan_state` (the central dispatcher): which lexical
  constructs are tracked, which fall outside (regex literals, nested
  template literals), how downstream `rsc_plugin_option_sections_partition`
  detects the unsafe cases and warns, and what real-world expansion would
  look like. Replace the scattered shorter notes at
  `rsc_plugin_options_without_comments` and `matching_js_closing_brace`
  with pointers to the consolidated doc.
- Document the defensive guards in `add_rsc_client_references_setup`:
  what bad outcome they prevent (a duplicate `const rscClientReferences`
  declaration → `Identifier already declared` SyntaxError at config load)
  and why two boolean checks are worth keeping in case a future caller
  bypasses `ensure_rsc_client_references_setup`.

Fixes #3258

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
justin808 added a commit that referenced this pull request May 27, 2026
Follow-up to PR #3219 cloud review. Internal cleanup, no behavior change.

- Rename `rsc_plugin_without_client_references?` to
  `any_rsc_plugin_missing_client_references?` and add a docstring spelling
  out the any-section existential semantics — the prior name read as
  universal but the implementation is existential.
- Consolidate the lightweight JS scanner's supported-surface notes onto
  `advance_js_scan_state` (the central dispatcher): which lexical
  constructs are tracked, which fall outside (regex literals, nested
  template literals), how downstream `rsc_plugin_option_sections_partition`
  detects the unsafe cases and warns, and what real-world expansion would
  look like. Replace the scattered shorter notes at
  `rsc_plugin_options_without_comments` and `matching_js_closing_brace`
  with pointers to the consolidated doc.
- Document the defensive guards in `add_rsc_client_references_setup`:
  what bad outcome they prevent (a duplicate `const rscClientReferences`
  declaration → `Identifier already declared` SyntaxError at config load)
  and why two boolean checks are worth keeping in case a future caller
  bypasses `ensure_rsc_client_references_setup`.

Fixes #3258

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
justin808 added a commit that referenced this pull request May 27, 2026
Follow-up to PR #3219 cloud review. Internal cleanup, no behavior change.

- Rename `rsc_plugin_without_client_references?` to
  `any_rsc_plugin_missing_client_references?` and add a docstring spelling
  out the any-section existential semantics — the prior name read as
  universal but the implementation is existential.
- Consolidate the lightweight JS scanner's supported-surface notes onto
  `advance_js_scan_state` (the central dispatcher): which lexical
  constructs are tracked, which fall outside (regex literals, nested
  template literals), how downstream `rsc_plugin_option_sections_partition`
  detects the unsafe cases and warns, and what real-world expansion would
  look like. Replace the scattered shorter notes at
  `rsc_plugin_options_without_comments` and `matching_js_closing_brace`
  with pointers to the consolidated doc.
- Document the defensive guards in `add_rsc_client_references_setup`:
  what bad outcome they prevent (a duplicate `const rscClientReferences`
  declaration → `Identifier already declared` SyntaxError at config load)
  and why two boolean checks are worth keeping in case a future caller
  bypasses `ensure_rsc_client_references_setup`.

Fixes #3258

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
justin808 added a commit that referenced this pull request May 27, 2026
Design spec for the three follow-up clarity items from PR #3219's cloud
review: rename the per-section predicate, document the scanner's
supported surface around regex-literal braces, and drop dead defensive
guards in the helper-injection method.

No behavior change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
justin808 added a commit that referenced this pull request May 27, 2026
Design spec for the three follow-up clarity items from PR #3219's cloud
review: rename the per-section predicate, document the scanner's
supported surface around regex-literal braces, and drop dead defensive
guards in the helper-injection method.

No behavior change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
justin808 added a commit that referenced this pull request May 28, 2026
Follow-up to PR #3219 cloud review. Internal cleanup, no behavior change.

- Rename `rsc_plugin_without_client_references?` to
  `any_rsc_plugin_missing_client_references?` and add a docstring spelling
  out the any-section existential semantics — the prior name read as
  universal but the implementation is existential.
- Consolidate the lightweight JS scanner's supported-surface notes onto
  `advance_js_scan_state` (the central dispatcher): which lexical
  constructs are tracked, which fall outside (regex literals, nested
  template literals), how downstream `rsc_plugin_option_sections_partition`
  detects the unsafe cases and warns, and what real-world expansion would
  look like. Replace the scattered shorter notes at
  `rsc_plugin_options_without_comments` and `matching_js_closing_brace`
  with pointers to the consolidated doc.
- Document the defensive guards in `add_rsc_client_references_setup`:
  what bad outcome they prevent (a duplicate `const rscClientReferences`
  declaration → `Identifier already declared` SyntaxError at config load)
  and why two boolean checks are worth keeping in case a future caller
  bypasses `ensure_rsc_client_references_setup`.

Fixes #3258

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
justin808 added a commit that referenced this pull request May 28, 2026
Follow-up to PR #3219 cloud review. Internal cleanup, no behavior change.

- Rename `rsc_plugin_without_client_references?` to
  `any_rsc_plugin_missing_client_references?` and add a docstring spelling
  out the any-section existential semantics — the prior name read as
  universal but the implementation is existential.
- Consolidate the lightweight JS scanner's supported-surface notes onto
  `advance_js_scan_state` (the central dispatcher): which lexical
  constructs are tracked, which fall outside (regex literals, nested
  template literals), how downstream `rsc_plugin_option_sections_partition`
  detects the unsafe cases and warns, and what real-world expansion would
  look like. Replace the scattered shorter notes at
  `rsc_plugin_options_without_comments` and `matching_js_closing_brace`
  with pointers to the consolidated doc.
- Document the defensive guards in `add_rsc_client_references_setup`:
  what bad outcome they prevent (a duplicate `const rscClientReferences`
  declaration → `Identifier already declared` SyntaxError at config load)
  and why two boolean checks are worth keeping in case a future caller
  bypasses `ensure_rsc_client_references_setup`.

Fixes #3258

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
justin808 added a commit that referenced this pull request May 28, 2026
Design spec for the three follow-up clarity items from PR #3219's cloud
review: rename the per-section predicate, document the scanner's
supported surface around regex-literal braces, and drop dead defensive
guards in the helper-injection method.

No behavior change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
justin808 added a commit that referenced this pull request May 28, 2026
## Summary

Follow-up to #3219 / fixes #3258. This PR is now rebased onto current
`origin/main`.

### RSC migration parser cleanup

- Rename `rsc_plugin_without_client_references?` to
`any_rsc_plugin_missing_client_references?` and document the existential
semantics.
- Consolidate lightweight JS scanner support/limitation notes onto
`advance_js_scan_state`, including caller coverage and
regex/template-literal caveats.
- Clarify why `add_rsc_client_references_setup` keeps defensive
duplicate-helper guards.

### Adjacent hardening

- Strengthen Node renderer `SECURITY:` comments for CommonJS wrapping
via both `additionalContext` and `supportModules`.
- Make the package-json publish rewrite use a same-directory temp file
plus atomic rename before marking the file as changed.
- Add direct coverage for the package-json pin fallback warning
formatter.
- Keep the retry-sleep guard in release metadata lookup explicit and
make the webpack helper test description path-agnostic.

## Review Follow-Ups

- **Must-fix:** none. Thread-aware review scan currently shows 0
unresolved review threads.
- **Optional fixed:** added the direct `supportModules` security anchor
and the `package_json_pin_fallback_warning` formatter spec.
- **Discuss / advice:** the prior `Generator tests / examples (3.2,
minimum)` failure was caused by `npm install shakapacker@10.1.0`
returning `ETARGET` before that npm version was available. The registry
now resolves `shakapacker@10.1.0`, and the React 16 pinned example task
passed locally.
- **Discuss / non-code CI:** the current `claude-review` failure is the
Claude Action hitting its weekly limit (`resets May 29, 3am UTC`), not a
repository test failure.

## CI / Test Plan

- [x] Rebased onto `origin/main`
- [x] `npm view shakapacker@10.1.0 version`
- [x] `(cd react_on_rails && bundle exec rake
run_rspec:shakapacker_examples_react16)`
- [x] `(cd react_on_rails && BUNDLE_FORCE_RUBY_PLATFORM=true bundle exec
rspec spec/react_on_rails/shakapacker_examples_rake_spec.rb
spec/react_on_rails/release_rake_helpers_spec.rb
spec/react_on_rails/generators/js_dependency_manager_spec.rb)`
- [x] Pre-push hook: branch Ruby RuboCop on changed Ruby files

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Changes RSC config auto-migration control flow and npm publish
package.json handling; mistakes could corrupt webpack configs or leave
bad publish manifests, though new specs cover the release path.
> 
> **Overview**
> This PR tightens **RSC webpack config migration** logic and documents
**Node renderer sandboxing**, plus small **release** and **generator**
hardening.
> 
> **RSC clientReferences migration** renames
`rsc_plugin_without_client_references?` to
**`any_rsc_plugin_missing_client_references?`** and documents that it is
an existential check (not the complement of “all plugins define
clientReferences”). **`prepare_rsc_client_references_rewrite!`**
replaces the old prepare helper so helper injection and rewrite
decisions stay correct when some plugins already use scoped
`clientReferences`. **`rsc_plugin_sections_safe_to_rewrite?`** now takes
**`is_server:`** when counting unparseable sections. Lightweight JS
scanner limits and caller contracts are **centralized on
`advance_js_scan_state`**, with extra comments on defensive
duplicate-helper guards.
> 
> **Node renderer config** comments add explicit **SECURITY** notes that
**`supportModules: true`** and a plain-object **`additionalContext`**
both enable CommonJS wrapping and host **`require`**.
> 
> **npm publish** rewrites `package.json` via a **same-directory temp
file and atomic rename**, sets the restore **`changed`** flag only after
rename succeeds, and adds a spec for failed rename cleanup.
> 
> **JS dependency fallback** emits a clearer warning listing
**`name@version`** pins written to `package.json` when install fails.
The webpack helper test description is made path-agnostic.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
92f41ed. 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**
  * More robust npm publishing via atomic package.json replacement.
* Improved React Server Components setup handling for multi-plugin
configurations.

* **Documentation**
  * Expanded security notes for supportModules and sandboxing behavior.

* **Improvements**
  * Clearer, better-formatted package manager warning messages.

* **Tests**
* Updated/added tests and path-agnostic test descriptions for related
warnings.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/shakacode/react_on_rails/pull/3343?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
justin808 added a commit that referenced this pull request May 28, 2026
Design spec for the three follow-up clarity items from PR #3219's cloud
review: rename the per-section predicate, document the scanner's
supported surface around regex-literal braces, and drop dead defensive
guards in the helper-injection method.

No behavior change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

benchmark codex-wip Codex is actively working this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RSCWebpackPlugin defaults to scanning project root and can bundle vendor gem templates

1 participant