Skip to content

Improve fresh Pro install docs and generator defaults#2494

Open
justin808 wants to merge 2 commits intomasterfrom
codex/pro-install-docs-16-4-0-rc5
Open

Improve fresh Pro install docs and generator defaults#2494
justin808 wants to merge 2 commits intomasterfrom
codex/pro-install-docs-16-4-0-rc5

Conversation

@justin808
Copy link
Member

@justin808 justin808 commented Feb 26, 2026

Summary

  • update existing-app and Pro installation docs to remove stale version examples and use explicit version placeholders
  • document the clean-git prerequisite for generator runs and add a short post-install validation checklist
  • auto-configure private_output_path: ssr-generated in config/shakapacker.yml for Shakapacker 9+ during generator setup
  • allow PORT overrides in generated Procfiles (Procfile.dev, Procfile.dev-static-assets, Procfile.dev-prod-assets)
  • add generator specs covering the new private_output_path behavior

Why

A real fresh-install run for 16.4.0.rc.5 exposed avoidable friction:

  • docs referenced old versions and skipped a required clean-git precondition
  • fresh installs emitted private bundle path warnings unless users manually edited shakapacker.yml
  • generated Procfiles hardcoded ports, making documented PORT=... overrides ineffective

Test Plan

  • mise exec [email protected] -- bundle exec rubocop react_on_rails/lib/generators/react_on_rails/base_generator.rb react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb
  • mise exec [email protected] -- bundle exec rspec react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb --tag type:generator

Note

Medium Risk
Medium risk because it changes generator behavior that edits config/shakapacker.yml and modifies node-renderer VM build concurrency cleanup; both can affect SSR build/runtime if edge cases are missed. Documentation updates are low risk, but the generator and VM changes warrant review/testing across Shakapacker versions and failure paths.

Overview
Improves install docs to use version placeholders (including pre-release formatting), consistently recommend bundle exec, call out the generator’s clean-git requirement, and document a quick post-install validation + PORT=... overrides.

Updates the Rails generator to auto-configure private_output_path: ssr-generated in config/shakapacker.yml for Shakapacker 9+ (keeping SSR bundle paths aligned) and changes generated Procfiles to honor the PORT environment variable.

Hardens the Pro node renderer’s buildVM concurrency tracking by ensuring vmCreationPromises cleanup happens via .finally() on the stored promise, and adds a regression test to confirm recovery after a failed VM build (e.g., missing bundle file).

Written by Cursor Bugbot for commit e72bebd. Configure here.

Summary by CodeRabbit

  • Documentation

    • Updated installation guides to use bundle exec commands and version placeholders for clearer setup instructions.
  • New Features

    • Development server port is now configurable via environment variables while maintaining default values.
    • Automatic SSR bundle path configuration support for Shakapacker 9.0.0+.
  • Bug Fixes

    • Improved error recovery for VM creation, enabling successful retries after initial failures.
  • Tests

    • Added test coverage for VM recovery after failed bundle creation.
    • Added conditional tests for SSR path configuration across different Shakapacker versions.

justin808 and others added 2 commits February 24, 2026 16:58
Move vmCreationPromises cleanup from a try/finally inside the async IIFE
to a .finally() on the promise chain after vmCreationPromises.set(). The
IIFE's finally block runs synchronously before set() when code throws
before the first await, leaving a rejected promise permanently stuck in
the map and preventing retries for that bundle path.

Add test verifying buildVM recovers after failure for a nonexistent bundle.

Fixes #2483

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

coderabbitai bot commented Feb 26, 2026

Walkthrough

Documentation guides, generator logic, and development tooling are updated to improve Rails installation instructions with environment-variable-driven port configuration, SSR path setup, and placeholder-based version management. VM promise cleanup is refactored for concurrent safety, and corresponding recovery tests are added.

Changes

Cohort / File(s) Summary
Installation Documentation
docs/getting-started/installation-into-an-existing-rails-app.md, react_on_rails_pro/docs/installation.md
Updated Rails installation guides to use bundle exec for all commands, replace exact version pins with placeholder tokens (<gem_version>, <npm_version>), and add explicit port configuration examples.
Generator Configuration
react_on_rails/lib/generators/react_on_rails/base_generator.rb
Added configure_private_output_path_in_shakapacker method to set Shakapacker 9.0+ SSR path configuration, automatically uncommented or inserted private_output_path: ssr-generated in config/shakapacker.yml.
Development Process Templates
react_on_rails/lib/generators/react_on_rails/templates/base/base/Procfile.dev*
Updated three Procfile templates to use environment-variable-driven port configuration (${PORT:-default}) instead of hardcoded ports, enabling flexible port assignment during development.
VM Promise Handling
packages/react-on-rails-pro-node-renderer/src/worker/vm.ts
Refactored VM creation promise storage and cleanup to store promise immediately before async work, delegate cleanup to post-settlement handlers, and prevent stale rejections from concurrent callers.
VM and Generator Tests
packages/react-on-rails-pro-node-renderer/tests/vm.test.ts, react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb
Added VM recovery test after bundle creation failure and generator tests verifying private_output_path configuration based on Shakapacker version detection.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Poem

🐰 With promises stored safe and cleanup arranged,
Port configs dance with variables changed,
Shakapacker paths bloom in config delight,
Bundle exec guides make installations just right,
Version placeholders guide gems to their flight! ✨

🚥 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 accurately summarizes the main changes: improving Pro installation documentation and adding generator defaults for port configuration and Shakapacker path setup.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/pro-install-docs-16-4-0-rc5

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

greptile-apps bot commented Feb 26, 2026

Greptile Summary

This PR improves the fresh Pro install experience by addressing friction discovered during real-world testing of 16.4.0.rc.5.

Key improvements:

  • Documentation now uses explicit version placeholders (e.g., <gem_version>) instead of outdated examples, with clear notes about pre-release version format differences
  • Generator automatically configures private_output_path: ssr-generated in config/shakapacker.yml for Shakapacker 9+ to prevent bundle path warnings
  • Generated Procfiles support PORT environment variable overrides via ${PORT:-<default>} syntax
  • Docs now document the clean-git prerequisite and include post-install validation steps
  • Fixed a race condition in buildVM where synchronous failures before the first await could leave stale rejected promises in the map

Technical notes:

  • The vm.ts fix moves promise cleanup from a try/finally inside the IIFE to .finally() on the stored promise, ensuring cleanup always runs after the promise is stored
  • The generator's configure_private_output_path_in_shakapacker method tries uncommenting existing placeholders first, then falls back to inserting after public_output_path
  • All changes have comprehensive test coverage

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • All changes are well-designed, thoroughly tested, and address real user friction. The documentation improvements remove outdated version examples and add critical prerequisites. The generator enhancement automatically configures Shakapacker 9+ for SSR bundles, preventing common warnings. The vm.ts race condition fix is properly explained with detailed comments and has test coverage. The Procfile PORT override changes are simple shell syntax improvements. No breaking changes or risky operations.
  • No files require special attention

Important Files Changed

Filename Overview
docs/getting-started/installation-into-an-existing-rails-app.md Updated version examples to use placeholders, added clean-git prerequisite, and documented PORT override with bundle exec prefix
packages/react-on-rails-pro-node-renderer/src/worker/vm.ts Fixed race condition in buildVM by moving promise cleanup from try/finally to .finally() on stored promise
react_on_rails/lib/generators/react_on_rails/base_generator.rb Added configure_private_output_path_in_shakapacker method to auto-configure private_output_path: ssr-generated for Shakapacker 9+
react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb Added specs verifying private_output_path configuration for fresh and pre-existing Shakapacker installs
react_on_rails_pro/docs/installation.md Updated version examples to use placeholders, added clean-git prerequisite, post-install validation steps, and PORT override example

Last reviewed commit: e72bebd

@claude
Copy link

claude bot commented Feb 26, 2026

PR Review: Improve fresh Pro install docs and generator defaults

Overall this is a solid improvement — replacing hardcoded version numbers with explicit placeholders, adding the clean-git prerequisite, and auto-configuring private_output_path reduce a real pain point for new installers. The vm.ts concurrency fix is also correct and well-explained.

A few issues worth addressing:

configure_private_output_path_in_shakapacker bypasses Thor generator DSL

The new method uses File.read / File.write directly (lines 377, 398), while the adjacent configure_precompile_hook_in_shakapacker and configure_rspack_in_shakapacker use Thor's gsub_file for writes. Using raw File.write bypasses --pretend mode (dry-run), --force behavior, and the generator's change logging, creating an inconsistency. Consider splitting the two-step logic into separate gsub_file + insert_into_file calls, matching the pattern used by the other configure methods in the same file.

Already-configured regex misses null / empty YAML values

The guard at line 380 (return if content.match?(/^\sprivate_output_path:\s\S+/)) won't match private_output_path: (empty value) or private_output_path: ~ (YAML null). If a user previously set one of those values, the generator will attempt to insert a second private_output_path key, producing invalid (ambiguous) YAML. The check should also cover the null/no-value form, e.g. content.match?(/^\s*private_output_path:/).

Recovery test in vm.test.ts passes trivially when VM already exists

The new test at line 545 doesn't call resetVM() before running. If serverBundlePath is already present in vmContexts from an earlier test in the suite, buildVM(serverBundlePath) hits the early-return (line 189 – 'return Promise.resolve(true)') and the hasVMContextForBundle assertion passes without exercising the recovery path. Adding a resetVM() at the top of the test would make the coverage meaningful.

Minor: Procfile.dev-static-assets uses a different process name

The other two Procfiles use 'rails:' as the process name; Procfile.dev-static-assets uses 'web:'. That inconsistency predates this PR but is worth a follow-up since it affects how process names appear in overmind/foreman output.

@github-actions
Copy link
Contributor

size-limit report 📦

Path Size
react-on-rails/client bundled (gzip) 62.5 KB (0%)
react-on-rails/client bundled (gzip) (time) 62.5 KB (0%)
react-on-rails/client bundled (brotli) 53.71 KB (0%)
react-on-rails/client bundled (brotli) (time) 53.71 KB (0%)
react-on-rails-pro/client bundled (gzip) 63.5 KB (0%)
react-on-rails-pro/client bundled (gzip) (time) 63.5 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.16 KB (0%)
registerServerComponent/client bundled (gzip) (time) 127.16 KB (0%)
registerServerComponent/client bundled (brotli) 61.54 KB (0%)
registerServerComponent/client bundled (brotli) (time) 61.54 KB (0%)
wrapServerComponentRenderer/client bundled (gzip) 121.69 KB (0%)
wrapServerComponentRenderer/client bundled (gzip) (time) 121.69 KB (0%)
wrapServerComponentRenderer/client bundled (brotli) 56.63 KB (0%)
wrapServerComponentRenderer/client bundled (brotli) (time) 56.63 KB (0%)


return if updated_content == content

File.write(shakapacker_config_path, updated_content)
Copy link

Choose a reason for hiding this comment

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

Using File.write here bypasses Thor's generator DSL. configure_precompile_hook_in_shakapacker (line 363) and configure_rspack_in_shakapacker (line 333) both use gsub_file for their writes, which respects --pretend (dry-run), --force, and logs the change in generator output.

Consider restructuring as two separate gsub_file / insert_into_file calls:

# Step 1: try to uncomment the placeholder line
gsub_file shakapacker_config_path,
          /^(\s*)#\s*private_output_path:\s*.*$/,
          "\\1private_output_path: ssr-generated"

# Step 2: if the placeholder wasn't there, insert after public_output_path
unless File.read(shakapacker_config_path).match?(/^\s*private_output_path: ssr-generated/)
  insert_into_file shakapacker_config_path,
                   "\\1private_output_path: ssr-generated\n",
                   after: /^(\s*)public_output_path:.*\n/
end

This keeps the logic consistent with the rest of the generator.

content = File.read(shakapacker_config_path)

# Already configured? Do nothing.
return if content.match?(/^\s*private_output_path:\s*\S+/)
Copy link

Choose a reason for hiding this comment

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

The \S+ quantifier requires at least one non-whitespace character after the colon, so this guard won't fire for private_output_path: (empty) or private_output_path: ~ (YAML null). A user who previously added the key with a null value would get a duplicate key inserted by the fallback branch, producing ambiguous YAML.

Suggest broadening the guard to cover any existing key (regardless of value):

return if content.match?(/^\s*private_output_path:/)

If you need to distinguish "set to a real value" from "set to null / empty", handle that case explicitly rather than silently re-inserting.

expect(vmContext1).toBe(vmContext2);
});

test('buildVM recovers after failure for nonexistent bundle', async () => {
Copy link

Choose a reason for hiding this comment

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

This test doesn't call resetVM() before running. If serverBundlePath is already present in vmContexts from a prior test in the suite, buildVM(serverBundlePath) returns immediately via the early-exit branch (return Promise.resolve(true) on line ~192) and the hasVMContextForBundle assertion passes trivially — without actually verifying that the cleanup after failure enabled a fresh build.

Adding resetVM() at the start ensures the test exercises the actual recovery path:

test('buildVM recovers after failure for nonexistent bundle', async () => {
  resetVM(); // start with a clean slate
  const config = getConfig();
  config.supportModules = true;
  config.stubTimers = false;

  const nonExistentPath = path.resolve(__dirname, './tmp/nonexistent-bundle.js');

  // First call should fail because the bundle file does not exist
  await expect(buildVM(nonExistentPath)).rejects.toThrow();

  // After failure, the vmCreationPromises map should be cleaned up,
  // allowing a retry with the correct path to succeed
  await buildVM(serverBundlePath);
  expect(hasVMContextForBundle(serverBundlePath)).toBeTruthy();
});

// Remove from the map after the promise settles. Using .finally() on the
// stored promise guarantees this runs after set(), unlike a try/finally
// inside the IIFE which can run synchronously before set() on sync throws.
void vmCreationPromise
Copy link

Choose a reason for hiding this comment

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

The .catch(() => {}).finally(...) chain is the right pattern here — the .catch is necessary to prevent an unhandled rejection on the void-ed chain (since .finally() on a rejected promise propagates the rejection through the tail). The comment could be a touch more explicit about this:

// Suppress the rejection on this internal chain so it doesn't surface as an
// unhandled rejection when the outer `void` discards the tail promise.
// The original `vmCreationPromise` is still returned to callers and will
// reject normally.
void vmCreationPromise
  .catch(() => {})
  .finally(() => {
    vmCreationPromises.delete(filePath);
  });

No functional change needed — just a suggestion for clarity.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
docs/getting-started/installation-into-an-existing-rails-app.md (1)

24-29: Non-descriptive link text "here".

The link on line 26 uses "here" which isn't descriptive per accessibility best practices. Consider using descriptive text.

📝 Suggested improvement
-2. Run the following command to install Shakapacker with React. Note, if you are using an older version of
-   Rails than 5.1, you'll need to install Webpacker with React per the instructions
-   [here](https://github.com/rails/webpacker).
+2. Run the following command to install Shakapacker with React. Note, if you are using an older version of
+   Rails than 5.1, you'll need to install Webpacker with React per the
+   [Webpacker documentation](https://github.com/rails/webpacker).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/getting-started/installation-into-an-existing-rails-app.md` around lines
24 - 29, Replace the non-descriptive link text "here" with a descriptive label
so it’s accessible; in the markdown near the command example (the paragraph
mentioning older Rails than 5.1 and the link whose text is "here"), change the
link text to something meaningful like "Webpacker README on GitHub" or "Rails
Webpacker repository" while keeping the same URL, so the link reads e.g.
[Webpacker README on GitHub](https://github.com/rails/webpacker).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@docs/getting-started/installation-into-an-existing-rails-app.md`:
- Around line 24-29: Replace the non-descriptive link text "here" with a
descriptive label so it’s accessible; in the markdown near the command example
(the paragraph mentioning older Rails than 5.1 and the link whose text is
"here"), change the link text to something meaningful like "Webpacker README on
GitHub" or "Rails Webpacker repository" while keeping the same URL, so the link
reads e.g. [Webpacker README on GitHub](https://github.com/rails/webpacker).

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ba18e0d and e72bebd.

📒 Files selected for processing (9)
  • docs/getting-started/installation-into-an-existing-rails-app.md
  • packages/react-on-rails-pro-node-renderer/src/worker/vm.ts
  • packages/react-on-rails-pro-node-renderer/tests/vm.test.ts
  • react_on_rails/lib/generators/react_on_rails/base_generator.rb
  • react_on_rails/lib/generators/react_on_rails/templates/base/base/Procfile.dev
  • react_on_rails/lib/generators/react_on_rails/templates/base/base/Procfile.dev-prod-assets
  • react_on_rails/lib/generators/react_on_rails/templates/base/base/Procfile.dev-static-assets
  • react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb
  • react_on_rails_pro/docs/installation.md

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is ON, but it could not run because Privacy Mode (Legacy) is turned on. To enable Bugbot Autofix, switch your privacy mode in the Cursor dashboard.

# Procfile for development
# You can run these commands in separate shells
rails: bundle exec rails s -p 3000
rails: bundle exec rails s -p ${PORT:-3000}
Copy link

Choose a reason for hiding this comment

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

Procfile PORT default never used via process managers

Medium Severity

The ${PORT:-3000} default is effectively dead code when running through bin/dev. Both foreman and overmind assign their own PORT to each child process (defaulting to base 5000), so the :-3000 fallback is never reached. This changes the default Rails port from 3000 to 5000. The bin/dev Ruby script doesn't set ENV["PORT"] before invoking the process manager (unlike the standard Rails 7.1+ bin/dev pattern of export PORT="${PORT:-3000}"), so all downstream references to port 3000 — docs step 7, procfile_port, and print_server_info — become incorrect.

Additional Locations (2)

Fix in Cursor Fix in Web

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