Skip to content

Follow up #2890: harden generator route checks#2891

Merged
justin808 merged 4 commits intomainfrom
jg/fix-2890
Mar 30, 2026
Merged

Follow up #2890: harden generator route checks#2891
justin808 merged 4 commits intomainfrom
jg/fix-2890

Conversation

@justin808
Copy link
Copy Markdown
Member

@justin808 justin808 commented Mar 29, 2026

Summary

Follow up issue #2890 by hardening generator behavior around new-app root-route handling and removing duplicated root-route detection logic. This centralizes root-route checks in a shared helper used by base/install/demo-page generator paths and makes home-page generation resilient to action-order refactors. It also adds an explicit railties major-version guard around RAILS_GIT_TEMPLATE_RENDERER in create-react-on-rails-app, with fallback scaffolds preserved when unsupported.

Pull Request checklist

  • Add/update test to cover these changes
  • Update documentation
  • Update CHANGELOG file

Other Information

Validated locally with targeted specs and linting:

  • bundle exec rspec react_on_rails/spec/react_on_rails/generators/base_generator_spec.rb react_on_rails/spec/react_on_rails/generators/generator_helper_spec.rb
  • bundle exec rspec react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb:117 react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb:138 react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb:1594 react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb:1912
  • pnpm --filter create-react-on-rails-app run test -- create-app.test.ts
  • bundle exec rubocop on touched Ruby files and pnpm eslint on touched TS source

Note

Medium Risk
Touches Rails generator scaffolding paths (root-route injection, landing-page enablement, and git scaffold restoration), so regressions could affect newly generated apps and Windows checkouts. Changes are localized and covered by new unit/spec tests, reducing risk.

Overview
Generator hardening for --new-app: Centralizes root-route detection into a shared root_route_present? helper and reuses it across base/install/demo-page generator flows to remove duplicated parsing.

More resilient root-route injection: add_root_route now guards against double invocation, warns (instead of raising) when config/routes.rb is missing or unexpected, and copy_base_files explicitly ensures root-route state is initialized so home-page generation won’t break if generator action ordering changes.

CLI git scaffold robustness: create-react-on-rails-app now only attempts rendering Rails’ installed .gitignore/.gitattributes templates when railties major is in a supported set (7/8); failures fall back to bundled defaults. Tests were added/updated to cover the railties guard, fallback behavior, and the new root-route helper and warnings.

Written by Cursor Bugbot for commit 05aa93a. This will update automatically on new commits. Configure here.

Summary by CodeRabbit

  • New Features

    • Installer now validates Rails major versions (7 and 8), warns and aborts on unsupported versions and falls back to bundled scaffold if template rendering fails.
  • Bug Fixes

    • Home-page generation and root-route handling made more robust to generator ordering and duplicate additions; missing routes file now emits a warning instead of failing.
  • Tests

    • Expanded coverage for version gating, rendering fallback, and root-route detection.

Reduce new-app generator fragility by centralizing root-route detection and making home-page generation resilient to action ordering changes. Also add an explicit railties-major guard around create-app template rendering so unsupported Rails internals fail safely to fallback scaffolds.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 29, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e3fe86aa-bb78-4505-bbd3-3fab09d8f349

📥 Commits

Reviewing files that changed from the base of the PR and between 0d44cf7 and 05aa93a.

📒 Files selected for processing (2)
  • react_on_rails/lib/generators/react_on_rails/base_generator.rb
  • react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb

Walkthrough

Adds railties-major validation to the embedded Rails template renderer (exits on unsupported majors) and centralizes root-route detection in a new root_route_present? helper. Generator methods now lazily initialize root-route state and delegate detection to the helper; tests cover both behaviors.

Changes

Cohort / File(s) Summary
Railties Version Gating
packages/create-react-on-rails-app/src/create-app.ts, packages/create-react-on-rails-app/tests/create-app.test.ts
Embedded Ruby renderer now reads Gem::Specification.find_by_name("railties"), sets railties_major = spec.version.major, validates against SUPPORTED_RAILTIES_MAJORS = [7, 8], warns and exit 1 on unsupported majors. Tests assert the guard is injected and verify fallback to bundled scaffold on renderer failure.
Root Route Detection Helper
react_on_rails/lib/generators/react_on_rails/generator_helper.rb
Adds root_route_present?(routes_path = File.join(destination_root, "config/routes.rb")) that returns false if missing, otherwise scans for a non-commented root route.
Generator Control Flow Updates
react_on_rails/lib/generators/react_on_rails/base_generator.rb, react_on_rails/lib/generators/react_on_rails/demo_page_config.rb, react_on_rails/lib/generators/react_on_rails/install_generator.rb
Refactors generators to delegate root-route checks to the new helper, introduces ensure_new_app_root_route_initialized/lazy @new_app_root_route_added initialization, and adds an early-return guard when config/routes.rb is missing.
Test Coverage
react_on_rails/spec/react_on_rails/generators/base_generator_spec.rb, react_on_rails/spec/react_on_rails/generators/generator_helper_spec.rb, react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb
Adds specs for #generate_new_app_home_page? lazy-init behavior, for #root_route_present? (missing file, commented vs active root lines), and for generator warning when config/routes.rb is absent.

Sequence Diagram(s)

sequenceDiagram
    participant CLI as create-react-on-rails CLI
    participant Ruby as ruby -e (embedded renderer)
    participant Gem as Gem::Specification
    participant ERB as ERB/template

    CLI->>Ruby: execute renderer script
    Ruby->>Gem: find_by_name("railties")
    Gem-->>Ruby: spec (version, gem_dir)
    Ruby->>Ruby: set railties_major = spec.version.major
    alt railties_major in [7,8]
        Ruby->>ERB: render template from spec.gem_dir
        ERB-->>Ruby: rendered output
        Ruby-->>CLI: template applied
    else unsupported
        Ruby->>Ruby: warn with spec.version
        Ruby-->>CLI: exit 1
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 I sniffed the gems and checked the rails,

Major seven or eight prevails.
Routes no longer hide in moss,
One helper finds them, quick as toss.
Hops and tests — the code set sails!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ 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 'Follow up #2890: harden generator route checks' directly and clearly summarizes the main changes: hardening/improving the robustness of generator route-checking logic as a follow-up to a previous issue.

✏️ 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/fix-2890

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.

Comment thread react_on_rails/lib/generators/react_on_rails/base_generator.rb Outdated
Comment thread react_on_rails/lib/generators/react_on_rails/base_generator.rb
Comment thread packages/create-react-on-rails-app/tests/create-app.test.ts
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Mar 29, 2026

Review

Overall the consolidation is a clean improvement — deduplicating three copies of the file-scan logic into a single root_route_present? helper is the right call. The railties version guard is a good defensive addition. A few things worth discussing:

Behavioural change: memoization removed from preexisting_root_route?

The old implementation cached its result in @preexisting_root_route, so the check always reflected the filesystem state before any injection. The new delegation to root_route_present? re-reads the file on every call, meaning a call after the injection returns true instead of the original false.

This is safe today because the double-invocation guard (return if defined?(@new_app_root_route_added)) prevents preexisting_root_route? from being called a second time through add_root_route. But the safety now relies on both guards staying in sync — a future change that removes one could silently introduce a regression. Inline note left on preexisting_root_route?.

Side effect inside a predicate method

generate_new_app_home_page? lazily calls add_root_route, which has real side effects (inject_into_file, say_status). In the normal flow this is fine, but it means a boolean predicate can mutate config/routes.rb mid-action. Inline note suggests making this explicit.

Test coverage gap

root_route_present? has no test for the case where routes.rb exists but contains no root route (the File.foreach.any? returning false path). Inline suggestion added.

Fallback coverage for the railties version guard

The new TS test verifies the guard expression is present in the script string, but doesn't confirm the fallback scaffold is actually used when the Ruby process exits 1. If that path isn't covered elsewhere, a note or additional test would help.

CHANGELOG

The checklist item is unchecked — presumably intentional, but worth confirming.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 29, 2026

size-limit report 📦

Path Size
react-on-rails/client bundled (gzip) 62.63 KB (0%)
react-on-rails/client bundled (gzip) (time) 62.63 KB (0%)
react-on-rails/client bundled (brotli) 53.7 KB (0%)
react-on-rails/client bundled (brotli) (time) 53.7 KB (0%)
react-on-rails-pro/client bundled (gzip) 63.65 KB (0%)
react-on-rails-pro/client bundled (gzip) (time) 63.65 KB (0%)
react-on-rails-pro/client bundled (brotli) 54.67 KB (0%)
react-on-rails-pro/client bundled (brotli) (time) 54.67 KB (0%)
registerServerComponent/client bundled (gzip) 127.46 KB (0%)
registerServerComponent/client bundled (gzip) (time) 127.46 KB (0%)
registerServerComponent/client bundled (brotli) 61.65 KB (0%)
registerServerComponent/client bundled (brotli) (time) 61.65 KB (0%)
wrapServerComponentRenderer/client bundled (gzip) 122.02 KB (0%)
wrapServerComponentRenderer/client bundled (gzip) (time) 122.02 KB (0%)
wrapServerComponentRenderer/client bundled (brotli) 56.77 KB (0%)
wrapServerComponentRenderer/client bundled (brotli) (time) 56.77 KB (0%)

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Mar 29, 2026

Greptile Summary

This PR follows up #2890 by hardening the generator's root-route handling in two complementary ways. On the Ruby side it centralises the previously-duplicated config/routes.rb inspection into a single GeneratorHelper#root_route_present? helper that is now shared by BaseGenerator, InstallGenerator, and DemoPageConfig, and makes generate_new_app_home_page? resilient to action-ordering changes via a lazy add_root_route call guarded against double invocation. On the TypeScript side it adds an explicit SUPPORTED_RAILTIES_MAJORS constant and a corresponding guard in the inline Ruby renderer so that unsupported railties versions (and a missing gem) cleanly fall back to the bundled scaffold instead of producing cryptic errors.

  • Centralised route detectionroot_route_present? replaces three identical file-reading blocks, eliminating drift risk.
  • Double-invocation guardadd_root_route now returns early if @new_app_root_route_added is already defined, preventing accidental re-injection if action ordering ever changes.
  • Lazy initialisation resiliencegenerate_new_app_home_page? calls add_root_route itself if the ivar has not been set, so copy_base_files behaves correctly regardless of whether it runs before or after the add_root_route action.
  • Railties version guardRAILS_GIT_TEMPLATE_RENDERER now exits early with a clear message for unsupported majors, keeping the fallback scaffold path well-exercised.
  • Test coverage — new specs cover root_route_present?, the lazy-init path in generate_new_app_home_page?, and the version-guard presence in the rendered Ruby script.
  • Minor: preexisting_root_route? loses its previous memoization and becomes a thin passthrough; harmless in practice but worth noting.

Confidence Score: 5/5

Safe to merge — all remaining findings are minor style suggestions with no impact on correctness or runtime behaviour.

The refactoring is logically correct: centralized route detection removes duplication, the double-invocation guard is consistent across add_root_route and generate_new_app_home_page?, and the railties version guard correctly triggers the existing fallback path. The only findings are P2: an opaque nil dereference error message in the Ruby script and a now-unmemoized passthrough method — neither affects functionality. Test coverage for the new helper and the lazy-init path is solid.

No files require special attention.

Important Files Changed

Filename Overview
react_on_rails/lib/generators/react_on_rails/generator_helper.rb Adds shared root_route_present? helper that centralizes the routes.rb detection logic previously duplicated across three generator files.
react_on_rails/lib/generators/react_on_rails/base_generator.rb Hardens add_root_route with a defined?(@new_app_root_route_added) double-invocation guard and makes generate_new_app_home_page? resilient to action-ordering changes via a lazy add_root_route call.
react_on_rails/lib/generators/react_on_rails/install_generator.rb Replaces the inline new_app_root_route_available? duplication with a direct delegation to the shared root_route_present? helper.
react_on_rails/lib/generators/react_on_rails/demo_page_config.rb Replaces the inline new_app_landing_page_available? duplication with a direct delegation to the shared root_route_present? helper.
packages/create-react-on-rails-app/src/create-app.ts Adds SUPPORTED_RAILTIES_MAJORS constant and a Rails-side version guard in the inline Ruby renderer, exiting with code 1 (triggering the fallback scaffold) for unsupported railties majors.
packages/create-react-on-rails-app/tests/create-app.test.ts Adds a test that captures the generated Ruby renderer script and verifies the [7, 8].include?(railties_major) guard is present.
react_on_rails/spec/react_on_rails/generators/base_generator_spec.rb Adds specs for generate_new_app_home_page?: one for the early-return when --new-app is disabled, and one confirming the lazy add_root_route call path.
react_on_rails/spec/react_on_rails/generators/generator_helper_spec.rb Adds specs for the new root_route_present? helper covering missing file, commented-out routes, and active root routes.

Sequence Diagram

sequenceDiagram
    participant Gen as Generator Actions
    participant ARR as add_root_route
    participant CBF as copy_base_files
    participant GHP as generate_new_app_home_page?
    participant RRP as root_route_present?

    Note over Gen: Normal action order
    Gen->>ARR: invoke add_root_route
    ARR->>RRP: preexisting_root_route? → root_route_present?
    RRP-->>ARR: true/false
    ARR-->>ARR: set @new_app_root_route_added
    Gen->>CBF: invoke copy_base_files
    CBF->>GHP: generate_new_app_home_page?
    GHP-->>GHP: defined?(@new_app_root_route_added) → true, skip lazy call
    GHP-->>CBF: @new_app_root_route_added value

    Note over Gen: Resilient (reversed) order
    Gen->>CBF: invoke copy_base_files first
    CBF->>GHP: generate_new_app_home_page?
    GHP-->>GHP: defined?(@new_app_root_route_added) → false
    GHP->>ARR: lazy call add_root_route
    ARR->>RRP: preexisting_root_route? → root_route_present?
    RRP-->>ARR: true/false
    ARR-->>ARR: set @new_app_root_route_added
    ARR-->>GHP: return
    GHP-->>CBF: @new_app_root_route_added value
    Gen->>ARR: invoke add_root_route (scheduled)
    ARR-->>ARR: defined?(@new_app_root_route_added) → true, no-op return
Loading

Reviews (1): Last reviewed commit: "Harden generator route checks and templa..." | Re-trigger Greptile

Comment thread packages/create-react-on-rails-app/src/create-app.ts
Comment thread react_on_rails/lib/generators/react_on_rails/base_generator.rb
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/spec/react_on_rails/generators/generator_helper_spec.rb (1)

337-346: Consider adding edge case coverage for alternative root route syntax.

The root_route_present? regex uses \b word boundary, which should also match the shorthand syntax root "home#index" (without to:). A quick additional test case would confirm this and serve as documentation.

💡 Optional: additional test case
it "matches shorthand root syntax without 'to:'" do
  File.write(routes_path, <<~RUBY)
    Rails.application.routes.draw do
      root "home#index"
    end
  RUBY

  expect(root_route_present?).to be(true)
end
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@react_on_rails/spec/react_on_rails/generators/generator_helper_spec.rb`
around lines 337 - 346, Add a spec that verifies the shorthand root syntax
without `to:` is detected by root_route_present?: create a new example in
generator_helper_spec.rb that writes a routes file containing
Rails.application.routes.draw do with a line `root "home#index"` and then
asserts expect(root_route_present?).to be(true); reference the existing test
pattern (the surrounding examples and routes_path usage) and mirror its
File.write/expect structure to ensure the regex handles the alternative
shorthand syntax.
🤖 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/spec/react_on_rails/generators/generator_helper_spec.rb`:
- Around line 337-346: Add a spec that verifies the shorthand root syntax
without `to:` is detected by root_route_present?: create a new example in
generator_helper_spec.rb that writes a routes file containing
Rails.application.routes.draw do with a line `root "home#index"` and then
asserts expect(root_route_present?).to be(true); reference the existing test
pattern (the surrounding examples and routes_path usage) and mirror its
File.write/expect structure to ensure the regex handles the alternative
shorthand syntax.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e833e8fa-0d83-4eef-a97a-234cb6d3a98e

📥 Commits

Reviewing files that changed from the base of the PR and between 2b09b6a and 3948c16.

📒 Files selected for processing (8)
  • packages/create-react-on-rails-app/src/create-app.ts
  • packages/create-react-on-rails-app/tests/create-app.test.ts
  • react_on_rails/lib/generators/react_on_rails/base_generator.rb
  • react_on_rails/lib/generators/react_on_rails/demo_page_config.rb
  • react_on_rails/lib/generators/react_on_rails/generator_helper.rb
  • react_on_rails/lib/generators/react_on_rails/install_generator.rb
  • react_on_rails/spec/react_on_rails/generators/base_generator_spec.rb
  • react_on_rails/spec/react_on_rails/generators/generator_helper_spec.rb

Cover the File.foreach.any? returning false path in
root_route_present? specs, where routes.rb exists but
contains no root route definition.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Mar 29, 2026

Review

The consolidation of the three duplicate file-scan blocks into GeneratorHelper#root_route_present? is a clear improvement, and the railties version guard in the TypeScript CLI is a sensible defensive addition. A few concerns worth addressing before merge:


1. Side effect inside a predicate — CQS violation

generate_new_app_home_page? now calls add_root_route as a lazy side effect:

def generate_new_app_home_page?
  return false unless options.new_app?
  add_root_route unless defined?(@new_app_root_route_added)
  new_app_root_route_added?
end

add_root_route is not a pure query — it calls inject_into_file and say_status, mutating config/routes.rb and printing output. Hiding that inside a ? predicate violates the Command-Query Separation principle and makes the execution model hard to reason about: any caller that reads generate_new_app_home_page? for a boolean now risks triggering a file write.

The safer path is to keep generate_new_app_home_page? as a pure predicate and ensure add_root_route is always called first in the action sequence, with an explicit guard at the call site if ordering cannot be guaranteed.


2. Memoization removed from preexisting_root_route?

The old implementation cached @preexisting_root_route so the route-file state was snapshotted before any injection. The new implementation delegates straight to root_route_present?, which re-reads the file on every call. This means a call to preexisting_root_route? after inject_into_file runs will return true even though the route didn't exist before the action ran.

Today this is safe because the defined?(@new_app_root_route_added) double-invocation guard in add_root_route prevents a second call. But the safety now depends on two independent guards staying in sync — remove either and you get a silent regression.


3. TypeScript test verifies guard expression, not fallback behaviour

The new test in create-app.test.ts captures the renderer script and asserts the version-check expression is present:

expect(rendererScript).toMatch(/\[\s*7,\s*8\s*\]\.include\?\(railties_major\)/);

This confirms the constant is interpolated correctly, but it doesn't verify that readInstalledRailsGitTemplate returns null (and the fallback strings are used) when the Ruby subprocess exits with code 1. A test that stubs execCaptureArgs to throw for an unsupported version and then asserts gitignore/gitattributes come from the fallback constants would close this gap.


CHANGELOG checkbox is unchecked — presumably intentional given this is a follow-up hardening PR, but worth confirming.

Comment thread react_on_rails/lib/generators/react_on_rails/base_generator.rb Outdated
Comment thread react_on_rails/lib/generators/react_on_rails/base_generator.rb
Comment thread packages/create-react-on-rails-app/src/create-app.ts
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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@react_on_rails/lib/generators/react_on_rails/base_generator.rb`:
- Around line 329-335: The helper ensure_new_app_root_route_initialized
currently calls add_root_route which performs an unguarded File.read on
config/routes.rb; to prevent a raised error when that file is missing, add a
presence check for the routes file before calling add_root_route (e.g., return
with a warning/skip if File.exist?("config/routes.rb") is false) or
alternatively modify add_root_route to safely handle a missing config/routes.rb
(guard the File.read, bail out gracefully and log a warning). Ensure the change
references ensure_new_app_root_route_initialized and add_root_route so the
generator remains safe when actions are reordered.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a72f66c2-e937-4b11-9cce-2b5ef7843d6a

📥 Commits

Reviewing files that changed from the base of the PR and between 6a80706 and 0d44cf7.

📒 Files selected for processing (3)
  • packages/create-react-on-rails-app/tests/create-app.test.ts
  • react_on_rails/lib/generators/react_on_rails/base_generator.rb
  • react_on_rails/spec/react_on_rails/generators/base_generator_spec.rb
✅ Files skipped from review due to trivial changes (1)
  • react_on_rails/spec/react_on_rails/generators/base_generator_spec.rb
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/create-react-on-rails-app/tests/create-app.test.ts

Comment thread react_on_rails/lib/generators/react_on_rails/base_generator.rb
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Mar 30, 2026

Code Review

Overall this is a clean, well-scoped PR. Centralizing root_route_present? eliminates three nearly-identical blocks of route-reading code and the double-invocation guard in add_root_route / ensure_new_app_root_route_initialized is a good defensive design. A few issues worth addressing below.

Notable issue: silent fallback with no user notification

readInstalledRailsGitTemplate (create-app.ts) catches all errors and returns null without logging anything. The Ruby script now explicitly emits a diagnostic via warn and exits with code 1 when the railties version is unsupported — but since execCaptureArgs includes stderr in its thrown error and the catch block discards the whole error, users on Rails 9+ (or any unsupported major) will get the bundled fallback .gitignore/.gitattributes with zero explanation. The purpose of adding the version guard was precisely to surface this case, so silently falling back undermines the intent.

Consider logging the caught error at info/warn level before returning null, or at least logging once in railsGitScaffold when either template fell back.

Minor: CHANGELOG not updated

The PR checklist has this unchecked — worth confirming whether this release should be captured or if it is intentionally deferred.

Nit: version-list/template coupling is undocumented in the test suite

The embedded Ruby template is baked from SUPPORTED_RAILTIES_MAJORS at module-load time via template interpolation. The test asserts the shape of the rendered Ruby (a specific regex), which ties the test to the Ruby idiom rather than the semantic behavior. If the version check is ever rewritten to use case/when or another idiom, the test will fail even when the gate works correctly. Consider asserting that each value in SUPPORTED_RAILTIES_MAJORS appears in the script rather than asserting the exact Ruby expression.

The root_route_present? helper safely returns false when config/routes.rb
is missing, but add_root_route then falls through to an unguarded
File.read that raises Errno::ENOENT. Add a File.file? check before the
read so the generator warns gracefully instead of crashing.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@preexisting_root_route = root_route_present?
end

def ensure_new_app_root_route_initialized
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.

The defined?(@new_app_root_route_added) guard here is redundant — add_root_route already has the identical guard on line 113. If ordering is wrong and add_root_route hasn't run, calling it here will set @new_app_root_route_added normally. If it has run, add_root_route will no-op immediately.

The early return here adds a tiny short-circuit but also obscures the invariant. Consider dropping it and relying solely on the guard inside add_root_route, or add a comment explaining the optimization:

Suggested change
def ensure_new_app_root_route_initialized
def ensure_new_app_root_route_initialized
return unless options.new_app?
# add_root_route is idempotent via its own defined? guard; call unconditionally.
add_root_route
end


spec = Gem::Specification.find_by_name("railties")
railties_major = spec.version.segments.first
unless [${SUPPORTED_RAILTIES_MAJORS.join(', ')}].include?(railties_major)
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.

This embeds SUPPORTED_RAILTIES_MAJORS (a TypeScript readonly tuple) directly into a Ruby heredoc via .join(', '). That works for simple integers, but the interpolation is invisible to anyone reading just the Ruby logic later and there's no compile-time or runtime guarantee the generated Ruby is syntactically valid (e.g. if the array ever holds strings or floats).

A minimal safeguard would be an assertion at the TypeScript level:

const majorList = SUPPORTED_RAILTIES_MAJORS.join(', ');
// Sanity-check: values must be plain integers so the Ruby literal stays valid.
if (!/^\d+(, \d+)*$/.test(majorList)) {
  throw new Error(`SUPPORTED_RAILTIES_MAJORS contains non-integer values: ${majorList}`);
}

Or at minimum a comment noting the constraint so future contributors don't accidentally break the generated Ruby.

root to: "home#index"
end
RUBY

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.

The three new examples cover the public routes_path default well. One gap worth adding: the helper accepts a custom routes_path argument (used by callers that pass an explicit path) — a quick example that passes an explicit path and finds a root route would confirm the parameter is wired correctly and not accidentally shadowed.

@new_app_root_route_added = false

if preexisting_root_route?
say_status :skip, "Root route already exists; keeping existing root route", :yellow
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.

When a pre-existing root route is found, @new_app_root_route_added is initialized to false (line 115) and the method returns here — so generate_new_app_home_page? will be false, suppressing home-page generation even when the app already has a valid root route.

This is almost certainly the right intent (avoid trampling user-customized roots), but it's non-obvious. A short comment here (and a matching spec example) would make the invariant explicit and protect against someone "fixing" it later.

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Mar 30, 2026

Review Summary

Good hardening work overall. Centralizing root_route_present? eliminates three copies of the same file-parsing logic, the double-invocation guard in add_root_route is clean, and adding the railties version gate with a tested fallback is the right defensive approach.

Must-fix / worth discussing

CHANGELOG not updated — the PR checklist item is unchecked. Per project policy this should be filled in before merge.

Pre-existing root route suppresses home-page generation silently — when preexisting_root_route? is true, @new_app_root_route_added stays false (set on line 115, never flipped to true), so generate_new_app_home_page? returns false. This means a --new-app run on an app that already has a root route will quietly skip home-page scaffolding with no user-visible explanation beyond the :skip status line. Left an inline comment on line 118 suggesting a clarifying comment + spec.

Redundant defined? guard in ensure_new_app_root_route_initializedadd_root_route already has the identical defined?(@new_app_root_route_added) guard, so the early-return here is a no-op optimization that adds noise. Left an inline suggestion.

Minor

TypeScript→Ruby interpolation in RAILS_GIT_TEMPLATE_RENDERERSUPPORTED_RAILTIES_MAJORS.join(', ') is interpolated directly into a Ruby heredoc. Works fine for the current integer values, but there's no guard preventing a future value from producing invalid Ruby syntax. Left an inline note; at minimum a comment stating "values must be plain integers" would help future contributors.

root_route_present? custom-path parameter untested — the new specs cover only the default-path behavior. A single example passing an explicit path would confirm the parameter binding is correct and not shadowed.

Looks good

  • preexisting_root_route? delegating to root_route_present? is clean; memoization is correct.
  • The File.file? guard on line 125 is not redundant — root_route_present? returning false doesn't distinguish "file missing" from "file exists but no root route", so the explicit check is needed to produce the right warning.
  • New specs in install_generator_spec.rb and generator_helper_spec.rb cover the happy and missing-file paths well.
  • Fallback-on-failure behavior for RAILS_GIT_TEMPLATE_RENDERER is tested with the right assertions.

@justin808 justin808 merged commit c5288af into main Mar 30, 2026
52 checks passed
@justin808 justin808 deleted the jg/fix-2890 branch March 30, 2026 05:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant