Skip to content

fix: replace marker file with Thor-idiomatic force option (#2289)#2411

Merged
ihabadham merged 15 commits intomasterfrom
ihabadham/fix/replace-marker-with-force-option
Mar 5, 2026
Merged

fix: replace marker file with Thor-idiomatic force option (#2289)#2411
ihabadham merged 15 commits intomasterfrom
ihabadham/fix/replace-marker-with-force-option

Conversation

@ihabadham
Copy link
Copy Markdown
Collaborator

@ihabadham ihabadham commented Feb 14, 2026

Summary

Fixes #2289 — when rails generate react_on_rails:install runs on a fresh Rails app (Shakapacker not pre-installed), private_output_path: ssr-generated stays commented out in config/shakapacker.yml.

Root cause: The .shakapacker_just_installed marker file approach caused copy_packer_config to skip RoR's shakapacker.yml.tt template entirely, so the <% if shakapacker_version_9_or_higher? %> conditional that uncomments private_output_path never executed.

Fix: Replace the marker file with Thor-idiomatic force: true option:

  • Record config_existed = File.exist?("config/shakapacker.yml") before running install_shakapacker
  • Pass shakapacker_just_installed: !config_existed to base_generator via invoke options
  • copy_packer_config uses template(..., force: true) for fresh installs (safe to overwrite Shakapacker defaults), and plain template(...) for pre-existing configs (prompts user)

This also fixes a secondary edge case: in partial-install scenarios (customized config exists but shakapacker_configured? returns false), the old approach would silently force-overwrite user customizations.

Changes

  • install_generator.rb
    • config_existed check before install, passes boolean via invoke options, removed marker file creation
    • Declared hidden class_option :shakapacker_just_installed so tests can pass the flag via CLI
    • Forward force/skip flags to invoked sub-generators (Thor's explicit options hash was dropping them)
  • base_generator.rb — Hidden class_option :shakapacker_just_installed, conditional force: true in copy_packer_config
  • generator_messages.rb — Removed dead marker file branch from build_shakapacker_status_section
  • install_generator_spec.rb — Regression test for Generator skips private_output_path configuration when Shakapacker is freshly installed #2289, unit test for force/non-force behavior, --skip flag for pre-existing config tests

Related

Test plan

  • Fresh install via test app: private_output_path: ssr-generated uncommented in generated config
  • CI passes (RSpec generator tests)
  • Pre-installed Shakapacker: user prompted before overwriting customized config

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Refactor

    • Improved Shakapacker configuration handling during installation to better detect and manage configuration state.
  • Tests

    • Enhanced test coverage for fresh Shakapacker installations and configuration force-overwriting scenarios.
    • Expanded testing for rspack integration with and without TypeScript.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 14, 2026

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

Replaces the on-disk marker file .shakapacker_just_installed with a hidden boolean option shakapacker_just_installed propagated from InstallGenerator to BaseGenerator. copy_packer_config now applies the shakapacker template with force: true for fresh installs and without force for pre-installed configurations; marker-file creation/removal was removed.

Changes

Cohort / File(s) Summary
BaseGenerator configuration
react_on_rails/lib/generators/react_on_rails/base_generator.rb
Added class_option :shakapacker_just_installed (hidden boolean). Reworked copy_packer_config to branch on the option instead of an on-disk marker; apply template with force: true for fresh installs, otherwise apply non-forced template and update log messages.
InstallGenerator logic
react_on_rails/lib/generators/react_on_rails/install_generator.rb
Added class_option :shakapacker_just_installed and compute config_existed = File.exist?("config/shakapacker.yml"); set @shakapacker_just_installed = !config_existed (in-memory) and pass that flag to downstream generators. Removed creation/removal of .shakapacker_just_installed; updated finalize_shakapacker_setup signature to accept config_existed.
Generator messages
react_on_rails/lib/generators/react_on_rails/generator_messages.rb
Removed verbose marker-file-specific messaging branch. Simplified Shakapacker status output to two-path logic based on presence of bin/shakapacker and bin/shakapacker-dev-server.
InstallGenerator tests
react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb
Reworked tests to simulate pre-installed vs freshly installed Shakapacker via filesystem setup rather than marker file; removed marker-file assertions; added regression tests (including #2289) and assertions verifying copy_packer_config uses force: true for fresh installs and non-forced otherwise; expanded rspack/TypeScript scenarios and helpers.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant InstallGen as InstallGenerator
    participant BaseGen as BaseGenerator
    participant Template as shakapacker.yml.tt
    participant Config as config/shakapacker.yml

    User->>InstallGen: run react_on_rails:install
    InstallGen->>InstallGen: config_existed = File.exist?("config/shakapacker.yml")
    alt fresh install
        InstallGen->>InstallGen: set `@shakapacker_just_installed` = true
    else pre-installed
        InstallGen->>InstallGen: set `@shakapacker_just_installed` = false
    end
    InstallGen->>BaseGen: invoke generators (pass shakapacker_just_installed, force, skip, typescript)
    BaseGen->>BaseGen: evaluate shakapacker_just_installed?
    alt true
        BaseGen->>Template: apply template (force: true)
        Template->>Config: overwrite/update (ensure version-aware settings)
    else false
        BaseGen->>Template: apply template (no force)
        Template->>Config: preserve existing file
    end
    BaseGen->>User: complete
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • #2289: Generator skips private_output_path configuration when Shakapacker is freshly installed — addressed by applying the RoR template with force: true on fresh installs (regression test added).

Poem

🐰 A hop, a flag no longer on disk,
Memory keeps whether Shaka was brisk,
Templates run where markers once stood,
private paths unhidden — configuration good,
Hooray for clean hops and fewer risks! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately describes the main fix: replacing the marker file approach with a Thor-idiomatic force option to resolve issue #2289.
Linked Issues check ✅ Passed The changes comprehensively address issue #2289 by implementing the suggested Thor-idiomatic force option approach with proper version-conditional template execution and interactive behavior preservation.
Out of Scope Changes check ✅ Passed All changes are scoped to fixing the marker file mechanism and ensuring proper Shakapacker config handling as specified in the linked issue.
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 unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ihabadham/fix/replace-marker-with-force-option

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
Copy link
Copy Markdown

greptile-apps Bot commented Feb 14, 2026

Greptile Summary

Replaces the .shakapacker_just_installed marker file with Thor-idiomatic force: true option to fix issue #2289 where private_output_path: ssr-generated stayed commented in fresh Shakapacker installs.

Key changes:

  • install_generator.rb now checks config_existed = File.exist?("config/shakapacker.yml") before running install_shakapacker, then sets @shakapacker_just_installed = !config_existed to pass to base generator
  • base_generator.rb uses template(..., force: true) when shakapacker_just_installed? is true (safe to overwrite fresh defaults), otherwise uses plain template(...) which respects --force/--skip flags or prompts user
  • Added hidden class_option :shakapacker_just_installed to both generators for test support
  • Forwarded force: and skip: flags to invoked sub-generators (Thor's explicit options hash was dropping them)
  • generator_messages.rb removed dead marker file detection branch
  • Tests updated to use --skip flag and added regression test for Generator skips private_output_path configuration when Shakapacker is freshly installed #2289

This ensures RoR's version-aware template (with private_output_path uncommented for Shakapacker 9+) is always applied to fresh installs, while preserving user customizations in pre-existing configs.

Confidence Score: 5/5

  • Safe to merge - well-tested fix with comprehensive test coverage and no breaking changes
  • The PR fixes a real bug with a clean, Thor-idiomatic solution. The logic is sound: checking file existence before install, passing the flag through options, and using force only when safe. Tests cover the regression case, force/non-force behavior, and existing workflows. No breaking changes or risky patterns.
  • No files require special attention

Important Files Changed

Filename Overview
react_on_rails/lib/generators/react_on_rails/install_generator.rb Replaced marker file with runtime detection of pre-existing config, added hidden class option for tests, forwarded force/skip flags to sub-generators
react_on_rails/lib/generators/react_on_rails/base_generator.rb Added hidden shakapacker_just_installed option, uses force: true when Shakapacker was freshly installed to ensure RoR template is applied
react_on_rails/lib/generators/react_on_rails/generator_messages.rb Removed dead code branch for marker file detection
react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb Updated tests to use --skip flag instead of marker file simulation, added regression test for #2289, added unit tests for force/non-force behavior

Flowchart

flowchart TD
    A[rails generate react_on_rails:install] --> B{config/shakapacker.yml exists?}
    B -->|Yes| C[config_existed = true]
    B -->|No| D[config_existed = false]
    C --> E[install_shakapacker runs]
    D --> E
    E --> F[Set @shakapacker_just_installed = !config_existed]
    F --> G[Pass shakapacker_just_installed flag to base_generator]
    G --> H{shakapacker_just_installed?}
    H -->|true| I[template with force: true]
    H -->|false| J[template without force - Thor prompts user]
    I --> K[RoR template applies: private_output_path uncommented, precompile_hook configured]
    J --> L[User decides: overwrite, skip, or diff]
    K --> M[gsub_file for additional config: rspack, precompile_hook]
    L --> M
    M --> N[Generation complete]
Loading

Last reviewed commit: f93a161

Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

4 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Feb 14, 2026

Additional Comments (1)

react_on_rails/lib/generators/react_on_rails/install_generator.rb
Edge case with --ignore-warnings and failed install

If install_shakapacker fails but --ignore-warnings is set, handle_shakapacker_install_error doesn't raise, install_shakapacker returns early, and then finalize_shakapacker_setup(config_existed) still runs — potentially setting @shakapacker_just_installed = true even though the install didn't succeed.

This is a pre-existing pattern (the old marker file would also have been created in this scenario), so it's not a regression. But you could guard against it:

      def ensure_shakapacker_installed
        return if shakapacker_configured?

        print_shakapacker_setup_banner
        ensure_shakapacker_in_gemfile

        config_existed = File.exist?("config/shakapacker.yml")

        return unless install_shakapacker

        finalize_shakapacker_setup(config_existed)

This would require install_shakapacker to return a truthy value on success (it currently returns nil on both success and failure paths).

Copy link
Copy Markdown
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: 1

🤖 Fix all issues with AI agents
In `@react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb`:
- Around line 233-263: The test never triggers
InstallGenerator.finalize_shakapacker_setup because it simulates existing
shakapacker files (so shakapacker_configured? returns true) and the CLI flag
--shakapacker-just-installed isn't effective; to fix, change the before(:all)
setup for this context so ensure_shakapacker_installed actually runs — either
remove the simulated files ("config/shakapacker.yml", "bin/shakapacker",
"config/webpack/webpack.config.js") so InstallGenerator will proceed to call
finalize_shakapacker_setup and set `@shakapacker_just_installed`, or explicitly
set `@shakapacker_just_installed` = true before invoke so BaseGenerator receives
shakapacker_just_installed: true and copy_packer_config will use force: true;
also confirm the test does not rely on run_generator_test_with_args always
adding --force which masks the bug.

Copy link
Copy Markdown
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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
react_on_rails/lib/generators/react_on_rails/install_generator.rb (1)

177-187: ⚠️ Potential issue | 🟡 Minor

finalize_shakapacker_setup runs even when install_shakapacker fails under --ignore-warnings.

If install_shakapacker fails and --ignore-warnings is set, the error handler returns without raising, and execution falls through to finalize_shakapacker_setup. This prints "✅ Shakapacker installed successfully!" and sets @shakapacker_just_installed, which could lead to a force: true overwrite of a config that wasn't actually freshly generated.

Consider guarding finalize on install success:

Proposed fix
-        install_shakapacker
-        finalize_shakapacker_setup(config_existed)
+        install_shakapacker
+        return unless shakapacker_configured?
+
+        finalize_shakapacker_setup(config_existed)

@ihabadham ihabadham added the bug label Feb 15, 2026
@ihabadham ihabadham requested a review from justin808 February 15, 2026 00:24
@justin808
Copy link
Copy Markdown
Member

@greptile, review this PR.
@claude, review this PR.

@justin808 justin808 added the P0 Merge this week label Feb 15, 2026
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Feb 15, 2026

Claude finished @justin808's task in 2m 24s —— View job


PR Review Complete ✅

Summary

This PR successfully fixes issue #2289 by replacing the fragile marker file approach with Thor-idiomatic option passing. The implementation is well-designed and the previous review concerns from CodeRabbit have been properly addressed in commit f93a161.

Verdict: Ready to merge ✅ All CI checks pass, good test coverage, clean implementation.


What This PR Does

Problem: When rails generate react_on_rails:install runs on a fresh Rails app (Shakapacker not pre-installed), the private_output_path: ssr-generated setting stayed commented out in config/shakapacker.yml.

Root Cause: The .shakapacker_just_installed marker file caused copy_packer_config to skip React on Rails' version-aware template entirely, so the ERB conditional <% if shakapacker_version_9_or_higher? %> at react_on_rails/lib/generators/react_on_rails/templates/base/base/config/shakapacker.yml.tt:35 never executed.

Fix: Replace marker file with in-memory boolean flag:

  1. install_generator.rb:192 records config_existed = File.exist?("config/shakapacker.yml") before running Shakapacker installer
  2. install_generator.rb:318 sets @shakapacker_just_installed = !config_existed after install completes
  3. install_generator.rb:116 passes shakapacker_just_installed: just_installed to base generator via invoke options
  4. base_generator.rb:104-112 uses template(..., force: true) for fresh installs vs. plain template(...) for pre-existing configs

Code Quality Assessment

✅ Strengths

  1. Previous Review Issue Fixed: Commit f93a161 properly addresses CodeRabbit's concern about the regression test not exercising the intended code path by declaring class_option :shakapacker_just_installed on InstallGenerator (lines 46-52).

  2. Clean Thor Integration: The implementation is Thor-idiomatic:

    • Uses hidden class_option instead of filesystem side effects
    • Properly forwards force/skip flags to invoked generators (install_generator.rb:119-120)
    • Respects Thor's conflict resolution when shakapacker_just_installed is false
  3. Good Test Coverage:

  4. Clear Comments: The code includes helpful inline documentation explaining the approach (base_generator.rb:30-36, install_generator.rb:46-49).

  5. No Breaking Changes: The fix is backward compatible and doesn't affect existing workflows.


Issues & Observations

⚠️ Minor: Edge Case with --ignore-warnings (Pre-existing, not a regression)

Location: install_generator.rb:194-195

If install_shakapacker fails but --ignore-warnings is set, execution continues to finalize_shakapacker_setup(config_existed), which:

  • Prints "✅ Shakapacker installed successfully!" (misleading)
  • Sets @shakapacker_just_installed = true even though install failed
  • Could lead to force-overwriting a config that wasn't actually freshly generated

This was also flagged by Greptile's review and is a pre-existing pattern, not introduced by this PR. The old marker file approach had the same issue. Consider guarding with:

def ensure_shakapacker_installed
  return if shakapacker_configured?

  print_shakapacker_setup_banner
  ensure_shakapacker_in_gemfile

  config_existed = File.exist?("config/shakapacker.yml")

  install_shakapacker
  return unless shakapacker_configured?  # Guard against failed install

  finalize_shakapacker_setup(config_existed)
end

This would require verifying that Shakapacker actually got configured before finalizing. However, since this is a pre-existing issue and not a regression, it can be addressed in a follow-up PR if desired.

ℹ️ Code Organization

The logic at install_generator.rb:116 has a bit of defensive redundancy:

just_installed = options.shakapacker_just_installed? || @shakapacker_just_installed || false

This handles three scenarios:

  1. CLI flag passed (tests)
  2. Runtime detection (normal flow)
  3. Fallback to false (when neither is set)

This is correct and defensive, but the || false is technically unnecessary since nil || nil would already be falsy. However, being explicit is fine for clarity.


Test Coverage Analysis

✅ Well-Tested Scenarios

  1. Fresh install with Shakapacker 9+ (lines 240-291):

    • Verifies private_output_path: ssr-generated is uncommented
    • Confirms full RoR template is applied (not Shakapacker's default)
  2. Pre-installed Shakapacker (lines 162-235):

    • Uses --skip to preserve existing config
    • Verifies configure_precompile_hook_in_shakapacker still runs via gsub_file
    • Confirms YAML structure, comments, and anchors are preserved
  3. Force behavior unit tests (lines 293-327):

    • Mocks verify force: true is passed when shakapacker_just_installed: true
    • Confirms no force when shakapacker_just_installed: false

📊 Coverage Improvements

The test suite properly simulates the scenario from issue #2289 by:

  • Creating a Shakapacker-default config with # private_output_path: ssr-generated commented out
  • Passing --shakapacker-just-installed to trigger force-overwrite
  • Asserting that the final config has the setting uncommented

Architecture & Design

✅ Solid Design Choices

  1. Separation of Concerns:

    • InstallGenerator detects whether config pre-existed
    • BaseGenerator decides whether to force-overwrite based on flag
    • Clean responsibility separation
  2. Follows Thor Conventions:

    • Hidden options for internal coordination between generators
    • Proper use of invoke with options hash
    • Respects user's --force and --skip flags
  3. Removes Filesystem Side Effects:

    • No temporary marker files to clean up
    • No race condition risks
    • Simpler control flow

CI Status

All checks passing:

  • Generator tests: ✅ Success
  • Rspec test for gem: ✅ Success
  • Integration Tests: ✅ Success
  • Lint JS and Ruby: ✅ Success
  • Assets Precompile Check: ✅ Success

Files Changed Review

react_on_rails/lib/generators/react_on_rails/base_generator.rb (+17/-9)

Rating: ⭐⭐⭐⭐⭐ Excellent

  • Clean replacement of marker file check with option check
  • Proper use of force: true vs. letting Thor handle conflicts
  • Clear comments explaining the approach

react_on_rails/lib/generators/react_on_rails/install_generator.rb (+24/-7)

Rating: ⭐⭐⭐⭐ Very Good

  • Declares hidden option (fixes CodeRabbit's concern)
  • Records config existence before install
  • Properly forwards options to invoked generators
  • One minor edge case with --ignore-warnings (pre-existing)

react_on_rails/lib/generators/react_on_rails/generator_messages.rb (+1/-11)

Rating: ⭐⭐⭐⭐⭐ Excellent

  • Clean removal of dead marker file code
  • Simplified logic maintains same functionality

react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb (+196/-89)

Rating: ⭐⭐⭐⭐⭐ Excellent

  • Comprehensive test coverage for regression
  • Unit tests for force/non-force behavior
  • Good use of test helpers and context setup
  • Clear test descriptions

Recommendations

For This PR (Optional)

  • The --ignore-warnings edge case mentioned above could be addressed, but it's not a blocker since it's pre-existing behavior

For Follow-up (Optional)

  • Consider adding integration test that actually runs rails shakapacker:install in a test Rails app to verify end-to-end behavior
  • Document the --shakapacker-just-installed flag in generator help text (currently hidden, which is fine for internal use)

Final Verdict

✅ Approved - Ready to Merge

This is a well-designed fix that:

The one minor edge case with --ignore-warnings is pre-existing and doesn't block this PR.

Great work on the fix and the follow-up commit addressing the test issue! 🎉


| Branch: ihabadham/fix/replace-marker-with-force-option

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Feb 24, 2026

Review: fix: replace marker file with Thor-idiomatic force option (#2289)

The approach here is well-reasoned — replacing a filesystem side-channel (marker file) with in-process state is a clear improvement, and forwarding force/skip flags to sub-generators fixes a real gap. A few issues worth addressing before merge:

Issues

1. finalize_shakapacker_setup is called unconditionally after a potentially-failed install

In ensure_shakapacker_installed, finalize_shakapacker_setup(config_existed) is called whether or not install_shakapacker succeeds. When --ignore-warnings is set, handle_shakapacker_install_error records the error and returns (rather than raising), so finalize_shakapacker_setup still runs and sets @shakapacker_just_installed = true. On the next step, copy_packer_config then silently force-overwrites whatever partial or missing config/shakapacker.yml exists. In a broken install this is probably harmless, but it masks the failure path — consider early-returning or checking a failure flag before calling finalize_shakapacker_setup.

2. Hidden --shakapacker-just-installed flag enables silent force-overwrite of user configs

While hide: true keeps it out of --help output, the flag is still accessible from the command line. If a user with a customized config/shakapacker.yml passes --shakapacker-just-installed (e.g. after reading docs or a StackOverflow answer), their customizations are silently overwritten without any Thor conflict prompt. For tests, this could alternatively be driven by stubbing shakapacker_configured? and install_shakapacker rather than exposing a destructive flag on the public interface.

3. Redundant || false in just_installed assignment

just_installed = options.shakapacker_just_installed? || @shakapacker_just_installed || false

options.shakapacker_just_installed? always returns a boolean (Thor type: :boolean), and @shakapacker_just_installed can only be nil or true. The trailing || false is unreachable.

4. Test isolation: describe "copy_packer_config force behavior" creates real files without cleanup

The before block writes config/shakapacker.yml into the shared dummy-for-generators destination, and since template is mocked, nothing cleans it up. There is no after block. This file persists and could affect subsequent test contexts that rely on the absence of config/shakapacker.yml. An after { FileUtils.rm_f(...) } would prevent leakage.

Minor

The regression test for #2289 exercises the options.shakapacker_just_installed? CLI flag path but does not exercise the @shakapacker_just_installed instance-variable path (the one that fires in real production use). Covering that path — e.g. by stubbing install_shakapacker and letting ensure_shakapacker_installed set the ivar — would give better confidence the fix works end-to-end.


Overall direction is correct; the marker-file approach was fragile. Items 1 and 4 are the most actionable before merge.

Comment thread react_on_rails/lib/generators/react_on_rails/install_generator.rb Outdated
Comment thread react_on_rails/lib/generators/react_on_rails/install_generator.rb Outdated
Comment thread react_on_rails/lib/generators/react_on_rails/install_generator.rb
Comment thread react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb Outdated
Copy link
Copy Markdown
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.

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

145-152: Consider also checking for config/shakapacker.yml before reporting “already configured.”

If the bin scripts exist but the config file was deleted or never created, the status message will be misleading. Including the config file in the check reduces false positives.

Suggested tweak
-      if File.exist?("bin/shakapacker") && File.exist?("bin/shakapacker-dev-server")
+      if File.exist?("bin/shakapacker") &&
+         File.exist?("bin/shakapacker-dev-server") &&
+         File.exist?("config/shakapacker.yml")
         "\n📦 #{Rainbow('Shakapacker already configured ✓').green}#{version_warning}"
       else
         "\n📦 #{Rainbow('Shakapacker setup may be incomplete').yellow}#{version_warning}"
       end
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@react_on_rails/lib/generators/react_on_rails/generator_messages.rb` around
lines 145 - 152, The status message in build_shakapacker_status_section can be
misleading because it only checks for bin/shakapacker and
bin/shakapacker-dev-server; update the conditional to also verify the presence
of the Shakapacker config (e.g. File.exist?("config/shakapacker.yml")) so the
"already configured" branch only runs when both bin scripts and the config file
exist; keep the appended version_warning (from
check_shakapacker_version_warning) unchanged and adjust the if condition in
build_shakapacker_status_section accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@react_on_rails/lib/generators/react_on_rails/generator_messages.rb`:
- Around line 145-152: The status message in build_shakapacker_status_section
can be misleading because it only checks for bin/shakapacker and
bin/shakapacker-dev-server; update the conditional to also verify the presence
of the Shakapacker config (e.g. File.exist?("config/shakapacker.yml")) so the
"already configured" branch only runs when both bin scripts and the config file
exist; keep the appended version_warning (from
check_shakapacker_version_warning) unchanged and adjust the if condition in
build_shakapacker_status_section accordingly.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f93a161 and 5a98064.

📒 Files selected for processing (4)
  • react_on_rails/lib/generators/react_on_rails/base_generator.rb
  • react_on_rails/lib/generators/react_on_rails/generator_messages.rb
  • react_on_rails/lib/generators/react_on_rails/install_generator.rb
  • react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb
🚧 Files skipped from review as they are similar to previous changes (2)
  • react_on_rails/lib/generators/react_on_rails/base_generator.rb
  • react_on_rails/lib/generators/react_on_rails/install_generator.rb

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Feb 24, 2026

Review: Replace marker file with Thor-idiomatic force option

Overall: The approach is sound - moving from a filesystem marker file to an in-memory boolean eliminates the surface-area risks (leftover files, concurrent runs, partial cleanup) and aligns with Thor idioms. The bug fix for #2289 is correct. A few issues worth addressing.

1. Runtime detection path is untested (main gap)

The regression test for #2289 uses the hidden CLI flag --shakapacker-just-installed to drive copy_packer_config with force: true. This correctly tests BaseGenerator's branch, but the actual end-to-end detection path in InstallGenerator - the config_existed = File.exist?(...) then @shakapacker_just_installed = \!config_existed chain - is never exercised by any test. In all new test contexts, shakapacker_configured? returns true (because bin stubs + yml + webpack config all pre-exist), so ensure_shakapacker_installed exits early and finalize_shakapacker_setup is never called.

A unit test for ensure_shakapacker_installed that stubs install_shakapacker returning true and asserts @shakapacker_just_installed is set correctly would close this gap.

2. Edge case: Shakapacker installer may re-create a pre-existing config

If config/shakapacker.yml exists but the binaries do not (partial install), shakapacker_configured? returns false, so we enter the setup path with config_existed = true. Then bundle exec rails shakapacker:install runs and may overwrite the yml with a fresh Shakapacker default. Because config_existed = true, we set @shakapacker_just_installed = false and skip the force-copy, leaving private_output_path commented out in the now-fresh config - reproducing the original bug.

This tradeoff is reasonable (protecting potential customizations), but it is worth documenting with a comment that this known edge case exists.

3. UX regression: fresh-install summary loses detail

The removed branch in generator_messages.rb used to show an informative just-installed summary (Added to Gemfile / Installer ran successfully / Webpack integration configured). After this change, a fresh install will show the generic 'Shakapacker already configured' message, since the binaries now exist post-install. Still accurate, just less informative. Low priority.

4. Minor: trailing || false is redundant

just_installed = options.shakapacker_just_installed? || @shakapacker_just_installed || false

@shakapacker_just_installed is true, false, or nil (unset). The trailing || false adds no value since all three cases are already handled by the || chain. Nit-level, no behavioral impact.

Comment thread react_on_rails/lib/generators/react_on_rails/install_generator.rb Outdated
Comment thread react_on_rails/lib/generators/react_on_rails/install_generator.rb Outdated
Comment thread react_on_rails/lib/generators/react_on_rails/generator_messages.rb Outdated
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Feb 24, 2026

PR Review: Replace Marker File with Thor-idiomatic Force Option

Overall assessment: The approach is sound and the root-cause fix is correct. Replacing the marker-file mechanism with content comparison in finalize_shakapacker_setup cleanly handles all three scenarios (fresh install, user accepted overwrite, user declined overwrite). The fix to forward force/skip flags through invoke is also a real correctness win. A few issues worth addressing before merge:


Issues

1. force/skip forwarding is incomplete

The PR correctly forwards force: options[:force], skip: options[:skip] to :base, :react_with_redux, and :react_no_redux, but NOT to :pro and :rsc (lines 156-159 of install_generator.rb). If a user runs rails g react_on_rails:install --rsc --force, the --force flag will apply to base-generator file operations but silently drop when :rsc is invoked. This should be consistent.

2. Duplicated just_installed resolution logic

options.shakapacker_just_installed? || @shakapacker_just_installed appears in two methods (invoke_generators and add_post_install_message). This should be extracted to a private helper method (e.g. shakapacker_just_installed?) to eliminate duplication and prevent the two sites from diverging.

3. yml_path string duplicated across two methods

"config/shakapacker.yml" is defined independently in ensure_shakapacker_installed and finalize_shakapacker_setup. A private constant or passing the path as a parameter would guard against future typos.

4. File.exist?/File.read use Dir.pwd rather than destination_root

The content-capture code in ensure_shakapacker_installed and finalize_shakapacker_setup reads relative to Dir.pwd, not destination_root. This works in practice (generators run from the Rails app root), but diverges from Thor's model and required the unit tests to use Dir.chdir(dir) rather than just stubbing destination_root. A comment noting this reliance would help future maintainers.

5. --shakapacker-just-installed hidden option on InstallGenerator is primarily a test seam

The comment acknowledges this ("In production, this is normally detected at runtime"), but exposing a hidden CLI option specifically to make integration tests easier is a design smell. The option is partly redundant: the unit tests for ensure_shakapacker_installed detection path already cover the runtime path without it. Consider whether the integration test for #2289 could instead stub @shakapacker_just_installed directly, or whether the option's value to advanced users justifies keeping it.


What's done well

  • Content comparison (yml_content_before \!= yml_content_after) correctly handles all three cases including the previously-missed "user accepted overwrite" scenario.
  • install_shakapacker now returns an explicit true/false, making the guard clause in ensure_shakapacker_installed clear.
  • Removing the marker-file side effect eliminates the risk of stale .shakapacker_just_installed files polluting the app working tree.
  • Unit tests for the detection path are well-structured and cover all cases including install failure.

Comment thread react_on_rails/lib/generators/react_on_rails/install_generator.rb Outdated
Comment thread react_on_rails/lib/generators/react_on_rails/install_generator.rb Outdated
Comment thread react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb Outdated
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Feb 24, 2026

Review

Previous rounds addressed most of the feedback well — the content-comparison approach in finalize_shakapacker_setup, the extracted shakapacker_just_installed? helper, the SHAKAPACKER_YML_PATH constant, the Dir.pwd comment, the test isolation fix, and guarding finalize_shakapacker_setup behind if install_shakapacker are all solid improvements.

Two remaining issues:

1. force/skip forwarding is incomplete

Commit 30be511 correctly forwarded force/skip to :base, :react_with_redux, and :react_no_redux, but the :pro and :rsc invocations were not updated:

# install_generator.rb:155-158
invoke "react_on_rails:pro", [], { invoked_by_install: true } if use_pro?
return unless use_rsc?
invoke "react_on_rails:rsc", [], { typescript: options.typescript?, invoked_by_install: true }

If a user runs rails g react_on_rails:install --rsc --force, the --force flag applies to the base generator's file operations but is silently dropped when :rsc is invoked, causing inconsistent conflict resolution behavior. Both should receive force: options[:force], skip: options[:skip] like the other invocations.

2. Dead destination_root stubs in detection path tests

See inline comment on line 1202 of the spec. The stubs are a minor cleanup but worth removing since they imply the code uses destination_root for path resolution when it actually relies on Dir.pwd.

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Mar 5, 2026

Code Review - PR 2411

The overall approach is a clear improvement over the marker-file pattern. Good changelog and solid test coverage.

ISSUE 1 - Content-comparison false-negative in finalize_shakapacker_setup:

The detection sets @shakapacker_just_installed = (yml_content_before != yml_content_after). This misses the case where a user whose existing file is already byte-for-byte identical to what Shakapacker would write, even if they accepted the conflict prompt. In that scenario @shakapacker_just_installed is false, copy_packer_config runs without force, and the user gets an unexpected conflict prompt. Bug 2289 does not recur (they are prompted) but the clean fresh-install UX degrades silently.

A simpler alternative: set @shakapacker_just_installed = true unconditionally in finalize_shakapacker_setup, which is only reached when install_shakapacker returns true. The content-diff approach aims to preserve user customizations when they decline Shakapacker overwrite prompt, but introduces a silent regression path worth documenting as an explicit trade-off.

ISSUE 2 - --shakapacker-just-installed bypasses shakapacker_configured?:

If passed via CLI, shakapacker_just_installed? returns true regardless of whether ensure_shakapacker_installed ran (which only fires when !shakapacker_configured?). Running the generator with this flag on an app with Shakapacker already fully configured will silently force-overwrite config/shakapacker.yml without any prompt. The code comment discloses this, but it is a destructive footgun for any user who discovers the flag. Consider adding a warning when used with a pre-configured install, or restricting it to test environments.

ISSUE 3 (minor) - puts vs Thor say:

New lines in copy_packer_config and finalize_shakapacker_setup use raw puts rather than Thor say/say_status. Thor say respects --quiet and the generator output stream.

POSITIVES:

  • Forwarding force/skip to sub-generators (react_with_redux, react_no_redux, pro, rsc) is a correct and overdue fix - these flags were silently dropped before.
  • Refactoring install_shakapacker to return true/false and gate finalize_shakapacker_setup on the return value is clean.
  • The copy_packer_config force behavior unit tests directly assert the force kwarg on the template call.
  • The ensure_shakapacker_installed detection path tests cover all four content-change variants including nil-to-nil.

…arity

Assign the heredoc to `base` before calling `.chomp + version_warning`
so the method chain is visually separate from the heredoc body.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
@ihabadham ihabadham merged commit 11f2320 into master Mar 5, 2026
5 of 12 checks passed
@ihabadham ihabadham deleted the ihabadham/fix/replace-marker-with-force-option branch March 5, 2026 22:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug P0 Merge this week P1 Target this sprint release:16.4.0-must-have Must-have for 16.4.0: critical bug/perf/usability

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Generator skips private_output_path configuration when Shakapacker is freshly installed

2 participants