Skip to content

Conversation

@ihabadham
Copy link
Collaborator

@ihabadham ihabadham commented Jan 2, 2026

Summary

Fixes two bugs in the Rspack support feature (PR #1852):

  1. bin/switch-bundler crashes with Psych::AliasesNotEnabled on YAML files containing anchors/aliases
  2. --rspack flag and bin/switch-bundler only update default section - environment sections retain webpack

Root Cause

Both the generator and bin/switch-bundler used YAML.load → modify default → YAML.dump which:

  • YAML.load expands <<: *default inheritance into explicit copies
  • Modifying config["default"] doesn't affect already-expanded environment sections
  • YAML.dump writes back with all sections having explicit values (only default was changed)

Solution

Replace YAML parse/dump with regex replacement (Thor's gsub_file pattern):

  • Preserves file structure (comments, anchors, aliases)
  • Updates assets_bundler in all sections, not just default
  • Standard Rails generator pattern for config file modifications

Testing

Before (reproduce bugs)

cd /tmp
rails new test-app --skip-javascript
cd test-app
bundle add react_on_rails --version 16.2.0.rc.0 --strict
git add . && git commit -m "Add gem"
bin/rails generate react_on_rails:install --rspack

# Bug: Only default has rspack, environments have webpack
grep "assets_bundler" config/shakapacker.yml

# Bug: switch-bundler crashes
bin/switch-bundler webpack

After (with this fix)

# All sections should show rspack after --rspack flag
grep "assets_bundler" config/shakapacker.yml
# Expected: All lines show "rspack"

# switch-bundler should work without crashing
bin/switch-bundler webpack
bin/switch-bundler rspack

Test plan

  • Fresh install with --rspack configures rspack in all environments
  • bin/switch-bundler rspack updates all environment sections
  • bin/switch-bundler webpack updates all environment sections
  • YAML comments and structure preserved after switch

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Refactor
    • Improved the bundler switching mechanism to better preserve configuration file structure and comments during updates.
    • Enhanced bundler and loader configuration file handling for more reliable updates while maintaining existing functionality.

✏️ Tip: You can customize this high-level summary in your review settings.

## Problem

When using `--rspack` flag with the generator or `bin/switch-bundler`,
only the `default` section of shakapacker.yml was updated. The environment
sections (development, test, production) retained `assets_bundler: webpack`
because:

1. `YAML.load` expands `<<: *default` inheritance into explicit copies
2. Modifying `config["default"]` doesn't affect the already-expanded copies
3. `YAML.dump` writes back with all sections having explicit values

Additionally, `bin/switch-bundler` crashed with `Psych::AliasesNotEnabled`
on YAML files containing anchors/aliases.

## Solution

Replace YAML parse/dump approach with regex replacement (Thor's `gsub_file`
pattern) which:

- Preserves file structure (comments, anchors, aliases)
- Updates `assets_bundler` in ALL sections, not just default
- Avoids YAML parsing issues entirely

This follows the standard Rails generator pattern for modifying config files.

## Files Changed

- `base_generator.rb`: Use `gsub_file` instead of YAML.load/dump
- `bin/switch-bundler` template: Use regex replacement, remove yaml require

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 2, 2026

Walkthrough

The changes replace YAML-based configuration parsing with string-based text replacements using regex and gsub_file methods. Both the generator and bundler-switching script now update bundler and loader settings through direct string substitution while preserving file structure and comments.

Changes

Cohort / File(s) Change Summary
Configuration Generator Updates
react_on_rails/lib/generators/react_on_rails/base_generator.rb, react_on_rails/lib/generators/react_on_rails/templates/base/base/bin/switch-bundler
Replaced YAML parsing/dumping with regex-based string substitution; updates assets_bundler (webpack → rspack) and webpack_loader (babel → swc) settings while preserving file structure and comments

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

bug, review-needed

Suggested reviewers

  • justin808
  • AbanoubGhadban

Poem

🐰 With regex and gsub we hop along,
No YAML round-trips make us strong,
From webpack to rspack we glide with glee,
Comments preserved in harmony!

Pre-merge checks and finishing touches

❌ 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%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix rspack configuration not applying to all environments' accurately captures the main issue being fixed—ensuring rspack configuration applies across all environment sections, not just the default section.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ihabadham/fix/rspack-config-all-environments

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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
react_on_rails/lib/generators/react_on_rails/base_generator.rb (1)

301-315: LGTM! Regex-based replacement correctly preserves file structure.

The regex patterns appropriately match configuration lines while preserving whitespace, comments, and file structure. The approach correctly updates all occurrences across all environment sections (not just default), which fixes the original issue.

Minor note: The patterns ["']?webpack["']? and ["']?babel["']? match quotes independently, which could theoretically match mismatched quotes like "webpack'. For more robustness, consider paired matching, though the current pattern is adequate for valid YAML.

Optional: More robust regex pattern
-        /^(\s*assets_bundler:\s*)["']?webpack["']?(\s*(?:#.*)?)$/,
+        /^(\s*assets_bundler:\s*)(?:"webpack"|'webpack'|webpack)(\s*(?:#.*)?)$/,
         '\1rspack\2'
       )
 
-        /^(\s*webpack_loader:\s*)["']?babel["']?(\s*(?:#.*)?)$/,
+        /^(\s*webpack_loader:\s*)(?:"babel"|'babel'|babel)(\s*(?:#.*)?)$/,
         '\1swc\2'

This explicitly matches paired quotes or no quotes, preventing edge cases with malformed YAML.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d864f66 and 6eb4ff3.

📒 Files selected for processing (2)
  • react_on_rails/lib/generators/react_on_rails/base_generator.rb
  • react_on_rails/lib/generators/react_on_rails/templates/base/base/bin/switch-bundler
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rb

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.rb: Run bundle exec rubocop and fix ALL violations before every commit/push
Run bundle exec rubocop (MANDATORY) before every commit to ensure zero offenses

Files:

  • react_on_rails/lib/generators/react_on_rails/base_generator.rb
🧠 Learnings (16)
📓 Common learnings
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to /{CHANGELOG.md,CHANGELOG_PRO.md} : Use format `[PR 1818](https://github.com/shakacode/react_on_rails/pull/1818) by [username](https://github.com/username)` in changelog entries (no hash in PR number)
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to {package.json,webpack.config.js,packages/*/package.json,react_on_rails_pro/package.json} : When resolving merge conflicts in build configuration files, verify file paths are correct by running `grep -r 'old/path' .` and test affected scripts like `pnpm run prepack` before continuing the merge
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to /CHANGELOG.md : Update `/CHANGELOG.md` for user-visible changes in the open-source react_on_rails gem and npm package (features, bug fixes, breaking changes, deprecations, performance improvements)
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to sig/react_on_rails/**/*.rbs : RBS signatures in `sig/react_on_rails/` should be added for new Ruby files in `lib/react_on_rails/`, included in Steepfile, validated with `bundle exec rake rbs:validate`, and type-checked with `bundle exec rake rbs:steep`
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Shakapacker configuration should be tested by creating debug scripts in dummy app root (debug-webpack-rules.js, debug-webpack-with-config.js) to inspect webpack rules before committing configuration changes
Learnt from: alexeyr-ci2
Repo: shakacode/react_on_rails PR: 1732
File: spec/dummy/client/app-react16/startup/ReduxSharedStoreApp.client.jsx:40-44
Timestamp: 2025-04-26T21:55:55.874Z
Learning: In the react_on_rails project, files under `app-react16` directories are copied/moved to corresponding `/app` directories during the conversion process (removing the `-react16` suffix), which affects their relative import paths at runtime.
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: For infrastructure/config changes, perform comprehensive local testing including: finding all affected files with grep, testing build pipeline, running relevant specs, and linting everything before pushing
📚 Learning: 2025-04-26T21:55:55.874Z
Learnt from: alexeyr-ci2
Repo: shakacode/react_on_rails PR: 1732
File: spec/dummy/client/app-react16/startup/ReduxSharedStoreApp.client.jsx:40-44
Timestamp: 2025-04-26T21:55:55.874Z
Learning: In the react_on_rails project, files under `app-react16` directories are copied/moved to corresponding `/app` directories during the conversion process (removing the `-react16` suffix), which affects their relative import paths at runtime.

Applied to files:

  • react_on_rails/lib/generators/react_on_rails/base_generator.rb
  • react_on_rails/lib/generators/react_on_rails/templates/base/base/bin/switch-bundler
📚 Learning: 2025-09-16T08:01:11.146Z
Learnt from: justin808
Repo: shakacode/react_on_rails PR: 1770
File: lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx:2-2
Timestamp: 2025-09-16T08:01:11.146Z
Learning: React on Rails uses webpack CSS Modules configuration with namedExports: true, which requires the import syntax `import * as style from './file.module.css'` rather than the default export pattern. This configuration enables better tree shaking and bundle size optimization for CSS modules.

Applied to files:

  • react_on_rails/lib/generators/react_on_rails/base_generator.rb
  • react_on_rails/lib/generators/react_on_rails/templates/base/base/bin/switch-bundler
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to /CHANGELOG_PRO.md : Update `/CHANGELOG_PRO.md` for user-visible changes in the Pro-only react_on_rails_pro gem and npm packages

Applied to files:

  • react_on_rails/lib/generators/react_on_rails/base_generator.rb
  • react_on_rails/lib/generators/react_on_rails/templates/base/base/bin/switch-bundler
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to /CHANGELOG.md : Update `/CHANGELOG.md` for user-visible changes in the open-source react_on_rails gem and npm package (features, bug fixes, breaking changes, deprecations, performance improvements)

Applied to files:

  • react_on_rails/lib/generators/react_on_rails/base_generator.rb
  • react_on_rails/lib/generators/react_on_rails/templates/base/base/bin/switch-bundler
📚 Learning: 2025-02-12T16:38:06.537Z
Learnt from: Romex91
Repo: shakacode/react_on_rails PR: 1697
File: package-scripts.yml:28-28
Timestamp: 2025-02-12T16:38:06.537Z
Learning: The file `node_package/lib/ReactOnRails.full.js` is autogenerated during the build process and should not be present in the repository.

Applied to files:

  • react_on_rails/lib/generators/react_on_rails/base_generator.rb
  • react_on_rails/lib/generators/react_on_rails/templates/base/base/bin/switch-bundler
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to sig/react_on_rails/**/*.rbs : RBS signatures in `sig/react_on_rails/` should be added for new Ruby files in `lib/react_on_rails/`, included in Steepfile, validated with `bundle exec rake rbs:validate`, and type-checked with `bundle exec rake rbs:steep`

Applied to files:

  • react_on_rails/lib/generators/react_on_rails/base_generator.rb
  • react_on_rails/lib/generators/react_on_rails/templates/base/base/bin/switch-bundler
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: In IDE configuration, exclude these directories to prevent slowdowns: /coverage, /tmp, /gen-examples, /packages/react-on-rails/lib, /node_modules, /react_on_rails/spec/dummy/node_modules, /react_on_rails/spec/dummy/tmp, /react_on_rails/spec/dummy/app/assets/webpack, /react_on_rails/spec/dummy/log, /react_on_rails/spec/dummy/e2e/playwright-report, /react_on_rails/spec/dummy/test-results

Applied to files:

  • react_on_rails/lib/generators/react_on_rails/base_generator.rb
  • react_on_rails/lib/generators/react_on_rails/templates/base/base/bin/switch-bundler
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to {package.json,webpack.config.js,packages/*/package.json,react_on_rails_pro/package.json} : When resolving merge conflicts in build configuration files, verify file paths are correct by running `grep -r 'old/path' .` and test affected scripts like `pnpm run prepack` before continuing the merge

Applied to files:

  • react_on_rails/lib/generators/react_on_rails/base_generator.rb
  • react_on_rails/lib/generators/react_on_rails/templates/base/base/bin/switch-bundler
📚 Learning: 2025-07-08T05:57:29.630Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1745
File: node_package/src/RSCRequestTracker.ts:8-14
Timestamp: 2025-07-08T05:57:29.630Z
Learning: The global `generateRSCPayload` function in React on Rails Pro (RORP) is provided by the framework during rendering requests, not implemented in application code. The `declare global` statements are used to document the expected interface that RORP will inject at runtime.

Applied to files:

  • react_on_rails/lib/generators/react_on_rails/base_generator.rb
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to **/config/webpack/**/*.js : Use `namedExport: false` and `exportLocalsConvention: 'camelCase'` for CSS Modules loader options in Shakapacker 9.0+ if existing code uses `import styles from './file.module.css'` pattern

Applied to files:

  • react_on_rails/lib/generators/react_on_rails/base_generator.rb
  • react_on_rails/lib/generators/react_on_rails/templates/base/base/bin/switch-bundler
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Shakapacker configuration should be tested by creating debug scripts in dummy app root (debug-webpack-rules.js, debug-webpack-with-config.js) to inspect webpack rules before committing configuration changes

Applied to files:

  • react_on_rails/lib/generators/react_on_rails/base_generator.rb
  • react_on_rails/lib/generators/react_on_rails/templates/base/base/bin/switch-bundler
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to **/*.rb : Run `bundle exec rubocop` (MANDATORY) before every commit to ensure zero offenses

Applied to files:

  • react_on_rails/lib/generators/react_on_rails/templates/base/base/bin/switch-bundler
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to **/*.rb : Run `bundle exec rubocop` and fix ALL violations before every commit/push

Applied to files:

  • react_on_rails/lib/generators/react_on_rails/templates/base/base/bin/switch-bundler
📚 Learning: 2025-09-29T14:45:42.687Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1833
File: lib/react_on_rails/dev/process_manager.rb:72-83
Timestamp: 2025-09-29T14:45:42.687Z
Learning: In Ruby bundler contexts, when bundler intercepts system commands for executables not in the Gemfile, both version checks (like `system("foreman", "--version")`) and execution commands (like `system("foreman", "start", ...)`) fail equally, both returning false. This means availability checks using version flags accurately reflect whether execution commands will work in the current bundler context.

Applied to files:

  • react_on_rails/lib/generators/react_on_rails/templates/base/base/bin/switch-bundler
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to react_on_rails/spec/dummy/e2e/playwright/app_commands/**/*.rb : Create custom Rails app commands in `e2e/playwright/app_commands/` directory with .rb files for reusable helpers in Playwright tests

Applied to files:

  • react_on_rails/lib/generators/react_on_rails/templates/base/base/bin/switch-bundler
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: rspec-package-tests (3.4, latest)
  • GitHub Check: examples (3.4, latest)
  • GitHub Check: claude-review
🔇 Additional comments (3)
react_on_rails/lib/generators/react_on_rails/templates/base/base/bin/switch-bundler (1)

50-68: LGTM! Implementation correctly mirrors base_generator.rb approach.

The regex-based replacement logic is implemented correctly:

  • Dynamic pattern building with controlled string interpolation is safe
  • Replacement string escaping ("\\1#{var}\\2") is correct
  • File I/O operations are appropriate for a standalone script
  • Logic for determining old vs. new bundler/loader values is sound

The implementation maintains consistency with the generator approach while adapting it for a script context.

react_on_rails/lib/generators/react_on_rails/base_generator.rb (2)

295-318: The regex patterns handle various YAML formats correctly.

The implementation successfully replaces all occurrences of webpack with rspack and babel with swc while preserving:

  • Inline comments
  • YAML anchors and aliases
  • Quote styles ("webpack", 'webpack', webpack)
  • Indentation and whitespace variations

295-318: Verify locally by running bundle exec rubocop on this file before committing.

The Ruby file must pass all rubocop checks as per the project's mandatory pre-commit requirements. Note that git pre-commit hooks automatically run RuboCop with auto-fix enabled, but ensure local verification passes first.

@claude
Copy link

claude bot commented Jan 3, 2026

PR Review: Fix rspack configuration not applying to all environments

✅ Strengths

1. Correct Problem Diagnosis

The root cause analysis is excellent. YAML.load expands anchor aliases (<<: *default) into explicit copies, so modifying config["default"] only affects that section, not the inherited environment sections.

2. Appropriate Solution

Using regex replacement (Thor's gsub_file) is the right approach:

  • Preserves YAML structure, comments, and anchors
  • Updates all occurrences across all sections
  • Follows Rails generator conventions
  • Eliminates the Psych::AliasesNotEnabled error

3. Well-Crafted Regex

The pattern is robust with proper anchoring, indentation handling, quote flexibility, and comment preservation.

⚠️ Critical Issues

1. MISSING TEST COVERAGE

The most critical issue: there are no automated tests verifying the fix works.

The existing spec/react_on_rails/generators/install_generator_spec.rb has a context "with --rspack" block at line 162-212, but it does NOT verify shakapacker.yml configuration. It only tests package.json dependencies and webpack config files.

Required addition:

context "with --rspack" do
  # ... existing tests ...
  
  it "configures rspack in shakapacker.yml" do
    assert_file "config/shakapacker.yml" do |content|
      # Verify assets_bundler is set to rspack (multiple occurrences expected)
      expect(content.scan(/assets_bundler:\s*rspack/).count).to be >= 1
      
      # Verify webpack_loader is set to swc
      expect(content).to match(/webpack_loader:\s*swc/)
      
      # Verify YAML structure preserved (anchors still present)
      expect(content).to include("default: &default")
      expect(content).to include("<<: *default")
    end
  end
end

2. Silent Failure Risk

If the regex doesn't match (e.g., shakapacker.yml already has assets_bundler: esbuild), gsub_file succeeds without changes but still shows "✅ Updated shakapacker.yml".

Recommendation: Add validation in base_generator.rb:configure_rspack_in_shakapacker:

# Before gsub_file calls, add:
content = File.read(shakapacker_config_path)

# Check if already configured
if content.match?(/^\s*assets_bundler:\s*["']?rspack["']?/)
  puts Rainbow("ℹ️  Shakapacker already configured for Rspack").blue
  return
end

# Check if webpack is present to replace  
unless content.match?(/^\s*assets_bundler:\s*["']?webpack["']?/)
  puts Rainbow("⚠️  Warning: No 'assets_bundler: webpack' found. Manual configuration may be needed.").yellow
  return
end

3. Regex Edge Cases

The pattern assumes assets_bundler has value webpack or rspack. If someone has a different value or typo, the switch-bundler script will fail silently.

bin/switch-bundler line 54-58: Consider adding a check:

matched = new_content.scan(/^\s*assets_bundler:/).size
replaced = content.scan(/^\s*assets_bundler:\s*["']?#{old_bundler}/).size

if matched > 0 && replaced == 0
  puts "⚠️  Warning: Found assets_bundler but none matched '#{old_bundler}'"
end

4. Documentation of Testing

Per CLAUDE.md "LOCAL TESTING REQUIREMENTS":

NEVER claim a test is "fixed" without running it locally first

The PR test plan shows manual test commands but doesn't indicate whether they were actually executed. Please add to the PR description:

## Testing Performed
✅ Tested locally in fresh Rails app:
- Ran generator with --rspack flag
- Verified shakapacker.yml updated in all sections
- Executed bin/switch-bundler webpack && bin/switch-bundler rspack
- Confirmed YAML structure preserved

[OR if you couldn't test]
⚠️ Cannot test locally: [reason - e.g., Conductor workspace, etc.]

🔍 Minor Issues

5. Inconsistent String Escaping

  • base_generator.rb:307: '\1rspack\2' (single quotes)
  • bin/switch-bundler:56: "\\1#{@target_bundler}\\2" (double backslash for interpolation)

Both work, but consider documenting why they differ (single quote doesn't need escaping).

📋 Recommendations

Must Fix (Before Merge):

  1. Add automated tests - Verify shakapacker.yml updated correctly
  2. Add test for bin/switch-bundler - Ensure YAML structure preserved
  3. Add validation - Detect and warn on silent failures

Should Fix:

  1. Document actual testing performed (per CLAUDE.md requirements)
  2. Consider adding example specs for edge cases

Nice to Have:

  1. Add defensiveness for unexpected shakapacker.yml content
  2. Log what actually changed (e.g., "Updated 3 occurrences of assets_bundler")

🎯 Verdict

Status: 🟡 Requires Changes

The solution is architecturally correct and fixes real bugs, but:

  • Lacks test coverage (critical gap)
  • No validation of success/failure
  • Testing not documented per project guidelines

Estimated effort: 2-3 hours to add tests + validation + update docs


Files to modify:

  • spec/react_on_rails/generators/install_generator_spec.rb - Add shakapacker.yml verification
  • lib/generators/react_on_rails/base_generator.rb - Add validation logic
  • PR description - Document testing performed

@claude
Copy link

claude bot commented Jan 3, 2026

Code Review for PR #2275: Fix rspack configuration not applying to all environments

Summary

This PR fixes two critical bugs in the Rspack support feature by replacing YAML parse/dump operations with regex replacements. The approach is sound and follows Rails generator patterns. Overall, this is a well-executed fix that addresses real issues, but there are some considerations around testing and edge cases.


Strengths

  1. Root cause properly identified: The PR correctly identifies that YAML.load + YAML.dump expands YAML anchors (<<: *default) into explicit copies, making it impossible to update all environments by only modifying the default section.

  2. Appropriate solution: Using regex replacement with gsub_file (Thor method) is the right Rails generator pattern and preserves file structure, comments, and YAML anchors.

  3. Fixes both locations: Both base_generator.rb and bin/switch-bundler are updated consistently.

  4. Removes unnecessary dependency: The bin/switch-bundler no longer needs require "yaml", which is correctly removed.

  5. Good regex patterns: The regex patterns capture whitespace, optional quotes, and inline comments properly:

    /^(\s*assets_bundler:\s*)["']?webpack["']?(\s*(?:#.*)?)$/

⚠️ Issues & Recommendations

1. CRITICAL: Missing CHANGELOG Entry

Per CLAUDE.md guidelines, bug fixes are user-visible changes that MUST be documented in CHANGELOG.md. This PR fixes bugs that would affect users installing with --rspack or using bin/switch-bundler.

Required action:

#### Bug Fixes
- [PR 2275](https://github.com/shakacode/react_on_rails/pull/2275) by [ihabadham](https://github.com/ihabadham): Fixed `--rspack` flag and `bin/switch-bundler` only updating `default` section instead of all environments (development/test/production). Also fixed `bin/switch-bundler` crashing with `Psych::AliasesNotEnabled` error on YAML files with anchors/aliases.

2. Missing Test Coverage

The existing test suite doesn't verify that all environment sections get updated when using --rspack. Looking at install_generator_spec.rb:162-212, the current tests only verify:

  • bin/switch-bundler script exists
  • Dependencies are installed correctly
  • Webpack configs use bundler detection

Missing test: Verify that shakapacker.yml has assets_bundler: rspack in development, test, AND production sections after running the generator with --rspack.

Recommended addition to install_generator_spec.rb (around line 212):

it "configures rspack in all environment sections of shakapacker.yml" do
  assert_file "config/shakapacker.yml" do |content|
    # Verify default section has rspack
    expect(content).to match(/^default:.*?assets_bundler:\s*["']?rspack["']?/m)

    # Count how many times assets_bundler appears and verify all are rspack
    assets_bundler_lines = content.lines.select { |line| line.match?(/^\s*assets_bundler:/) }

    # Should have at least one (in default section)
    expect(assets_bundler_lines).not_to be_empty

    # All assets_bundler lines should be rspack (not webpack)
    assets_bundler_lines.each do |line|
      expect(line).to match(/rspack/), "Expected all assets_bundler lines to be rspack, got: #{line}"
      expect(line).not_to match(/webpack/), "Found webpack instead of rspack in: #{line}"
    end
  end
end

3. Edge Case: Idempotency in bin/switch-bundler

The bin/switch-bundler script doesn't check if it's already using the target bundler. Running bin/switch-bundler rspack twice when already on rspack will attempt unnecessary operations.

Recommended improvement:

def update_shakapacker_config
  abort "❌ #{@shakapacker_config} not found" unless File.exist?(@shakapacker_config)

  puts "📝 Updating #{@shakapacker_config}..."
  content = File.read(@shakapacker_config)

  # Check if already using target bundler
  if content.match?(/^\s*assets_bundler:\s*["']?#{@target_bundler}["']?/m)
    puts "⚠️  Already using #{@target_bundler}, no changes needed"
    return
  end

  # ... rest of replacement logic
end

4. Future-Proofing: Hardcoded Bundler Assumption

The code assumes only two bundlers exist (webpack and rspack):

old_bundler = @target_bundler == "rspack" ? "webpack" : "rspack"

If Shakapacker adds support for other bundlers (e.g., vite, esbuild), this breaks.

Better approach (replaces any bundler value):

new_content = content.gsub(
  /^(\s*assets_bundler:\s*)["']?\w+["']?(\s*(?:#.*)?)$/,
  "\\1#{@target_bundler}\\2"
)

🎯 Performance & Security

Performance: ✅ Regex replacement is faster than YAML parse/dump

Security: ✅ No concerns

  • File path validation present
  • No command injection
  • Regex patterns are safe (anchored, no dangerous backtracking)

📊 Overall Assessment

Category Rating Notes
Code Quality ⭐⭐⭐⭐ (4/5) Clean, follows Rails patterns
Correctness ⭐⭐⭐⭐⭐ (5/5) Fixes both reported bugs correctly
Test Coverage ⭐⭐⭐ (3/5) Missing automated tests for environments
Documentation ⭐⭐⭐⭐ (4/5) Good PR description, missing CHANGELOG

Overall: 4.3/5 — Solid fix with minor improvements needed


Action Items Before Merge

Must fix:

  1. Add CHANGELOG.md entry (user-visible bug fix per CLAUDE.md)

Should add:
2. ⚠️ Add automated test verifying all environment sections are updated
3. ⚠️ Handle idempotency: early return if already on target bundler

Nice to have:
4. 💡 Make bin/switch-bundler more robust by matching any bundler value
5. 💡 Add example comment showing what the regex matches


📋 Recommendation

Approve with requested changes

The core fix is correct and well-implemented. The bugs are real, the solution is appropriate, and the code follows Rails patterns.

Great work identifying and fixing these bugs! The regex approach is the right solution for preserving YAML structure.

@claude
Copy link

claude bot commented Jan 3, 2026

Code Review - PR #2275

Thank you for this fix! I've reviewed the changes and they effectively address the reported bugs. Here's my detailed feedback:


Strengths

1. Root Cause Analysis

  • Excellent identification of the YAML parse/dump issue that caused anchors/aliases to be expanded
  • Clear documentation of why the previous approach only updated the default section

2. Solution Design

  • Regex replacement approach is the right choice - preserves file structure and comments
  • Aligns with standard Rails generator patterns (using Thor's gsub_file)
  • Handles all environment sections (development, test, production) automatically

3. Code Quality

  • Clean, readable regex patterns with proper capturing groups
  • Good comments explaining the approach
  • Consistent implementation across both base_generator.rb and bin/switch-bundler

4. Testing Documentation

  • Comprehensive manual testing instructions in PR description
  • Clear before/after reproduction steps

🔍 Observations & Suggestions

1. Regex Pattern Edge Cases

The current regex patterns work well for standard cases:

/^(\s*assets_bundler:\s*)["']?webpack["']?(\s*(?:#.*)?)$/

Potential edge cases to consider:

  • Flow-style YAML: {assets_bundler: webpack} (inline format) - won't match
  • Quoted keys: "assets_bundler": webpack - won't match
  • Multiline values (unlikely but possible in YAML)

Recommendation: These edge cases are unlikely in real-world Shakapacker configs, but consider adding a comment noting the assumption of block-style YAML formatting.

2. Error Handling in bin/switch-bundler

The switch-bundler script reads the entire file into memory:

content = File.read(@shakapacker_config)
new_content = content.gsub(...)

Suggestion: Add validation to ensure the replacement actually occurred:

if new_content == content
  puts "⚠️  Warning: No changes made to assets_bundler (already set to #{@target_bundler}?)"
end

This would help users understand if they run the script when already on the target bundler.

3. Test Coverage Gap ⚠️

I noticed the existing test suite has comprehensive coverage for the --rspack flag generator behavior (lines 162-212 in install_generator_spec.rb), but there's no test verifying that assets_bundler is set in ALL environments.

Critical test missing:

it "configures rspack in all environment sections" do
  assert_file "config/shakapacker.yml" do |content|
    # Verify default section has rspack
    expect(content).to match(/^\s*default:\s*&default/m)
    expect(content).to match(/^\s*assets_bundler:\s*rspack/m)
    
    # Verify environments inherit properly OR have explicit values
    %w[development test production].each do |env|
      # After generation, running: grep -A 20 "^#{env}:" should show rspack
      env_section = content.match(/^#{env}:.*?(?=^\w+:|\z)/m).to_s
      expect(env_section).to match(/assets_bundler:\s*rspack/)
    end
  end
end

However, I see the challenge: If the shakapacker.yml uses YAML anchors (<<: *default), the environments won't have explicit assets_bundler lines - they inherit from default. Your regex approach works because it replaces the value in default, and YAML inheritance handles the rest.

Revised recommendation: Add a test that verifies the runtime behavior by checking what Shakapacker would actually load:

it "configures rspack that applies to all environments" do
  # After generation, simulate loading the config
  config = YAML.load_file("config/shakapacker.yml")
  %w[default development test production].each do |env|
    expect(config[env]["assets_bundler"]).to eq("rspack")
  end
end

This test would fail with the old implementation (where only default was updated before YAML.dump) but pass with your fix.

4. Documentation Update Needed

The fix changes behavior from the original implementation. Consider updating:

  • Generator documentation mentioning the YAML structure preservation
  • bin/switch-bundler help text could note it preserves comments/formatting

🛡️ Security Review

No security concerns identified

  • Regex patterns are safe (no user input in patterns)
  • File operations are properly scoped to config files
  • No command injection risks (switch-bundler uses proper array form for system calls)

🎯 Performance Considerations

Performance impact: Negligible

  • Regex replacement on small config files is faster than YAML parse/dump
  • No N+1 issues or inefficient loops
  • Generator runs once during setup, so performance is not critical

📋 Recommendations Summary

Must Have (before merge):

  1. Add automated test verifying rspack config applies to all environments (see suggestion Add linting and CI scripts #3)

Nice to Have (can be follow-up):

  1. Add edge case handling comment for regex patterns
  2. Add validation in bin/switch-bundler to detect if no changes occurred
  3. Update generator documentation about YAML structure preservation

🎉 Conclusion

Overall Assessment: ✅ APPROVE with minor suggestions

This is a solid fix that correctly addresses both reported bugs:

  • ✅ Fixes Psych::AliasesNotEnabled crash
  • ✅ Updates rspack config in all environments, not just default
  • ✅ Preserves YAML comments and structure
  • ✅ Aligns with Rails generator best practices

The regex approach is superior to the YAML parse/dump approach for config file modifications when structure preservation matters.

Main action item: Add the automated test to prevent regression. Once that's added, this is ready to merge!

Great work investigating the root cause and choosing the right solution pattern! 🚀

@claude
Copy link

claude bot commented Jan 4, 2026

Code Review: Fix rspack configuration not applying to all environments

Great work identifying and fixing these bugs! The approach is solid, but I have some important feedback on the implementation.


🟢 Strengths

  1. Correct root cause analysis: You've correctly identified that YAML.load → YAML.dump expands anchor inheritance, making modifications only apply to the default section.

  2. Good solution choice: Using regex replacement instead of YAML parsing preserves file structure and comments, which is important for config files.

  3. Comprehensive fix: Both the generator and bin/switch-bundler are updated consistently.

  4. Clear documentation: The PR description explains the problem, solution, and provides reproduction steps.


🔴 Critical Issues

1. Regex patterns may not match all valid YAML formats

The regex patterns assume specific formatting:

/^(\s*assets_bundler:\s*)["\']?webpack["\']?(\s*(?:#.*)?)$/

Potential issues:

  • What if the value has single quotes? assets_bundler: 'webpack'
  • What if there's no space after the colon? assets_bundler:webpack
  • What if there are trailing spaces before comments?
  • What if the file uses tabs instead of spaces?

Recommendation: Test with different YAML formatting variations:

# Test cases:
assets_bundler: webpack
assets_bundler:webpack  
assets_bundler: "webpack"
assets_bundler: 'webpack'
assets_bundler: webpack # comment
assets_bundler: webpack#comment (no space)

2. Missing test coverage for the actual bug

Looking at install_generator_spec.rb, I don't see tests that verify:

  • ✅ All environment sections (development, test, production) have rspack configured after --rspack flag
  • ✅ YAML anchors/aliases are preserved
  • ✅ Comments are preserved
  • ✅ File structure is maintained

Current test (line 162-212) only checks:

  • bin/switch-bundler script exists
  • package.json has correct dependencies
  • webpack config files are generated

Recommendation: Add a test like:

it "configures rspack in all environment sections" do
  assert_file "config/shakapacker.yml" do |content|
    # Verify default section
    expect(content).to match(/default:.*assets_bundler:\s*rspack/m)
    # Verify development section  
    expect(content).to match(/development:.*assets_bundler:\s*rspack/m)
    # Verify test section
    expect(content).to match(/test:.*assets_bundler:\s*rspack/m)
    # Verify production section
    expect(content).to match(/production:.*assets_bundler:\s*rspack/m)
    # Verify YAML anchors are preserved
    expect(content).to include("<<: *default")
  end
end

3. bin/switch-bundler: Hardcoded old/new bundler values

In bin/switch-bundler (lines 47-61 of the diff):

old_bundler = @target_bundler == "rspack" ? "webpack" : "rspack"
new_content = content.gsub(
  /^(\s*assets_bundler:\s*)["\']?#{old_bundler}["\']?(\s*(?:#.*)?)$/,
  "\\1#{@target_bundler}\\2"
)

Problem: This assumes the current bundler is the opposite of the target. What if:

  • The file already has assets_bundler: rspack and user runs bin/switch-bundler rspack again?
  • The file has a typo or different value like assets_bundler: vite?

Recommendation: Make the replacement more flexible:

# Replace any bundler value, not just the opposite one
new_content = content.gsub(
  /^(\s*assets_bundler:\s*)["\']?\w+["\']?(\s*(?:#.*)?)$/,
  "\\1#{@target_bundler}\\2"
)

🟡 Minor Issues

4. Removed YAML require but not referenced in comments

In bin/switch-bundler, line 5 removes require "yaml" but the comment on line 8 still says "Script to switch between webpack and rspack bundlers" - might want to add a comment explaining why YAML is no longer needed.

5. Generator uses Thor's gsub_file, script uses manual File.read/write

For consistency, both should use the same approach. Since bin/switch-bundler is a standalone script (not a generator), it can't use Thor's gsub_file. This is fine, but worth noting for maintainability.

6. No validation that the replacement succeeded

After the regex replacement, there's no check to ensure it actually matched and replaced something. If the regex doesn't match (due to unexpected formatting), the operation silently does nothing.

Recommendation: Add validation:

unless new_content.include?("assets_bundler: #{@target_bundler}")
  warn "⚠️  Warning: Could not find assets_bundler setting to update"
end

📋 Testing Recommendations

Before merging, manually test these scenarios:

  1. Fresh install with --rspack:

    rails new test-app --skip-javascript
    cd test-app
    bundle add react_on_rails --path ../path/to/local/gem
    bin/rails generate react_on_rails:install --rspack
    
    # Verify ALL sections have rspack:
    grep -A1 "^development:\|^test:\|^production:\|^default:" config/shakapacker.yml | grep assets_bundler
  2. Switch back and forth:

    bin/switch-bundler webpack
    grep assets_bundler config/shakapacker.yml  # Should show webpack in all sections
    
    bin/switch-bundler rspack  
    grep assets_bundler config/shakapacker.yml  # Should show rspack in all sections
  3. Verify YAML structure preserved:

    # Add some comments and custom formatting to shakapacker.yml
    bin/switch-bundler webpack
    # Check that comments and formatting are intact
  4. Test with unusual YAML formatting:

    • Single quotes around values
    • No spaces after colons
    • Trailing spaces
    • Different indentation

🎯 Summary

Severity breakdown:

Recommendation: Add comprehensive tests before merging. The fix is heading in the right direction, but needs more robust regex patterns and test coverage to ensure it works reliably across different YAML formatting styles.


Great work on the investigation and fix! The approach is much better than YAML parsing. Just needs more comprehensive testing and slightly more flexible regex patterns. 🚀

@claude
Copy link

claude bot commented Jan 4, 2026

Code Review: Rspack Configuration Fix

This PR fixes two important bugs in the Rspack support feature. The solution replaces YAML parsing with regex-based replacement, which is the correct approach.

Strengths

  1. Root Cause Analysis: Excellent identification of the YAML inheritance issue where YAML.load expands anchors into explicit copies
  2. Appropriate Solution: Using regex replacement (gsub_file) preserves file structure and is standard Rails generator pattern
  3. Comprehensive Fix: Both base_generator.rb and bin/switch-bundler updated consistently

Issues & Recommendations

1. CRITICAL: Missing Test Coverage

Per CLAUDE.md requirements, these changes need automated tests for:

  • Generator with --rspack flag sets rspack in ALL environments
  • YAML structure/comments are preserved
  • bin/switch-bundler updates all sections

2. Regex Pattern Robustness

In bin/switch-bundler, the pattern only matches the old bundler value. If a user has mixed values, not all sections will update. Consider matching ANY bundler value (webpack|rspack) regardless of current state.

3. Testing Requirements

Per CLAUDE.md: Test fresh install, bin/switch-bundler in both directions, and verify YAML anchors/comments are preserved.

Checklist Before Merge

  • Add automated tests (RSpec) for generator behavior
  • Consider pattern that matches any bundler value
  • Run bundle exec rubocop and fix all violations
  • Test manually with fresh Rails app
  • Test bin/switch-bundler in both directions
  • Verify YAML anchors/comments preserved
  • Ensure all CI checks pass

Overall, this is a well-reasoned fix. Main gaps are test coverage and some regex edge cases. Great work on the analysis!

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.

2 participants