Skip to content

Doctor: accept TS/TSX server bundle suffixes#3111

Merged
justin808 merged 4 commits intomainfrom
jg/doctor-ts-suffix
Apr 12, 2026
Merged

Doctor: accept TS/TSX server bundle suffixes#3111
justin808 merged 4 commits intomainfrom
jg/doctor-ts-suffix

Conversation

@justin808
Copy link
Copy Markdown
Member

@justin808 justin808 commented Apr 12, 2026

Summary

react_on_rails:doctor could report a false warning when server_bundle_js_file was server-bundle.js but the Shakapacker source entrypoint file was server-bundle.ts.
This PR makes doctor resolve common source suffixes (.js, .jsx, .ts, .tsx, .mjs, .cjs) before warning, and updates doctor messaging to clarify TS/TSX source entrypoints are valid.

Pull Request checklist

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

Other Information

Issue searches in shakacode/react_on_rails did not find an existing issue for this exact suffix false-positive.


Note

Low Risk
Low risk: changes only adjust react_on_rails:doctor path detection to reduce false warnings; no runtime rendering or security-sensitive logic is modified.

Overview
Doctor now accepts non-.js server bundle source entrypoints. When react_on_rails:doctor determines the server bundle path, it now tries common source extensions (.js, .jsx, .ts, .tsx, .mjs, .cjs) before warning that the configured bundle is missing, avoiding false positives for apps using server-bundle.ts.

Adds focused specs for the new extension-resolution behavior and documents the fix in the CHANGELOG.

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

Summary by CodeRabbit

  • Bug Fixes

    • react_on_rails:doctor now correctly resolves server bundle files with alternate extensions (.js, .jsx, .ts, .tsx, .mjs, .cjs), preventing false warnings for missing bundles when using TypeScript configurations.
  • Tests

    • Added comprehensive test coverage for server bundle path resolution across multiple configuration scenarios.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 12, 2026

Walkthrough

The react_on_rails:doctor task now resolves server bundle source paths by probing for alternative file extensions (.js, .jsx, .ts, .tsx, .mjs, .cjs) before warning about missing bundles, preventing false positives for configurations using different source file types.

Changes

Cohort / File(s) Summary
Changelog
CHANGELOG.md
Added unreleased fix entry documenting that react_on_rails:doctor resolves common TypeScript/JavaScript server bundle entrypoint suffixes before emitting missing bundle warnings.
Doctor Implementation
react_on_rails/lib/react_on_rails/doctor.rb
Added SERVER_BUNDLE_SOURCE_EXTENSIONS constant and two new methods (resolve_server_bundle_source_path, server_bundle_source_extensions_for) to probe for alternative file extensions when a configured bundle path does not exist. Updated determine_server_bundle_path to use the new resolution logic.
Doctor Specifications
react_on_rails/spec/lib/react_on_rails/doctor_spec.rb
Added test cases verifying that determine_server_bundle_path correctly resolves TypeScript bundles (.ts) when JavaScript (.js) is configured but missing, across multiple Shakapacker availability scenarios.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A bundle by any extension,
Still hops along with the same intention!
Doctor now finds .ts and .js,
No false alarms—just perfect bliss! 🌟

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Doctor: accept TS/TSX server bundle suffixes' directly and clearly summarizes the main change—enabling the doctor tool to recognize TypeScript/TSX server bundle files, preventing false positives. It is concise, specific, and reflects the core objective of the PR.

✏️ 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/doctor-ts-suffix

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/react_on_rails/doctor.rb
Comment thread react_on_rails/spec/lib/react_on_rails/doctor_spec.rb
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 12, 2026

Review

The fix is correct and well-motivated. The false positive occurred because determine_server_bundle_path returned a .js path verbatim, which check_javascript_bundles then tested with File.exist? — naturally failing when the source file was .ts. Resolving sibling extensions before returning the path is the right place to handle this.

Issues

Redundant File.exist? in the candidate loop (see inline on doctor.rb)

resolve_server_bundle_source_path already calls File.exist?(bundle_path) and returns early if it exists. server_bundle_source_extensions_for then prepends the original extension back into the candidate list, so the first loop iteration calls File.exist?("#{base_path}#{extension}") — which reconstructs the original path — a check guaranteed to return false. Excluding the original extension from the candidates avoids this.

Existing test now has implicit filesystem dependency (see inline on doctor_spec.rb)

The "uses Shakapacker API configuration with relative paths" test (unchanged) now runs File.exist? against real paths on the host for all six candidate extensions. It passes only because those files don't exist in the test environment. Adding File.exist? stubs to that test (or a dedicated unit test for resolve_server_bundle_source_path) would make this hermetic.

Minor

  • CHANGELOG update is unchecked in the PR description — worth confirming whether this is intentional.
  • The new info message ("Source entrypoint can use .js/.jsx/.ts/.tsx suffixes") is placed inside the server_bundle_js_file not configured branch of check_server_bundle_prerender_consistency. The actual fix is transparent to the user (no extra output when the TS file is found), so this hint only surfaces when SSR is enabled but server_bundle_js_file is not set — a reasonable spot.

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 12, 2026

Review: Doctor accepts TS/TSX server bundle suffixes

The overall approach is sound and addresses a real user pain point. The resolve_server_bundle_source_path helper is clean and the regex (%r{\.[^./]+\z}) correctly strips only the file extension without touching directory components.

A few things worth addressing:

Redundant extension re-check

In server_bundle_source_extensions_for, when a bundle_path has an extension (e.g. .js), the method prepends that same extension to the candidate list:

([extension] + SERVER_BUNDLE_SOURCE_EXTENSIONS).uniq

But by the time this method is called, resolve_server_bundle_source_path has already confirmed the file at bundle_path does not exist. The base_path + original_extension candidate is therefore guaranteed to produce the same non-existent path. The .uniq prevents a duplicate when the extension is already in SERVER_BUNDLE_SOURCE_EXTENSIONS (e.g. .js), but for extensions outside the set (e.g. a hypothetical .es) it adds a redundant File.exist? call. Simplest fix: always return only SERVER_BUNDLE_SOURCE_EXTENSIONS since we already tried the original.

Misleading placement of the new info message

The new line:

💡 Source entrypoint can use .js/.jsx/.ts/.tsx suffixes

is emitted in the branch where server_bundle_js_file is not configured at all. The TS/TSX auto-resolution only kicks in when a bundle filename is configured—so this hint appears in a context where it doesn't yet apply. Consider moving it to the elsif server_bundle_set branch or to the success path where the feature is actually exercised.

Test coverage gap

The new test exercises only .ts. It would be good to add at least one more case (e.g. .tsx) and a case where no alternative extension exists (fallback returns the original .js path unchanged). The latter is implicitly covered by the pre-existing "relative paths" test, but making it explicit would document the intended fallback behaviour.

Comment thread react_on_rails/lib/react_on_rails/doctor.rb Outdated
Comment thread react_on_rails/lib/react_on_rails/doctor.rb Outdated
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 12, 2026

Greptile Summary

Fixes a false-positive doctor warning where server_bundle_js_file = \"server-bundle.js\" was flagged as missing when the actual Shakapacker source entrypoint was server-bundle.ts (or another TS/JSX variant). The new resolve_server_bundle_source_path helper tries all common source extensions before falling back to the configured name, and a spec covers the primary .js.ts resolution case.

Confidence Score: 5/5

Safe to merge; both findings are minor style suggestions that do not affect runtime correctness.

All findings are P2 — an incomplete info message string and a test isolation nit. The core fix logic is correct, the regex properly strips extensions, and the fallback behavior is sound.

No files require special attention.

Important Files Changed

Filename Overview
react_on_rails/lib/react_on_rails/doctor.rb Adds SERVER_BUNDLE_SOURCE_EXTENSIONS constant and resolve_server_bundle_source_path helper to try alternate source extensions before reporting a missing bundle; logic and regex are correct, info message text is slightly incomplete (omits .mjs/.cjs).
react_on_rails/spec/lib/react_on_rails/doctor_spec.rb New test correctly covers the .ts fallback path; existing "relative paths" test now implicitly relies on filesystem state since File.exist? is no longer a no-op in determine_server_bundle_path.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[determine_server_bundle_path] --> B[Build bundle_path from source_path + entry + filename]
    B --> C[resolve_server_bundle_source_path]
    C --> D{File.exist? bundle_path?}
    D -- yes --> E[Return bundle_path]
    D -- no --> F[Strip extension to base_path]
    F --> G[server_bundle_source_extensions_for .js .jsx .ts .tsx .mjs .cjs]
    G --> H{Try each extension}
    H -- exists --> I[Return candidate_path]
    H -- none found --> J[Return original bundle_path]
    E --> K[check_javascript_bundles File.exist? check]
    I --> K
    J --> K
    K -- exists --> L[Success]
    K -- missing --> M[Warning]
Loading

Comments Outside Diff (1)

  1. react_on_rails/spec/lib/react_on_rails/doctor_spec.rb, line 299-302 (link)

    P2 Existing test now implicitly relies on filesystem state

    After this PR, determine_server_bundle_path calls resolve_server_bundle_source_path, which calls File.exist? on up to 7 real paths (.js, .jsx, .ts, .tsx, .mjs, .cjs). The existing test expects "client/app/packs/server-bundle.js" but doesn't stub those calls, so it silently relies on none of those files existing in the CI workspace. Adding explicit stubs (as done in the new test) would make the intent clear and prevent accidental flakiness.

Reviews (1): Last reviewed commit: "Document doctor TS entrypoint fix in cha..." | Re-trigger Greptile

Comment thread react_on_rails/lib/react_on_rails/doctor.rb Outdated
Copy link
Copy Markdown

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

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dc646adb5d

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread react_on_rails/lib/react_on_rails/doctor.rb
Comment thread react_on_rails/lib/react_on_rails/doctor.rb
Comment thread react_on_rails/lib/react_on_rails/doctor.rb
Comment thread react_on_rails/spec/lib/react_on_rails/doctor_spec.rb
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 12, 2026

Review: Doctor accepts TS/TSX server bundle suffixes

Overall: Looks good — the fix is correct and the core logic is sound. A few things worth addressing before merge.

What works well

  • The regex %r{\.[^./]+\z} correctly strips only the final extension and handles directory names that contain dots (e.g. client/app.v2/packs/server-bundle.js).
  • Excluding the original extension from the candidate list avoids a redundant re-check after the first File.exist? guard.
  • SERVER_BUNDLE_SOURCE_EXTENSIONS is properly frozen.
  • The fallback (return original path when no alternative exists) preserves the existing warning behaviour precisely.
  • Changelog entry is accurate and well-placed.

Issues to address

1. Filesystem-dependent tests in unchanged contexts (medium)

Three existing contexts now implicitly hit the real filesystem through resolve_server_bundle_source_path but have no File.exist? stubs:

  • "with absolute paths" → probes client/app/packs/server-bundle.{js,jsx,ts,tsx,mjs,cjs}
  • "with nested absolute paths" → same
  • "when Shakapacker gem is not available" → probes app/javascript/packs/server-bundle.*

All three pass today because those files don't exist in CI, but they'll give a false green if extension resolution is broken for those paths. The relative-paths context already shows the right pattern — apply the same allow(File).to receive(:exist?) stub to those three before blocks.

2. No TypeScript test for the rescue path (low)

The rescue branch ("when Shakapacker gem is not available") also routes through resolve_server_bundle_source_path now, but only the happy-path (no TS file found) is tested. A companion it "accepts a TypeScript…" case mirroring the one added here would give complete coverage.

3. server_bundle_source_extensions_for edge case with non-source extensions (low)

If bundle_path carries an extension that isn't in SERVER_BUNDLE_SOURCE_EXTENSIONS (e.g. .map), the method returns the full list. Combined with the base-path strip, this produces paths like server-bundle.js.js. This can't happen with the current callers, but the inline suggestion I left makes the guard explicit and self-documenting.

4. Double File.exist? call (trivial)

resolve_server_bundle_source_path verifies the file exists before returning it, and check_javascript_bundles then calls File.exist? on the returned path again (line 213). No correctness impact — just a redundant syscall.

The absolute-paths, nested-absolute-paths, and LoadError rescue test
contexts were implicitly filesystem-dependent after the addition of
resolve_server_bundle_source_path. Add the same File.exist? stub
pattern used in the relative-paths context, and add a TS resolution
test for the rescue path.

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

Review Comment Summary

Triaged 14 review comments across 9 threads. All threads are now resolved.

Fixed (2 items, commit 7570857)

  • Test hermiticity: Added File.exist? stubs to 3 test contexts (absolute paths, nested absolute paths, LoadError rescue) that were implicitly filesystem-dependent after resolve_server_bundle_source_path was introduced
  • Rescue path TS coverage: Added a TypeScript resolution test for the "Shakapacker gem not available" (LoadError) path

Already addressed in dc646ad (4 items)

  • Redundant extension re-check → changed to reject
  • SERVER_BUNDLE_SOURCE_EXTENSIONS returned directly → uses reject instead
  • Info hint in wrong branch → message removed
  • Info message missing .mjs/.cjs → message removed

Declined with rationale (3 items)

  • Misconfigured extension passing doctor → doctor resolves source entrypoints, not runtime config validation
  • Double File.exist? call → trivial, keeps check_javascript_bundles self-contained
  • Non-source extension edge case → can't happen with current callers

Copy link
Copy Markdown

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

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7570857364

ℹ️ About Codex in GitHub

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

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

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

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

Comment on lines +1583 to +1585
return SERVER_BUNDLE_SOURCE_EXTENSIONS if extension.empty?

SERVER_BUNDLE_SOURCE_EXTENSIONS.reject { |candidate_extension| candidate_extension == extension }
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Don't treat unknown server bundle extensions as valid

server_bundle_source_extensions_for falls back to all known source suffixes whenever the configured filename has an unrecognized extension, so a typo like server-bundle.jss will pass the doctor check as soon as server-bundle.ts (or .js/.jsx/.tsx/.mjs/.cjs) exists. In that scenario SSR can still fail at runtime because React on Rails resolves the configured server_bundle_js_file, not the discovered fallback path. Fresh evidence in this commit is that determine_server_bundle_path now always calls this fallback and the new specs only verify alternate-extension success cases.

Useful? React with 👍 / 👎.

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

🧹 Nitpick comments (1)
react_on_rails/spec/lib/react_on_rails/doctor_spec.rb (1)

297-300: Consider extracting shared extension-stubbing setup into a helper.

The same extension loop/stub is repeated in four contexts, which increases drift risk if supported extensions change again.

♻️ Optional refactor
+      def stub_server_bundle_candidates(base_path)
+        allow(File).to receive(:exist?).and_call_original
+        %w[.js .jsx .ts .tsx .mjs .cjs].each do |ext|
+          allow(File).to receive(:exist?).with("#{base_path}#{ext}").and_return(false)
+        end
+      end
+
       before do
         ...
-        allow(File).to receive(:exist?).and_call_original
-        %w[.js .jsx .ts .tsx .mjs .cjs].each do |extension|
-          allow(File).to receive(:exist?).with("client/app/packs/server-bundle#{extension}").and_return(false)
-        end
+        stub_server_bundle_candidates("client/app/packs/server-bundle")
       end

Also applies to: 330-333, 355-358, 371-374

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@react_on_rails/spec/lib/react_on_rails/doctor_spec.rb` around lines 297 -
300, Extract the repeated extension-stubbing loop into a single test helper to
avoid duplication: create a helper method (e.g., stub_server_bundle_extensions)
or a shared_context used by the examples in react_on_rails::doctor_spec that
performs the allow(File).to receive(:exist?).and_call_original setup and then
iterates the extensions array %w[.js .jsx .ts .tsx .mjs .cjs] to stub
File.exist? for "client/app/packs/server-bundle#{extension}" to false; replace
the four duplicated blocks (the occurrences using the extensions loop around
allow(File).to receive(:exist?).with(...).and_return(false)) with calls to that
helper or include the shared_context so all specs reference the single
implementation.
🤖 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/react_on_rails/doctor.rb`:
- Around line 1581-1586: The method server_bundle_source_extensions_for
currently treats any unknown extension like a valid specific suffix and still
returns alternate candidates; change it to first check the configured extension
(File.extname(bundle_path)) and if it's non-empty but not included in
SERVER_BUNDLE_SOURCE_EXTENSIONS return an empty array (no probes), otherwise
keep the existing behavior (return SERVER_BUNDLE_SOURCE_EXTENSIONS when
extension.empty? or reject the exact extension when it is a known member).
Ensure you update server_bundle_source_extensions_for to use
SERVER_BUNDLE_SOURCE_EXTENSIONS.include?(extension) for the guard.

---

Nitpick comments:
In `@react_on_rails/spec/lib/react_on_rails/doctor_spec.rb`:
- Around line 297-300: Extract the repeated extension-stubbing loop into a
single test helper to avoid duplication: create a helper method (e.g.,
stub_server_bundle_extensions) or a shared_context used by the examples in
react_on_rails::doctor_spec that performs the allow(File).to
receive(:exist?).and_call_original setup and then iterates the extensions array
%w[.js .jsx .ts .tsx .mjs .cjs] to stub File.exist? for
"client/app/packs/server-bundle#{extension}" to false; replace the four
duplicated blocks (the occurrences using the extensions loop around
allow(File).to receive(:exist?).with(...).and_return(false)) with calls to that
helper or include the shared_context so all specs reference the single
implementation.
🪄 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: 109af017-6cbb-4b96-a8f0-4531470476ec

📥 Commits

Reviewing files that changed from the base of the PR and between d73245e and 7570857.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • react_on_rails/lib/react_on_rails/doctor.rb
  • react_on_rails/spec/lib/react_on_rails/doctor_spec.rb

Comment on lines +1581 to +1586
def server_bundle_source_extensions_for(bundle_path)
extension = File.extname(bundle_path)
return SERVER_BUNDLE_SOURCE_EXTENSIONS if extension.empty?

SERVER_BUNDLE_SOURCE_EXTENSIONS.reject { |candidate_extension| candidate_extension == extension }
end
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.

⚠️ Potential issue | 🟡 Minor

Avoid probing alternate extensions for unknown configured suffixes.

If server_bundle_js_file has an unsupported extension (e.g., .jss), current logic still probes known suffixes and may incorrectly pass diagnostics.

🐛 Suggested guard
 def server_bundle_source_extensions_for(bundle_path)
   extension = File.extname(bundle_path)
   return SERVER_BUNDLE_SOURCE_EXTENSIONS if extension.empty?
+  return [] unless SERVER_BUNDLE_SOURCE_EXTENSIONS.include?(extension)
 
   SERVER_BUNDLE_SOURCE_EXTENSIONS.reject { |candidate_extension| candidate_extension == extension }
 end
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@react_on_rails/lib/react_on_rails/doctor.rb` around lines 1581 - 1586, The
method server_bundle_source_extensions_for currently treats any unknown
extension like a valid specific suffix and still returns alternate candidates;
change it to first check the configured extension (File.extname(bundle_path))
and if it's non-empty but not included in SERVER_BUNDLE_SOURCE_EXTENSIONS return
an empty array (no probes), otherwise keep the existing behavior (return
SERVER_BUNDLE_SOURCE_EXTENSIONS when extension.empty? or reject the exact
extension when it is a known member). Ensure you update
server_bundle_source_extensions_for to use
SERVER_BUNDLE_SOURCE_EXTENSIONS.include?(extension) for the guard.

bundle_path
end

def server_bundle_source_extensions_for(bundle_path)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The method name server_bundle_source_extensions_for is a bit ambiguous — it actually returns alternate candidate extensions (excluding the original), not all extensions for that bundle. A name like alternate_source_extensions_for would clarify intent.

Also, a subtle edge case: if the configured extension is not in SERVER_BUNDLE_SOURCE_EXTENSIONS (e.g. .mts, .cts), this returns the full list — meaning .js is included in the candidates even though the user explicitly chose a non-JS extension. The behavior is still correct (it finds the closest match), but it could unexpectedly resolve to a .js file that's unrelated. Worth a code comment explaining why full-list inclusion is intentional in that case.

def resolve_server_bundle_source_path(bundle_path)
return bundle_path if File.exist?(bundle_path)

base_path = bundle_path.sub(%r{\.[^./]+\z}, "")
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 regex %r{\.[^./]+\z} is correct (the [^./] exclusion prevents matching dots inside directory components), but this would silently strip nothing if bundle_path has no extension — leaving base_path == bundle_path. The subsequent extension candidates would then be appended to the extensionless stem, which is actually desirable behavior. Worth a brief comment since it's non-obvious:

Suggested change
base_path = bundle_path.sub(%r{\.[^./]+\z}, "")
base_path = bundle_path.sub(%r{\.[^./]+\z}, "") # strips the last extension, if any; handles dots in directory names

expect(path).to eq("client/app/packs/server-bundle.js")
end

it "accepts a TypeScript server bundle source file when the configured filename is .js" do
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 new test only covers .ts as an alternative extension. The parallel contexts for absolute paths ("when Shakapacker gem is available with absolute paths" and "when Shakapacker gem returns nested absolute paths") received the before-block stubs but no corresponding TS-resolution test. Suggest also adding coverage for at least one other non-JS extension (e.g. .tsx) either here or in a shared example, to guard against accidental ordering bugs in the extension list iteration.

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 12, 2026

Review: Doctor TS/TSX server bundle suffix fix

Overall this is a clean, focused, low-risk fix for a genuine false-positive in react_on_rails:doctor. The logic is correct and the CHANGELOG entry is clear.

How the fix works

resolve_server_bundle_source_path is called from determine_server_bundle_path. It:

  1. Returns the original path immediately if it exists (no change in behavior for existing setups).
  2. Strips the extension and tries each entry in SERVER_BUNDLE_SOURCE_EXTENSIONS until it finds one that exists on disk.
  3. Falls back to the original (non-existing) path if nothing matches — preserving the existing warning behavior in check_server_bundle.

The caller at line 213 then does File.exist? on whatever is returned, so the warning/success messaging remains accurate. Correct end-to-end.

Issues noted (inline)

  • server_bundle_source_extensions_for naming + edge case — the method excludes the original extension only when it's in the known list; if someone configures an unlisted extension (e.g. .mts), the full list including .js is tried, which could resolve to an unrelated file. Worth documenting the intent.
  • Opaque regex — a one-line comment on the base_path substitution would help future readers understand why [^./] is used.
  • Test coverage gap — the absolute-path contexts received before-block stubs but no TS-resolution it block; only .ts is tested across the new cases, not .tsx/.jsx/.mjs/.cjs.

None of these block the merge — the core logic is sound and the primary use case is well-covered.

@justin808 justin808 merged commit 9791180 into main Apr 12, 2026
54 checks passed
@justin808 justin808 deleted the jg/doctor-ts-suffix branch April 12, 2026 23:16
justin808 added a commit that referenced this pull request Apr 18, 2026
…ages

* origin/main:
  Fix initial page startup race for late-loading client bundles (#3151)
  chore: apply prettier formatting to tracked docs files (#3153)
  docs: comprehensive RSC API documentation and registration consolidation (#3140)
  Split rspec-package-tests into parallel generator/unit shards (#3134)
  fix: add concurrency groups to long-running CI workflows (#3133)
  refactor: add RenderRequest, JsCodeBuilder, and RenderingStrategy abstractions (#3094)
  fix: address deferred review items from PR #2849 (#3093)
  Add complimentary OSS license policy for React on Rails Pro (#3123)
  fix: centralize CI docs-only detection and add CLI flag validation (#3091)
  refactor: replace stub-throw + Object.assign with capability-based composition (#3096)
  Enhance address-review with parallel fixes, self-review, and Greptile verification (#3121)
  fix: Doctor no longer fails custom projects for missing bin/dev (#3117)
  fix: cap webpack <5.106.0 to prevent ExecJS SSR breakage (#3095)
  Add Rspack + RSC compatibility tests and documentation (#1828) (#3120)
  Add error scenarios hub and test pages (#2497)
  docs: document polyfill requirements for web-targeted server bundles (#3092)
  docs: RSC integration pitfalls from tutorial app (#3087)
  docs: fix render function/helper API documentation (#3088)
  Doctor: accept TS/TSX server bundle suffixes (#3111)
  feat: add CI guard requiring sidebar updates when adding docs (#3089)
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