Skip to content

refactor: clarify RSC migration parser and release safeguards#3343

Merged
justin808 merged 7 commits into
mainfrom
jg/3258-rsc-migration-parser-clarity
May 28, 2026
Merged

refactor: clarify RSC migration parser and release safeguards#3343
justin808 merged 7 commits into
mainfrom
jg/3258-rsc-migration-parser-clarity

Conversation

@justin808

@justin808 justin808 commented May 21, 2026

Copy link
Copy Markdown
Member

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

  • Rebased onto origin/main
  • npm view shakapacker@10.1.0 version
  • (cd react_on_rails && bundle exec rake run_rspec:shakapacker_examples_react16)
  • (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)
  • Pre-push hook: branch Ruby RuboCop on changed Ruby files

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.

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

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 Change Stack

@coderabbitai

coderabbitai Bot commented May 21, 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

Targeted maintenance: clarify node-renderer sandboxing docs, make package.json publishing atomic, extract dependency-pin warning helper (with test), refine webpack warning test wording, and refine RSC clientReferences detection and scanner comments. No runtime behavior changes.

Changes

Maintenance and refinement improvements across Node-renderer, release infra, generators, tests, and RSC setup

Layer / File(s) Summary
Node-renderer VM sandboxing documentation
packages/react-on-rails-pro-node-renderer/src/shared/configBuilder.ts
Security documentation for supportModules and additionalContext now explains when bundles are wrapped, when host require is injected, and how plain additionalContext objects switch execution to CommonJS with VM sandboxing disabled.
Webpack warning assertion refinement
packages/react-on-rails/tests/webpackHelpers.test.ts
reactDomClientWarning test updated to assert the "Module not found… Can't resolve 'react-dom/client'" match independent of file path.
Atomic package.json publication for npm release
rakelib/release.rake, react_on_rails/spec/react_on_rails/release_rake_helpers_spec.rb
Add write_publishable_package_json to atomically write a prettified package.json (tempfile in same dir + rename), set changed = true only after atomic write succeeds, and avoid printing/sleeping on the final npm-metadata retry attempt. Test stub adjusted to reflect write timing.
Dependency manager pin fallback warning helper
react_on_rails/lib/generators/react_on_rails/js_dependency_manager.rb, react_on_rails/spec/react_on_rails/generators/js_dependency_manager_spec.rb
Extract package_json_pin_fallback_warning(versioned_packages) to format a comma-separated name@version pin list and the "Wrote the following version pins to package.json" warning; add spec exercising the helper.
RSC client references migration logic refinement
react_on_rails/lib/generators/react_on_rails/rsc_setup/client_references.rb
Introduce any_rsc_plugin_missing_client_references? to detect when at least one RSCWebpackPlugin options section lacks clientReferences: and use it for both "rewrite needed?" and "rewritable plugin?" gates; refresh scanner and defensive-guard comments describing supported constructs and edge cases.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related issues

  • #3258: Follow-up RSC migration parser helper clarity — changes add the existential predicate, clarify scanner surface, and preserve defensive-guard documentation.

Possibly related PRs

Suggested labels

documentation

Poem

🐰 I nibble docs and stitch a patch so spry,

Atomic swaps make package.json fly,
Warnings shaped and tests now sing in tune,
RSC scans clearer beneath the moon,
A tiny hop — maintenance makes things fine.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.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 PR title accurately describes the main theme covering both RSC parser clarity refactoring and release safety improvements, aligning with the diverse changeset.
Linked Issues check ✅ Passed All coding objectives from #3258 are met: predicate renamed with explicit semantics, scanner limitations documented, defensive guards preserved with documentation, and advance_js_scan_state docs updated.
Out of Scope Changes check ✅ Passed Adjacent changes to release safety (atomic file writes, retry logic), generator warnings, documentation updates, and test refactoring are all justified as supporting infrastructure improvements related to the RSC migration work.
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 jg/3258-rsc-migration-parser-clarity

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 May 21, 2026

Copy link
Copy Markdown

Greptile Summary

This is a documentation and naming clarification pass over rsc_setup.rb with no behaviour change. A grep across the full repo confirms the old method name rsc_plugin_without_client_references? has been fully replaced by any_rsc_plugin_missing_client_references? with no dangling references.

  • Method rename (rsc_plugin_without_client_references?any_rsc_plugin_missing_client_references?): the new name accurately reflects the existential (any?) semantics and pairs more intuitively with the sibling rsc_plugin_defines_client_references?. A doc-comment is added explaining the non-complement behaviour when multiple plugin sections are present.
  • Scanner documentation consolidated onto advance_js_scan_state: the supported lexical surface (comments, all three string types, ${expr} interpolations), the two unsupported cases (regex literals, nested template literals), the downstream catch via rsc_plugin_options_followed_by_close_paren?, and a concrete "future expansion" sketch are all now in one authoritative place.
  • Guard documentation in add_rsc_client_references_setup made explicit: the comment now names the concrete failure mode the defensive returns prevent (Identifier 'rscClientReferences' has already been declared SyntaxError) and articulates why keeping them is cheaper than re-deriving the precondition at every future call site.

Confidence Score: 5/5

Safe to merge — the only functional change is a method rename that is fully applied with no dangling references anywhere in the repo.

All changes are documentation and a method rename. No logic paths are altered, the renamed predicate's call site is updated, and a repo-wide grep confirms zero remaining uses of the old name. The expanded advance_js_scan_state comment and the clarified guard rationale in add_rsc_client_references_setup are accurate descriptions of the existing code behaviour.

No files require special attention.

Important Files Changed

Filename Overview
react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Documentation-only refactor: renames one predicate method to clarify existential semantics and consolidates JS-scanner surface documentation onto advance_js_scan_state. No logic changes; old name has zero remaining references.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[rewritable_rsc_plugin?] -->|calls| B["any_rsc_plugin_missing_client_references?\nrenamed from rsc_plugin_without_client_references?"]
    B --> C[rsc_plugin_option_sections]
    C --> D{any section missing clientReferences?}
    D -->|yes| E[return true - file is rewritable]
    D -->|no| F[rsc_plugin_defines_client_references?]
    F -->|all configured| G[warn + return false]
    F -->|none found| H[warn_missing_rsc_plugin_target + return false]

    C --> JSS
    subgraph JSS [advance_js_scan_state - central dispatcher]
        S1[line and block comments]
        S2[single / double / template-literal strings]
        S3[simple dollar-brace interpolations]
    end

    JSS -->|brace depth confused by regex or nested templates| I[rsc_plugin_option_sections_partition checks for trailing close-paren]
    I -->|missing| J[mark section unparseable - warn user]
    I -->|present| K[section is parseable]
Loading

Reviews (1): Last reviewed commit: "refactor: clarify RSC migration parser h..." | Re-trigger Greptile

@claude

claude Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

Code Review

Summary: Pure documentation and refactoring PR — one method rename and comment/doc improvements in rsc_setup.rb. No runtime behavior changes; all existing specs pass.

What's good

  • Rename is semantically correct. any_rsc_plugin_missing_client_references? accurately conveys existential semantics (at least one section is missing the key), where the old name rsc_plugin_without_client_references? implied a universal check. The pairing with rsc_plugin_defines_client_references? is now consistent.
  • Non-complement clarification is valuable. The new docblock explicitly notes that a file with one configured and one unconfigured plugin returns true from both predicates — this edge case was easy to miss before.
  • Consolidated advance_js_scan_state doc is thorough. Listing supported constructs, unsupported surface (regex literals, nested template literals), the downstream catch mechanism, and future-expansion sketches all in one place is significantly cleaner than scattered per-method notes.
  • Defensive guard explanation in add_rsc_client_references_setup now documents the concrete failure mode (duplicate const → SyntaxError at config load) rather than just labelling it 'belt-and-suspenders'. Good.

Issues

1. Missing caller in the advance_js_scan_state docblock (see inline comment)

The docblock enumerates 6 callers but last_js_code_char_index (line ~1120) is a 7th. The list reads as exhaustive, so the omission is misleading — a future developer extending the scanner won't know to re-validate that method.

2. Minor: 'short-circuit on false' is imprecise terminology

In add_rsc_client_references_setup:

so in normal flow both guards short-circuit on false

'Short-circuit' conventionally describes boolean operators (&&, ||). Here the guards are return if condition — if the condition is false, no early return fires; nothing is short-circuited. Suggested wording: 'both conditions evaluate to false and no early return is triggered'.

3. Future-expansion note: point 2 understates the change scope

Both require expanding advance_js_default_scan_state

Stack-based nested template literals (point 2) would also require changes to advance_js_string_state (currently a simple toggle), not just advance_js_default_scan_state. Worth updating so a future implementer isn't surprised.


Overall this is a clean, low-risk clarification PR. The only actionable issue is the incomplete caller list; the other two are wording nits.

Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Outdated
@justin808 justin808 force-pushed the jg/3258-rsc-migration-parser-clarity branch 2 times, most recently from f952cb4 to 09137c8 Compare May 24, 2026 04:08
Comment thread react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
Comment thread packages/react-on-rails-pro-node-renderer/src/shared/configBuilder.ts Outdated
@claude

claude Bot commented May 24, 2026

Copy link
Copy Markdown
Contributor

Code Review

Overview

The PR title describes an RSC migration parser refactor, but this branch carries 12 commits across 6 independent concerns, each at a different risk level:

Area Risk
RSC migration parser rename + doc consolidation Low (no behaviour change, all specs pass)
CI: remove setup-node-with-retry action Medium (see note below)
react-on-rails/webpackHelpers subpath export Low (additive new export with tests)
Pro: wrapServerComponentRenderer side-effect import Medium (runtime behaviour change, mitigated by webpack integration test)
Pro: algorithms: ["RS256"] + jwt constraint >= 2.7 High (security fix — see below)
Docs / planning / analysis cleanup Low

Security: algorithm:algorithms: in license_validator.rb

This is the most important change in the PR. jwt 3.0 removed the singular algorithm: option (deprecated in 2.x), so on 16.7.0.rc.0 with jwt 3.2.0 resolved the RS256 restriction was silently dropped — tokens signed with HS256 or bearing alg:none were not rejected by the algorithm check. The new plural algorithms: ["RS256"] is honored by both jwt 2.x (>= 2.7) and 3.x.

The two new specs (HS256 confusion attack, hand-crafted alg:none) directly cover the attack vectors the old code was vulnerable to. This fix is correct and the tests are excellent.


CI: Removing setup-node-with-retry

The composite action was created to work around intermittent V8 bytecode deserialization crashes on Node 22.21.0. The workflows now pin node-version: 22.11.0 (comment: "Pin to 22.11.0 (LTS) to avoid V8 bug in 22.21.0"), which is a valid mitigation. Two concerns:

  1. The Node pin is a comment-level constraint, not an enforced upper bound. If someone bumps the version past the buggy range later without reinstating the retry, the flakiness would silently return.
  2. Once the action is deleted, the original context for why it existed is gone from the repo.

Consider adding an inline note to the affected workflow files linking to the original issue/PR so the decision context survives the deletion.


wrapServerComponentRenderer/client side-effect import

The direct import 'react-on-rails-rsc/client.browser' is the right fix for the transitive-chain fragility, and the webpack integration test with memfs is the correct level of verification. One small risk: the import ordering may conflict with an import/order ESLint rule if side-effect imports are enforced in a specific group position — worth a quick lint run to confirm.


additionalContext documentation in configBuilder.ts

The new JSDoc correctly calls out that any non-null additionalContext (including {}) enables CommonJS mode with host require. The phrase "Use only with trusted bundle sources" is accurate but underplays the blast radius — the bundle gets unrestricted access to require('fs'), require('child_process'), etc. Consider stronger language in the warning. (Inline comment posted.)


Minor: CHANGELOG.md Fastify CVE entry placement

The Fastify 5.8.5 bump entry was added to the [16.7.0.rc.0] section (a tagged/released version). If this was retroactively added, confirm the described version matches what was actually shipped to avoid misleading the changelog.


Summary

The core RSC parser rename is clean and correct. The license_validator security fix is the most impactful change and is well-tested. The main review friction is that 12 distinct changes are bundled under a title that describes only one of them — no blocking issues found other than the documentation wording suggestion for additionalContext.

@github-actions

github-actions Bot commented May 24, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size
react-on-rails/client bundled (gzip) 62.83 KB (0%)
react-on-rails/client bundled (gzip) (time) 62.83 KB (0%)
react-on-rails/client bundled (brotli) 53.9 KB (0%)
react-on-rails/client bundled (brotli) (time) 53.9 KB (0%)
react-on-rails-pro/client bundled (gzip) 63.77 KB (0%)
react-on-rails-pro/client bundled (gzip) (time) 63.77 KB (0%)
react-on-rails-pro/client bundled (brotli) 54.81 KB (0%)
react-on-rails-pro/client bundled (brotli) (time) 54.81 KB (0%)
registerServerComponent/client bundled (gzip) 127.95 KB (0%)
registerServerComponent/client bundled (gzip) (time) 127.95 KB (0%)
registerServerComponent/client bundled (brotli) 62.06 KB (0%)
registerServerComponent/client bundled (brotli) (time) 62.06 KB (0%)
wrapServerComponentRenderer/client bundled (gzip) 122.37 KB (0%)
wrapServerComponentRenderer/client bundled (gzip) (time) 122.37 KB (0%)
wrapServerComponentRenderer/client bundled (brotli) 57.09 KB (0%)
wrapServerComponentRenderer/client bundled (brotli) (time) 57.09 KB (0%)

@claude

claude Bot commented May 24, 2026

Copy link
Copy Markdown
Contributor

Code Review

Overview

This PR is titled as a refactor of RSC migration parser helpers, but it is actually a mixed-scope PR containing at least five distinct categories of change:

  1. RSC parser refactor — rename + comment consolidation in rsc_setup.rb (the stated purpose)
  2. Security fixalgorithm:algorithms: in LicenseValidator#decode_license, with new attack-vector specs
  3. CI simplification — remove the custom setup-node-with-retry composite action
  4. New public APIreact-on-rails/webpackHelpers subpath export
  5. Bug fixes — RSC client manifest side-effect import, Fastify CVE bump, Ruby 3.4 base64 compatibility

The mixed scope makes individual rollback harder and obscures the security change in review. That said, the changes themselves are generally well-executed. Notes below by area.


RSC parser refactor (rsc_setup.rb) ✅

The rename from rsc_plugin_without_client_references? to any_rsc_plugin_missing_client_references? is a clear improvement — the original read as a universal check but the body was existential. The comment added to advance_js_scan_state is the right place to consolidate the scanner's supported-surface documentation. The guard explanation in add_rsc_client_references_setup is accurate and well-reasoned.

No issues here.


Security fix — JWT algorithm allowlist ⚠️ Important

The CHANGELOG correctly identifies this as a real vulnerability in 16.7.0.rc.0: algorithm: "RS256" (the jwt 3.x singular form) was silently ignored on jwt 3.x, meaning the algorithm restriction was not enforced and HS256/alg-none tokens could pass the JWT.decode step. Switching to algorithms: ["RS256"] (the jwt 2.x plural form, also accepted in 3.x) is the correct fix, and the new specs for the HS256-confusion and alg-none attacks are exactly the right regression tests.

One item deserves explicit acknowledgment: the previous release (16.7.0.rc.0, PR #3322) intentionally raised the floor from ~> 2.7 to >= 3.2.0 specifically to pick up the ruby-jwt empty-key HMAC vulnerability fix. This PR lowers it back to >= 2.7. The rationale (RS256 is asymmetric so the HMAC empty-key advisory doesn't apply to this validator) is sound, but it should be documented in the gemspec comment alongside the existing algorithm-restriction comment so a future maintainer understands why the floor was widened again. See inline comment on react_on_rails_pro.gemspec.


CI: removal of setup-node-with-retry ⚠️ Regression risk

The custom composite action was introduced specifically to handle intermittent V8 bytecode deserialization crashes on GitHub-hosted runners (nodejs/node#56010). The workflows still pin to node-version: '22.11.0' which was the known-stable version, so the pin is the actual mitigation — but the reason that mitigation is sufficient should be documented. If a runner image upgrade silently upgrades the cached Node binary or the pin is relaxed in a future PR, the V8 crashes will return and CI will be intermittently flaky with no obvious cause.

Suggestion: either keep a # Removed setup-node-with-retry: V8 crash issue resolved upstream in Node 22.x after 22.11.0. See nodejs/node#56010. comment in one of the workflow files, or open a tracking issue so the decision is findable.


New webpackHelpers export ✅

Clean addition. .cts module format is consistent with reactApis.cts. Package exports map and files array are correctly updated to include lib/**/*.d.cts. Tests cover the positive and negative cases adequately.

Minor: the test description for the second case says "matches the webpack 4 'Module not found' warning for react-dom/client" — the actual warning text format is identical to webpack 5; the only difference is the file path (src vs lib). The test still verifies the right thing (the regex matches both paths), the description is just slightly misleading. See inline comment.


RSC client manifest side-effect import ✅

The direct import 'react-on-rails-rsc/client.browser' in wrapServerComponentRenderer/client.tsx is the correct fix for the transitive-chain fragility. The two-level test strategy (source-file regex check + webpack integration test) is the right approach for a webpack-plugin-dependent invariant. The conditional it.skip when the lib output doesn't exist is appropriately commented to be visible in the test report rather than silently green.


configBuilder.ts security note ✅

The new SECURITY: comment on additionalContext is valuable — the footgun (any non-null object activates CommonJS mode with unrestricted host require) is non-obvious and could lead to sandbox escapes. Good addition.


Minor dependency items

  • fastify 5.8.3 → 5.8.5 for CVE-2026-33806: straightforward, no concerns.
  • base64 gem declared explicitly for Ruby 3.4+: correct fix, require: false is right.

Comment thread react_on_rails_pro/react_on_rails_pro.gemspec Outdated
Comment thread packages/react-on-rails/tests/webpackHelpers.test.ts Outdated
Comment thread react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
@justin808

Copy link
Copy Markdown
Member Author

Mattered

  • Addressed the Pro jwt dependency rationale in 738dd85f: react_on_rails_pro.gemspec now explains why jwt >= 2.7 is sufficient for this RS256-only validator and why the jwt 3.2.0 empty-key HMAC advisory does not apply to this code path.
  • Verified the RSC client side-effect import ordering: eslint.config.ts has import/order and import/first disabled, and pnpm run lint passed on HEAD.
  • Verified the Pro license validator regression coverage: bundle exec rspec spec/react_on_rails_pro/license_validator_spec.rb passed with 62 examples, 0 failures.

Optional

  • Updated the webpackHelpers test description in 738dd85f to avoid implying a webpack 4/5 warning-format difference.
  • Confirmed the current RSC scanner doc already includes last_js_code_char_index in the caller list and calls out stack-aware advance_js_string_state work for nested template literals.
  • Confirmed the current additionalContext security warning documents module.wrap(), unrestricted host require, Node built-ins, and disabled VM sandboxing.

Skipped

  • CodeRabbit's skipped-review/status comment and Greptile's summary were informational.

Verification

  • pnpm run lint
  • pnpm --filter react-on-rails run test -- webpackHelpers.test.ts (Jest ran the matching package suite: 17 suites, 152 tests)
  • pnpm start format.listDifferent
  • bundle exec rspec spec/react_on_rails_pro/license_validator_spec.rb
  • bundle exec rubocop --ignore-parent-exclusion from react_on_rails_pro

Scan window: full PR review history through 2026-05-24T08:48:00Z. Future full-PR scans should start after this comment unless check all reviews is requested.

@justin808 justin808 force-pushed the jg/3258-rsc-migration-parser-clarity branch from 738dd85 to 5c13228 Compare May 25, 2026 04:22
@claude

claude Bot commented May 25, 2026

Copy link
Copy Markdown
Contributor

Review: refactor: clarify RSC migration parser helpers (#3258)

Overall: Approved with one minor nit. This is a clean documentation + rename PR with no behavioral changes. All four files look correct.


Rename: rsc_plugin_without_client_references?any_rsc_plugin_missing_client_references?

The rename is an improvement. The old name implied a universal check ("the plugin is without client references") when the implementation is existential ("any plugin section is missing client references"). The new name makes the quantifier explicit, and the added docstring correctly documents the non-complement relationship when multiple plugin sections exist. Both call sites in client_references.rb are updated (lines 80 and 102). No stale references remain.


Consolidated JS scanner docs on advance_js_scan_state

The new docblock is thorough and well-structured: supported constructs, unsupported constructs with concrete examples, how downstream code catches failures, and a two-item future expansion sketch. The cross-references from rsc_plugin_options_without_comments and matching_js_closing_brace pointing to the central doc are the right call — duplicated scanner-limit prose was harder to keep in sync.


add_rsc_client_references_setup comment

The expanded comment clearly justifies keeping the defensive guards: the duplicate-declaration error is named (Identifier 'rscClientReferences' has already been declared), the cost of the check is quantified (two boolean ops on the already-loaded body), and the "future second caller" motivation is explained. This is the right level of detail for a method with non-obvious guard semantics.


configBuilder.ts security comments

Upgrading NOTE: to SECURITY: and explicitly listing fs, child_process, and os as accessible built-ins is a meaningful improvement for anyone skimming the interface. One minor formatting nit posted inline.


react_on_rails_pro.gemspec jwt comment

The comment correctly justifies >= 2.7 by noting that decode_license uses only RS256, so the HMAC empty-key advisory fixed in 3.2.0 is irrelevant to this gem. That's exactly the reasoning a security auditor would need. Looks good.


Webpack helper test rename

"matches the 'Module not found' warning regardless of file path" is more accurate — the test fixture has a path that was never webpack-4-specific. Minor cleanup, no issues.

Comment thread packages/react-on-rails-pro-node-renderer/src/shared/configBuilder.ts Outdated
@claude

claude Bot commented May 25, 2026

Copy link
Copy Markdown
Contributor

Code Review — PR #3343

Overview

This PR is broader than its title suggests — it combines several distinct changes:

  1. Version bump 16.7.0-rc.116.7.0-rc.2 across all gems and npm packages
  2. Release pipeline safety (rakelib/release.rake): workspace protocol rewriting before pnpm publish + post-publish npm registry verification with retry
  3. Install generator fallback (js_dependency_manager.rb): writes versioned specs to package.json as last resort when all installs fail
  4. Security policy (SECURITY.md): structured support window with CVSS thresholds and time-bound backport commitments
  5. RSC parser clarity (rsc_setup/client_references.rb): rename rsc_plugin_without_client_references?any_rsc_plugin_missing_client_references? + consolidated advance_js_scan_state docblock

Strengths

  • Release pipeline (rakelib/release.rake): The with_publishable_package_json ensure-based restore is solid — it preserves original content verbatim and correctly prioritizes the publish error over a restore error when both fail. Test coverage is thorough (workspace protocol replacement, retry behavior, error preservation).
  • npm verification (verify_npm_package_published!): The three-check sequence (existence → version match → no workspace protocol leaks) directly addresses the root cause of the broken rc.1 publish. The 6×5s retry window handles normal registry propagation delays well.
  • RSC parser rename: any_rsc_plugin_missing_client_references? is a clear improvement — the existential semantics are now unambiguous, and the docblock correctly notes the non-complement property when mixed plugin sections exist.
  • advance_js_scan_state docblock: Centralizing the scanner's limitations (regex literals, nested template literals) and future-expansion notes in one place is the right call. The "future expansion" sketch in particular is useful for the next maintainer who needs to extend the scanner.
  • SECURITY.md: The explicit CVSS thresholds (≥ 7.0 / ≥ 9.0) and time-bounded windows (6 months post-next-minor) are a substantial improvement over the previous vague "we triage" policy.

Issues

1. write_versioned_package_specs_to_package_json succeeds silently

When both primary and fallback installs fail, this method modifies package.json without producing any output. Users who run the generator will find their package.json updated with no explanation of why or what to do next (e.g., "Please run npm install to finish installing the following packages: ..."). The error path already uses GeneratorMessages.add_warning — the success path needs the equivalent so users understand what happened.

2. add_packages return-value semantics changed

Previously add_packages returned whatever install_packages_with_fallback returned (which could be true on success). Now it always returns false when the package-manager install fails, even if write_versioned_package_specs_to_package_json wrote the pins. This is the correct semantic (the install did not succeed), but callers that previously interpreted the return value as "at least something succeeded" will silently see changed behavior. Worth a mention in the commit message or a brief inline comment on the method.

3. Retry logic readability nit in fetch_npm_package_metadata_with_retries

next if attempt == attempts - 1 is correct (it skips the sleep on the last attempt, which has no next iteration), but it reads as "skip to the next iteration" rather than "skip the trailing sleep." See the inline comment for a suggested rephrasing.

4. PR scope — mixed concerns make bisecting harder

Combining a version bump, release pipeline infrastructure, install generator behavior change, security policy, and RSC refactoring in one PR makes it harder to bisect regressions and to review each change in isolation. Not blocking, but worth noting for future splits.


Verdict

The release pipeline additions are well-implemented and address a real production incident. The RSC parser changes are behavior-neutral and well-documented. The main actionable item is the missing user message in the install-fallback success path (write_versioned_package_specs_to_package_json). Everything else is polish.

Comment thread rakelib/release.rake Outdated
Comment thread rakelib/release.rake
@justin808 justin808 force-pushed the codex/fix-rsc-client-reference-scope branch from 4e2b5e1 to d74e827 Compare May 26, 2026 03:23
@justin808 justin808 force-pushed the jg/3258-rsc-migration-parser-clarity branch from 900c70a to 20886c9 Compare May 26, 2026 03:40
Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup/client_references.rb Outdated
Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup/client_references.rb Outdated
justin808 added a commit that referenced this pull request May 28, 2026
…elper

Expand the comment on `changed = true` to spell out the trade-off the
current ordering accepts: if `File.write` partially writes before
raising, the `ensure` block will not restore the original content. This
is rare on local filesystems for small JSON files (effectively atomic
single-buffer write) and acceptable for a release-time helper, but
documenting it prevents a future reader from flipping the ordering back
without understanding the implication.

Addresses review feedback on PR #3343.
@justin808 justin808 force-pushed the jg/3258-rsc-migration-parser-clarity branch from 9200fad to c7673a6 Compare May 28, 2026 01:03
@claude

claude Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

Code Review

Overview: This PR is a follow-up cleanup — mostly comment/documentation improvements, an atomic write safeguard in release tooling, a method rename for clarity, and a new formatter spec. No runtime behavior changes except the safer package.json write path.


rakelib/release.rake — atomic write

The write_publishable_package_json approach is correct and a genuine hardening improvement. A few observations:

Tempfile + rename interaction: When File.rename(tmp.path, package_json_path) succeeds, the Tempfile.create block's cleanup tries to File.unlink(tmp.path) afterward, but that path no longer exists. Ruby's Tempfile rescues Errno::ENOENT silently, so this is safe — but it's a non-obvious pattern. A one-line comment would prevent future confusion (e.g., # After rename, Tempfile cleanup silently no-ops on the missing tmp.path).

Spec simplification is correct. The old spec counted File.write calls to distinguish the initial write from the restore. The new implementation never calls File.write for the initial write (it uses tmp.write on the open Tempfile IO object + File.rename), so the first — and only — File.write to package_json_path is now the restore. Stubbing it to raise unconditionally is the right approach.


js_dependency_manager.rb — pin fallback warning

Clean extraction. The name@version format is more useful than the old flat message. One minor note: package_json_pin_fallback_warning is only reachable via send in tests because it's private — acceptable for a formatting helper, but worth keeping in mind if this ever needs to be testable from a higher level.


client_references.rb — method rename + scanner docs

Rename: rsc_plugin_without_client_references?any_rsc_plugin_missing_client_references? is a clear win. The new name makes the existential ("at least one") semantics unambiguous, and the comment explaining the non-complement behavior for mixed files is exactly the kind of non-obvious invariant that belongs in a docstring.

advance_js_scan_state comment block: The content is accurate and the "Future expansion" section contains genuinely useful guidance for whoever eventually extends the scanner. At ~54 lines, it's on the long side, but this is a shared state machine with non-obvious limitations, so the length is justified. The block_comment_exit_guard invariant name is especially helpful for searchability.


configBuilder.ts — security comments

The upgraded SECURITY: prefix and the detailed module.wrap() / require description are an improvement. The intentional duplication (anchor on supportModules, details on additionalContext) is a deliberate cross-reference and reads clearly.


webpackHelpers.test.ts

Test description is now path-agnostic — minor but correct.


Summary

  • No correctness bugs found.
  • The atomic rename is the most impactful functional change; it looks right.
  • One suggestion: add a short comment in write_publishable_package_json about the Tempfile-rename interaction.
  • Everything else is documentation/clarity — well done.

Comment thread rakelib/release.rake Outdated
justin808 and others added 6 commits May 27, 2026 16:01
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>
- client_references.rb: include `last_js_code_line_start` in the
  `advance_js_scan_state` caller list and scope the regex-literal /
  template-literal limitations to callers that do not run
  `js_regex_literal_start?` preprocessing, since `js_code_position?` and
  `js_top_level_position?` handle regex literals via their own pre-pass.
- client_references.rb: switch `add_rsc_client_references_setup` early
  returns to explicit `return false` to match the boolean-return
  convention used in `ensure_rsc_client_references_setup`.
- configBuilder.ts: start the second `SECURITY:` block on a new line so
  the tag is a clear visual anchor, matching the first block.
- js_dependency_manager.rb: emit a `GeneratorMessages.add_warning` from
  `write_versioned_package_specs_to_package_json` so users learn when
  the fallback wrote version pins to `package.json` and what they need
  to install manually.
- release.rake: in `with_publishable_package_json`, only flip `changed`
  to true after `File.write` succeeds so a failed write does not drive
  the ensure-block restore against an unchanged file.
- release.rake: replace `next if attempt == attempts - 1` with an
  `unless` guard around the retry puts+sleep block for readability.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…elper

Expand the comment on `changed = true` to spell out the trade-off the
current ordering accepts: if `File.write` partially writes before
raising, the `ensure` block will not restore the original content. This
is rare on local filesystems for small JSON files (effectively atomic
single-buffer write) and acceptable for a release-time helper, but
documenting it prevents a future reader from flipping the ordering back
without understanding the implication.

Addresses review feedback on PR #3343.
@justin808 justin808 force-pushed the jg/3258-rsc-migration-parser-clarity branch from c7673a6 to cc49b01 Compare May 28, 2026 02:04

@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.

Fix All in Cursor

Bugbot Autofix is ON. A cloud agent has been kicked off to fix the reported issue.

Reviewed by Cursor Bugbot for commit cc49b01. Configure here.

Comment thread rakelib/release.rake
@claude

claude Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

Code Review — PR #3343

Overview

Solid cleanup PR. The changes decompose cleanly into four concerns: documentation/rename, release atomicity, generator warning UX, and test hygiene. The atomic package.json publish is a genuine hardening win, and the security-label upgrade in configBuilder.ts is well worth the noise.


Strengths

Atomic package.json publish (rakelib/release.rake): Using a same-directory Tempfile + File.rename is the correct POSIX pattern. Tempfile#unlink (called in Tempfile.create's ensure) rescues Errno::ENOENT, so the post-rename cleanup is safe even though the temp path no longer exists. Flipping changed = true only after the rename also fixes the subtle edge case where a failed rename would have left the ensure restore running against an unmodified file.

Security comments in configBuilder.ts: Upgrading from "NOTE:" to "SECURITY:", explicitly naming fs, child_process, and os, and anchoring the supportModules note to its own property declaration make the danger more scannable for a quick audit.

any_rsc_plugin_missing_client_references? rename: The old name rsc_plugin_without_client_references? was genuinely ambiguous (all sections? any section?). The new name + existential-semantics doc comment remove the ambiguity cleanly.

advance_js_scan_state doc comment: Centralising the scanner surface and limitations here (instead of scattering fragments across matching_js_closing_brace, rsc_plugin_options_without_comments, etc.) is the right structural decision. The "Future expansion" block is a useful breadcrumb for whoever eventually needs to handle regex literals.

release_rake_helpers_spec.rb simplification: Dropping write_count is correct — the first mutation is now File.rename, not File.write, so the stub can unconditionally raise on any write to the path.


Concerns

1. rsc_client_references_rewrite_needed? violates Ruby's ? predicate convention (medium)

Ruby's established convention is that ? methods are pure predicates: they return a boolean and have no observable side effects. This method violates that contract — its own comment says "the scoped helper may already have been injected on disk" when it returns true. The next step, rewrite_rsc_plugin_client_references, depends on that disk state.

This is a real maintenance risk: any reader who sees return unless rsc_client_references_rewrite_needed?(...) will reasonably assume it is safe to reorder or short-circuit the call without consequences.

Suggestion: choose a name that signals the combined prepare/check role — e.g., prepare_rsc_client_references_rewrite! (bang for side effects) or setup_and_check_rsc_client_references_rewrite. The inline comment is good documentation, but the method name is what readers trust first.

2. rsc_plugin_sections_safe_to_rewrite? — passing is_server has no effect on unparseable (informational, no action required)

The change from a hardcoded is_server: true to the actual is_server parameter is functionally a no-op. In rsc_plugin_option_sections_partition, the unparseable counter is incremented at the two early-next branches — both before the rsc_plugin_is_server_match? filter. The is_server argument only controls which parseable sections enter safe; it never affects unparseable. So partition(content, is_server: true).fetch(:unparseable) and partition(content, is_server: false).fetch(:unparseable) always return the same value for a given file.

The old hardcoded is_server: true was oddly worded, but its removed comment ("The unparseable count is file-wide … regardless of the is_server argument") was actually correct. The new code reads more naturally, but the Cursor AI summary's claim that this "scopes unparseable plugin detection to the current is_server target instead of the whole file" is factually incorrect — the count remains file-wide. Flagging so a future git blame reader doesn't over-interpret this as a behavior-bearing change.


Minor

  • The package_json_pin_fallback_warning spec reaches the private method via instance.send(...). That's fine for a pure formatter; if the method ever gains logic worth testing at a unit level, consider protected or extraction to a helper to avoid send.

Test Coverage

  • New spec for package_json_pin_fallback_warning covers the formatter contract (name@version format, human-readable body). ✓
  • release_rake_helpers_spec.rb correctly validates the ensure-restore path after the atomic-write refactor. ✓
  • No new spec for rsc_client_references_rewrite_needed? as a combined predicate — but integration coverage through update_existing_rsc_webpack_config likely suffices.

Summary

Low-risk, net-positive PR. The one issue worth resolving before merge is the naming of rsc_client_references_rewrite_needed? — a ? method with disk-write side effects will trip up a future maintainer. Everything else is a clear win or a no-op refactor.

Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup/client_references.rb Outdated
@justin808

Copy link
Copy Markdown
Member Author

Mattered

  • Addressed the publishable package.json tempfile concern by switching to non-block Tempfile.create with explicit cleanup only when the atomic rename does not complete.
  • Added a regression spec for failed atomic rename cleanup so leftover package-json-* temp files are covered.
  • Addressed the side-effecting Ruby predicate concern by renaming the combined RSC rewrite step to prepare_rsc_client_references_rewrite! and restoring rsc_plugin_needs_client_references_rewrite? as the pure predicate helper.
  • Added a short :unparseable note clarifying that the count is file-wide and happens before is_server filtering.

Optional

  • Left the scanner "Future expansion" guidance inline with a rationale reply, because the later review noted the shared scanner is the appropriate place for that limitation and expansion context.

Skipped

  • No actionable skipped items remain after the code changes and rationale reply.

Verification

  • bundle exec rspec react_on_rails/spec/react_on_rails/release_rake_helpers_spec.rb react_on_rails/spec/react_on_rails/generators/js_dependency_manager_spec.rb (148 examples, 0 failures)
  • bundle exec rspec react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb:2051 react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb:2116 react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb:2148 (3 examples, 0 failures)
  • (cd react_on_rails && bundle exec rubocop) (204 files inspected, no offenses)
  • Pre-push hook branch-lint: 6 Ruby files inspected, no offenses

Scan window: default full-PR scan after the previous <!-- address-review-summary --> checkpoint at 2026-05-27T08:40:11Z, completed 2026-05-28T03:18:32Z. Future full-PR scans should start after this comment unless check all reviews is requested.

@claude

claude Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

Code Review

Overall: Clean refactoring and hardening PR. The changes are internally consistent, the test coverage is solid, and the documentation improvements are well-targeted. No must-fix findings.


Atomic package.json rewrite (rakelib/release.rake)

The new write_publishable_package_json is a genuine correctness improvement:

  • Tempfile.create (not Tempfile.new) avoids the finalizer that would delete the file early.
  • Passing File.dirname(package_json_path) as the tempdir guarantees a same-filesystem rename, so File.rename is atomic on POSIX.
  • chmod to match the original file mode is a good detail that preserves ACL-derived permissions.
  • The renamed flag and the ensure cleanup are correct: temp is only unlinked when the rename didn't succeed, and tmp.close unless tmp.closed? handles all early-exit paths.
  • The changed = true placement (after write_publishable_package_json returns, not after rename succeeds inside the helper) is right: if the helper raises, the exception propagates before changed flips, so the outer ensure correctly skips the restore.

One minor note: File.stat(package_json_path).mode & 0o777 inside write_publishable_package_json will raise if package_json_path was deleted between the earlier File.read and this call — but that's a pre-existing environmental assumption, not something this PR introduced.

The updated test for "removes the temporary package manifest when atomic rename fails" correctly verifies all three postconditions (error propagates, temp is gone, original is preserved). The simplified "restore failed" stub — no longer needing a write counter to distinguish publish vs restore writes — is a nice side-effect of the new approach.


RSC migration parser cleanup (client_references.rb)

any_rsc_plugin_missing_client_references? rename — the new name precisely communicates the existential semantics and the non-complement relationship with rsc_plugin_defines_client_references?. The updated docblock ("a file with one configured plugin and one unconfigured plugin returns true from both") is the key insight and it's now clearly documented.

rsc_plugin_sections_safe_to_rewrite? signature change — functionally equivalent, because rsc_plugin_option_sections_partition increments :unparseable before any is_server filtering (i.e., the unparseable count is the same regardless of which is_server value is passed). The new signature is still the right call: it makes the API consistent with all other methods in this module that take is_server:, and removes the implicit contract that callers had to remember when the parameter was hardcoded.

prepare_rsc_client_references_rewrite! — the control flow is cleaner and correct. In the non-scoped path (rsc_plugin_references_any_scoped_client_references? is false), the removed rsc_plugin_needs_client_references_rewrite? check was redundant: rewritable_rsc_plugin? already returns true only when any_rsc_plugin_missing_client_references? holds, which is exactly what rsc_plugin_needs_client_references_rewrite? tests. No behavior change.


advance_js_scan_state docstring

The consolidated docstring is a meaningful improvement — it's the single authoritative place to understand the scanner's supported surface and its known limits. The "Future expansion" section is long but appropriate here: these are non-trivial changes that require re-validating all callers, and the "only worth doing if a real-world case needs it" framing sets the right bar.


package_json_pin_fallback_warning extraction

Clean extraction. The warning fires unconditionally after File.write in write_versioned_package_specs_to_package_json, which is correct: we're already in the last-resort fallback path where the package manager install failed, so the warning is always appropriate. The spec using instance.send to test the private method is consistent with the rest of this spec file.


Security comments in configBuilder.ts

The repeated SECURITY: anchor on supportModules (with a forward reference to the detailed additionalContext note) makes it easy to grep for security-relevant properties without reading the entire comment block. The expanded additionalContext comment — calling out module.wrap() by name and listing specific built-ins (fs, child_process, os) — gives consumers the concrete information they need to make an informed risk decision.


fetch_npm_package_metadata_with_retries refactor

unless ... end with multi-statement body is clearer than the inverted next if guard when the happy path is the important logic. Functionally identical.


Summary: No blocking issues. All behavioral changes are correct, all new tests cover meaningful failure paths, and the documentation improvements add lasting value. LGTM.

Comment thread rakelib/release.rake
Comment on lines +994 to +996
ensure
tmp.close unless tmp.closed?
File.unlink(tmp_path) if !renamed && File.exist?(tmp_path)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If Tempfile.create on line 984 raises (e.g. the directory isn't writable), Ruby initialises tmp and tmp_path to nil before the assignment runs. The ensure then calls nil.closed? and File.exist?(nil), both of which raise NoMethodError / TypeError, masking the original error.

Safe-navigation guards fix it:

Suggested change
ensure
tmp.close unless tmp.closed?
File.unlink(tmp_path) if !renamed && File.exist?(tmp_path)
tmp&.close unless tmp&.closed?
File.unlink(tmp_path) if !renamed && tmp_path && File.exist?(tmp_path)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Follow up RSC migration parser helper clarity

1 participant