Pin npm shakapacker version to match gem in example generation#2558
Pin npm shakapacker version to match gem in example generation#2558
Conversation
shakapacker:install adds the npm package with a semver caret range (e.g., ^9.5.0), which allows npm install to resolve a newer minor version. When a new shakapacker release is published to npm before or independently of the gem, this causes Shakapacker's version consistency check to fail (e.g., gem 9.5.0 vs npm 9.6.0). Fix by reading the exact gem version from the example app's Gemfile.lock and pinning the npm package to match before running npm install. Co-Authored-By: Claude Opus 4.6 <[email protected]>
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
WalkthroughAdds two Rake helper methods in the Changes
Sequence Diagram(s)sequenceDiagram
participant Dev as Developer
participant Rake as Rake Task (shakapacker_examples)
participant FS as File System (Gemfile.lock, package.json)
participant NPM as npm
Dev->>Rake: invoke example generation
Rake->>FS: read Gemfile.lock (extract shakapacker version)
FS-->>Rake: gem version (or nil)
alt gem version present
Rake->>FS: read package.json
FS-->>Rake: package.json contents
Rake->>Rake: update dependencies/devDependencies shakapacker => gem version
Rake->>FS: write package.json (if changed)
end
Rake->>NPM: run npm install (example dir)
NPM-->>Dev: installs complete
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 39fbbb66db
ℹ️ 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".
| deps = package_json["dependencies"] || {} | ||
| current = deps["shakapacker"] | ||
| return unless current && current != gem_version |
There was a problem hiding this comment.
Pin shakapacker from devDependencies too
pin_shakapacker_npm_version only checks package_json["dependencies"], so it silently skips pinning when shakapacker is declared under devDependencies (which is how this repo’s app package.json files are structured). In that case the caret range remains unchanged, npm install can still pull a newer minor npm package than the gem version, and the same gem/npm version mismatch this commit is trying to prevent can still occur.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Addressed in 6ac88c0. pin_shakapacker_npm_version now iterates over both dependencies and devDependencies sections.
Greptile SummaryThis PR adds a Key observations:
Confidence Score: 3/5
Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[shakapacker:install\nadds '^X.Y.Z' to package.json] --> B{example_type.pinned_react_version?}
B -- yes --> C[apply_react_version\nwrites package.json\nReact deps only]
C --> D[bundle_install_in]
D --> E["npm install\n--legacy-peer-deps --install-links\n⚠️ shakapacker still '^X.Y.Z'"]
E --> F[shakapacker:binstubs\n⚠️ gem/npm mismatch risk]
B -- no --> G[pin_shakapacker_npm_version\nread Gemfile.lock version\nwrite exact version to package.json]
G --> H{Gemfile.lock match\nX.Y.Z regex?}
H -- matched --> I[overwrite package.json\nshakapacker = exact version]
H -- no match\npre-release / missing --> J[silently skips pinning\n⚠️ mismatch risk]
I --> K["npm install --install-links\nnpm resolves exact version ✅"]
J --> K
|
| lockfile = File.join(dir, "Gemfile.lock") | ||
| return unless File.exist?(lockfile) | ||
|
|
||
| match = File.read(lockfile).match(/^\s+shakapacker\s+\((\d+\.\d+\.\d+)\)/) |
There was a problem hiding this comment.
Regex skips pre-release gem versions
The pattern \d+\.\d+\.\d+ only matches standard three-part semver (9.5.0). If shakapacker ever ships a pre-release gem (e.g., 9.6.0.beta1 or 9.6.0.rc1), the regex won't match, gem_version will be nil, and the function will silently skip pinning — re-exposing the same mismatch the PR is trying to prevent.
A broader pattern that also captures pre-release suffixes would be safer:
| match = File.read(lockfile).match(/^\s+shakapacker\s+\((\d+\.\d+\.\d+)\)/) | |
| match = File.read(lockfile).match(/^\s+shakapacker\s+\((\d+\.\d+\.\d+[^)]*)\)/) |
There was a problem hiding this comment.
Addressed in 6ac88c0. Changed regex to ([^)]+) which captures the full version string including prerelease segments like 9.6.0.beta.0.
|
The approach is sound - reading the gem version from Gemfile.lock and pinning the npm entry before npm install is the right way to prevent semver range resolution from causing version drift. Inconsistent error handling: Detached hash when dependencies is absent:
Overall: Small, focused, correct. The Gemfile.lock regex is right and the exact-version pin strategy is the right call. |
| package_json_path = File.join(dir, "package.json") | ||
| return unless File.exist?(package_json_path) | ||
|
|
||
| package_json = JSON.parse(File.read(package_json_path)) |
There was a problem hiding this comment.
Unlike update_package_json_for_react_version which wraps this in begin/rescue JSON::ParserError, this method has no error handling. Since the file was just written by shakapacker:install it should be valid JSON, but for consistency consider adding the same rescue pattern.
There was a problem hiding this comment.
Addressed in 6ac88c0. Added rescue JSON::ParserError with descriptive error message, matching the pattern in update_package_json_for_react_version.
| return unless File.exist?(package_json_path) | ||
|
|
||
| package_json = JSON.parse(File.read(package_json_path)) | ||
| deps = package_json["dependencies"] || {} |
There was a problem hiding this comment.
If package_json["dependencies"] is nil, deps is a detached {} — mutations to it won't propagate to package_json. The guard on line 31 makes this safe in practice (nil deps means nil current, so we return early), but the intent is clearer written as:
| deps = package_json["dependencies"] || {} | |
| deps = package_json["dependencies"] | |
| return unless deps&.key?("shakapacker") | |
| current = deps["shakapacker"] | |
| return if current == gem_version |
There was a problem hiding this comment.
Actionable comments posted: 1
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/rakelib/shakapacker_examples.rake (1)
137-160:⚠️ Potential issue | 🟠 MajorPin
shakapackerbefore bothnpm installpaths.Every example goes through
bundle exec rake shakapacker:installbefore this branch, but only theelsepath rewritespackage.jsonto exact versions. Thepinned_react_version?path still reaches thenpm installwith the original caret range, soshakapacker:binstubscan hit the same gem/npm version check failure that this PR protects against in the else branch.Suggested fix
+ # Keep the npm package aligned with the installed gem before any npm install. + pin_shakapacker_npm_version(example_type.dir) + # Apply specific React version for compatibility testing examples if example_type.pinned_react_version? apply_react_version(example_type.dir, example_type.react_version_string) # Re-run bundle install to ensure dependencies are resolved correctly bundle_install_in(example_type.dir) @@ -148,9 +151,6 @@ # Use --install-links to copy file: dependencies instead of symlinking, # preventing duplicate React instances from webpack resolving through symlinks sh_in_dir(example_type.dir, "npm install --legacy-peer-deps --install-links") else - # Pin the npm shakapacker version to exactly match the installed gem version. - # shakapacker:install may add "^X.Y.Z" to package.json, which allows npm to - # resolve a newer minor version (e.g., 9.6.0 when gem is 9.5.0), causing - # Shakapacker's gem/npm version consistency check to fail. - pin_shakapacker_npm_version(example_type.dir) # Use --install-links to copy file: dependencies instead of symlinking, # preventing duplicate React instances from webpack resolving through symlinks sh_in_dir(example_type.dir, "npm install --install-links")🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@react_on_rails/rakelib/shakapacker_examples.rake` around lines 137 - 160, In the pinned_react_version? branch you still run npm install with the original caret range, so call pin_shakapacker_npm_version(example_type.dir) before any npm install there (i.e., after bundle_install_in and before sh_in_dir that runs "npm install --legacy-peer-deps --install-links") so package.json is rewritten to exact shakapacker versions (and do this before unbundled_sh_in_dir("bundle exec rake shakapacker:binstubs") as well).
🤖 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/rakelib/shakapacker_examples.rake`:
- Around line 39-44: The current regex in shakapacker_gem_version_from_lockfile
only captures strict x.y.z and misses prerelease labels; update the match on
File.read(lockfile).match(...) to capture full version tokens including
prerelease segments (e.g. 9.6.0.beta.0 or 8.0.0.rc.2) — for example, match the
entire parenthesized version string instead of only digits (keep using the same
function name shakapacker_gem_version_from_lockfile and the existing match
variable and match&.[](1) return style) so prerelease versions are returned
instead of nil.
---
Outside diff comments:
In `@react_on_rails/rakelib/shakapacker_examples.rake`:
- Around line 137-160: In the pinned_react_version? branch you still run npm
install with the original caret range, so call
pin_shakapacker_npm_version(example_type.dir) before any npm install there
(i.e., after bundle_install_in and before sh_in_dir that runs "npm install
--legacy-peer-deps --install-links") so package.json is rewritten to exact
shakapacker versions (and do this before unbundled_sh_in_dir("bundle exec rake
shakapacker:binstubs") as well).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 8494674c-9bf8-4d63-947a-a885fea153b3
📒 Files selected for processing (1)
react_on_rails/rakelib/shakapacker_examples.rake
Document all the places where the shakapacker version is pinned (gem and npm, across OSS and Pro) so contributors know what to update when bumping to a new release. Co-Authored-By: Claude Opus 4.6 <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d75c22e7c1
ℹ️ 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".
| lockfile = File.join(dir, "Gemfile.lock") | ||
| return unless File.exist?(lockfile) | ||
|
|
||
| match = File.read(lockfile).match(/^\s+shakapacker\s+\((\d+\.\d+\.\d+)\)/) |
There was a problem hiding this comment.
Accept prerelease gem versions when pinning npm shakapacker
shakapacker_gem_version_from_lockfile only matches versions shaped like X.Y.Z, so it returns nil for valid lockfile entries such as shakapacker (9.6.0.rc1) or other prerelease/build forms. In that case pin_shakapacker_npm_version silently skips pinning, and npm install can still resolve a different package version than the gem, recreating the mismatch this change is meant to prevent when testing release candidates.
Useful? React with 👍 / 👎.
|
Code review notes (see inline comments for specifics): (1) Regex in shakapacker_gem_version_from_lockfile only matches X.Y.Z - pre-release versions like 9.6.0.rc1 would return nil, silently skipping the pin. (2) pin_shakapacker_npm_version is missing rescue JSON::ParserError unlike update_package_json_for_react_version. (3) The pinned_react_version? branch runs npm install without calling pin_shakapacker_npm_version - if intentionally excluded, document why. Minor: JSON.pretty_generate reformats all keys in package.json; CONTRIBUTING.md section should note react-pinned examples are not covered by this helper. |
| lockfile = File.join(dir, "Gemfile.lock") | ||
| return unless File.exist?(lockfile) | ||
|
|
||
| match = File.read(lockfile).match(/^\s+shakapacker\s+\((\d+\.\d+\.\d+)\)/) |
There was a problem hiding this comment.
The regex \d+\.\d+\.\d+ only matches three-part numeric versions. A pre-release gem version like 9.6.0.rc1 or 10.0.0.beta.1 would not match, causing this method to return nil and silently skip the version pin.
Consider broadening the capture group to also accept pre-release segments:
| match = File.read(lockfile).match(/^\s+shakapacker\s+\((\d+\.\d+\.\d+)\)/) | |
| match = File.read(lockfile).match(/^\s+shakapacker\s+\((\d+\.\d+\.\d+[^\)]*)\)/) |
This captures everything up to the closing ), preserving e.g. 9.6.0.rc1 correctly so the exact gem version can be pinned in package.json.
| package_json_path = File.join(dir, "package.json") | ||
| return unless File.exist?(package_json_path) | ||
|
|
||
| package_json = JSON.parse(File.read(package_json_path)) |
There was a problem hiding this comment.
pin_shakapacker_npm_version calls JSON.parse without error handling, while the nearby update_package_json_for_react_version wraps the same call in a rescue JSON::ParserError block (lines 59–63) that prints a descriptive message before re-raising.
A malformed package.json here would produce a cryptic Ruby backtrace instead of a useful error. Consider making these consistent:
| package_json = JSON.parse(File.read(package_json_path)) | |
| begin | |
| package_json = JSON.parse(File.read(package_json_path)) | |
| rescue JSON::ParserError => e | |
| puts " ERROR: Failed to parse #{package_json_path}: #{e.message}" | |
| raise | |
| end |
| # shakapacker:install may add "^X.Y.Z" to package.json, which allows npm to | ||
| # resolve a newer minor version (e.g., 9.6.0 when gem is 9.5.0), causing | ||
| # Shakapacker's gem/npm version consistency check to fail. | ||
| pin_shakapacker_npm_version(example_type.dir) |
There was a problem hiding this comment.
The pinned_react_version? branch (above this else) runs npm install without calling pin_shakapacker_npm_version first. The comment on lines 141–143 explicitly notes that the version-matching concern applies there too ("to ensure the npm shakapacker version matches the gem version").
If those examples are intentionally excluded because they lock an older shakapacker major version that prevents drift (e.g., ^8.2.0 with no published 8.3.x), please add a comment explaining the rationale, for example:
# Note: pinned_react_version? examples use an older shakapacker major (8.2.x)
# that has no newer published minor, so npm version drift is not a concern there.Otherwise, consider calling pin_shakapacker_npm_version in that branch as well.
There was a problem hiding this comment.
Addressed in 6ac88c0. Moved pin_shakapacker_npm_version call into both branches (before each npm install) so both pinned_react_version? and default paths get pinned.
- Check both dependencies and devDependencies when pinning shakapacker - Broaden regex to handle prerelease gem versions (e.g., 9.6.0.beta.0) - Add JSON::ParserError rescue for consistency with nearby code - Move pin call before both npm install branches so pinned_react_version? examples also get pinned Co-Authored-By: Claude Opus 4.6 <[email protected]>
Review: Pin npm shakapacker version to match gem in example generationThe approach is sound and directly addresses the version drift problem. A couple of things worth noting: Regex fragility in shakapacker_gem_version_from_lockfile The pattern uses JSON.pretty_generate silently reformats the whole file When changed is true, the entire package.json is rewritten with JSON.pretty_generate 2-space indentation. If the generated file used different spacing the format changes beyond the version pin. Harmless for ephemeral gen-examples/ apps, but worth knowing when debugging. Minor: rubocop disable The Metrics/CyclomaticComplexity disable on pin_shakapacker_npm_version is understandable, but extracting a small pin_section helper would lower complexity naturally and remove the need for it. Overall The fix is correct and well-placed — version is pinned after bundle install (Gemfile.lock has the resolved gem version) and before any npm install call. The CONTRIBUTING.md section is a welcome addition. The regex robustness issue is the only change worth considering before merging. |
| lockfile = File.join(dir, "Gemfile.lock") | ||
| return unless File.exist?(lockfile) | ||
|
|
||
| match = File.read(lockfile).match(/^\s+shakapacker\s+\(([^)]+)\)/) |
There was a problem hiding this comment.
The pattern [^)]+ will capture any text inside the parens, including semver constraint syntax. A Gemfile.lock DEPENDENCIES section can contain shakapacker (~> 9.0), which this regex would capture as ~> 9.0 if it appeared before the resolved version in the specs section. Locking it to lines where the captured text starts with a digit prevents that:
| match = File.read(lockfile).match(/^\s+shakapacker\s+\(([^)]+)\)/) | |
| match = File.read(lockfile).match(/^\s{4,}shakapacker \((\d[^)]*)\)/) |
This also tightens the leading-whitespace requirement (\s{4,}) so it matches gem entries in the specs block and not shallower indented section headers.
|
|
||
| # Pins the shakapacker npm package version to exactly match the installed gem version. | ||
| # Prevents semver range resolution (e.g., ^9.5.0 -> 9.6.0) from causing version mismatches. | ||
| def pin_shakapacker_npm_version(dir) # rubocop:disable Metrics/CyclomaticComplexity |
There was a problem hiding this comment.
The cyclomatic complexity here is legitimately borderline, but it could be reduced — and the disable removed — by extracting the inner loop body into a small helper:
def pin_shakapacker_in_section(deps, section_name, gem_version)
return false unless deps&.key?("shakapacker")
return false if deps["shakapacker"] == gem_version
puts " Pinning npm shakapacker in #{section_name} from #{deps['shakapacker']} to exact #{gem_version}"
deps["shakapacker"] = gem_version
true
endThen pin_shakapacker_npm_version just calls pin_shakapacker_in_section for each section and checks if any returned true.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6ac88c044b
ℹ️ 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".
| lockfile = File.join(dir, "Gemfile.lock") | ||
| return unless File.exist?(lockfile) | ||
|
|
||
| match = File.read(lockfile).match(/^\s+shakapacker\s+\(([^)]+)\)/) |
There was a problem hiding this comment.
Parse locked shakapacker version from specs section
shakapacker_gem_version_from_lockfile takes the first shakapacker (...) match in Gemfile.lock, but generated lockfiles include dependency requirement lines (for example, react_on_rails lists shakapacker (>= 6.0)) before the actual installed spec entry. This means gem_version can become a constraint like >= 6.0 instead of the resolved version (e.g., 9.5.0), so pin_shakapacker_npm_version writes a range back to package.json and still allows npm to resolve a mismatched version.
Useful? React with 👍 / 👎.
Pin shakapacker after apply_react_version in the pinned_react_version? branch to avoid the pin being overwritten by package.json modifications. Add spec for pin_shakapacker_npm_version behavior. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Code ReviewThe approach here is sound - pinning the npm shakapacker version to the installed gem version after shakapacker:install is the right fix for the CI drift issue. Potential Bug 1: Pre-release version format mismatch Potential Bug 2: Regex fragility — could match DEPENDENCIES section Missing test: non-pinned React (else) branch Minor nits
|
| lockfile = File.join(dir, "Gemfile.lock") | ||
| return unless File.exist?(lockfile) | ||
|
|
||
| match = File.read(lockfile).match(/^\s+shakapacker\s+\(([^)]+)\)/) |
There was a problem hiding this comment.
The regex \s+shakapacker\s+\(([^)]+)\) is fragile in two ways: (1) it matches the DEPENDENCIES section too, which may contain shakapacker (>= 6.0) — the first match wins, so if DEPENDENCIES somehow appeared first the captured value would be a constraint, not an exact version; (2) it does not guard against similarly-named gems (e.g. a hypothetical shakapacker-pro). Anchoring with \bshakapacker\b and/or scoping the scan to the GEM specs block would make this more reliable.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4c5b384c6d
ℹ️ 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".
| lockfile = File.join(dir, "Gemfile.lock") | ||
| return unless File.exist?(lockfile) | ||
|
|
||
| match = File.read(lockfile).match(/^\s+shakapacker\s+\(([^)]+)\)/) |
There was a problem hiding this comment.
Read shakapacker version from locked gem spec
shakapacker_gem_version_from_lockfile takes the first shakapacker (...) match anywhere in Gemfile.lock, which can be a dependency constraint (for example shakapacker (>= 6.0)) listed under another gem before the actual locked spec entry. In that case pin_shakapacker_npm_version writes a range back to package.json instead of the resolved gem version, so npm can still resolve a different minor version and the gem/npm mismatch this change is meant to prevent can still happen.
Useful? React with 👍 / 👎.
| end | ||
| end | ||
|
|
||
| it "reads prerelease shakapacker gem versions from Gemfile.lock" do |
There was a problem hiding this comment.
This test verifies that the pre-release version string is read correctly, but there is no follow-on test confirming that pin_shakapacker_npm_version actually writes 9.6.0.beta.0 to package.json and that npm would accept it. Ruby gem pre-release versions use dots (9.6.0.beta.0) while npm expects hyphens (9.6.0-beta.0). If a pre-release gem is ever installed, the pinned npm version string would be invalid and npm install would fail.
|
|
||
| Rake::Task["shakapacker_examples:gen_example_app"].invoke | ||
| end | ||
| end |
There was a problem hiding this comment.
There is no corresponding test for the else branch (non-pinned React examples) that verifies pin_shakapacker_npm_version is invoked before npm install --install-links. Consider adding a sibling describe block with pinned_react_version?: false to cover that path.
## Summary - Fix CI example generation failure when a new shakapacker version is published to npm before/independently of the gem - `shakapacker:install` adds the npm package with a caret range (e.g., `^9.5.0`), so `npm install` can resolve to a newer minor version (e.g., 9.6.0) while the gem remains at 9.5.0 - Added `pin_shakapacker_npm_version` that reads the exact gem version from the example app's `Gemfile.lock` and pins the npm `package.json` entry to match Fixes the `examples (3.4, latest)` CI failure seen on PR #2516 where npm shakapacker resolved to 9.6.0 while the gem was 9.5.0. ## Test plan - [ ] CI `examples (3.4, latest)` job passes - [ ] Verify that generated example apps have matching gem and npm shakapacker versions 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Small, localized change to example-generation rake tasks that only adjusts dependency pinning and should not affect runtime application behavior. > > **Overview** > Prevents CI/example app generation failures caused by `shakapacker` gem/npm version drift by **pinning the npm `shakapacker` dependency to the exact gem version** after `shakapacker:install`. > > Adds helpers to read the installed Shakapacker gem version from `Gemfile.lock` and rewrite `package.json` when it contains a semver range (e.g., `^X.Y.Z`) before running `npm install` for non-react-pinned examples. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 39fbbb6. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Automatically syncs the npm Shakapacker dependency to the installed gem version during example app generation to prevent mismatches. * **Bug Fixes** * Improved error handling when reading package metadata and only rewrites files when changes occur. * **Documentation** * Added CONTRIBUTING guidance on pinning and updating the Shakapacker version and regenerating lockfiles. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 4.6 <[email protected]>
## Summary - Add changelog entries for 6 user-visible PRs merged since v16.4.0.rc.6 that were missing from `[Unreleased]` - Update existing #2561 entry to include #2568 contributor credit ### New entries added | Section | PR | Description | |---|---|---| | Added | #2539 | Environment-variable-driven ports in Procfile templates | | Fixed | #2417 | Rspack generator config path fix | | Fixed | #2419 | Precompile hook load-based execution fix | | Fixed | #2577 | `create-react-on-rails-app` validation improvements | | Pro Fixed | #2416 | StreamResponse status fallback fix | | Pro Fixed | #2566 | Empty-string license plan mismatch fix | ### Skipped PRs (not user-visible) Docs (#2406, #2414, #2479, #2494, #2518, #2537, #2544), CI/internal (#2533, #2547, #2555, #2557, #2558, #2564), dependabot (#2387, #2541), dev dependencies (#2559, #2569, #2573). ## Test plan - [ ] Verify changelog formatting matches existing entries - [ ] Verify all user-visible PRs since v16.4.0.rc.6 are covered 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Documentation-only changelog updates with no runtime or build behavior changes. > > **Overview** > Updates `CHANGELOG.md`’s **[Unreleased]** section to include previously missing user-facing entries: Procfile templates now support env-driven ports, several generator/`bin/dev` precompile-hook and rspack-path fixes are documented, and `create-react-on-rails-app` validation improvements are noted. > > Also adds two Pro fix entries (StreamResponse status fallback and license plan empty-string validation) and updates the existing `bin/dev` precompile-hook entry to credit an additional PR/contributor. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit e75d2b5. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: Claude Opus 4.6 <[email protected]>
Summary
shakapacker:installadds the npm package with a caret range (e.g.,^9.5.0), sonpm installcan resolve to a newer minor version (e.g., 9.6.0) while the gem remains at 9.5.0pin_shakapacker_npm_versionthat reads the exact gem version from the example app'sGemfile.lockand pins the npmpackage.jsonentry to matchFixes the
examples (3.4, latest)CI failure seen on PR #2516 where npm shakapacker resolved to 9.6.0 while the gem was 9.5.0.Test plan
examples (3.4, latest)job passes🤖 Generated with Claude Code
Note
Low Risk
Small, localized change to example-generation rake tasks that only adjusts dependency pinning and should not affect runtime application behavior.
Overview
Prevents CI/example app generation failures caused by
shakapackergem/npm version drift by pinning the npmshakapackerdependency to the exact gem version aftershakapacker:install.Adds helpers to read the installed Shakapacker gem version from
Gemfile.lockand rewritepackage.jsonwhen it contains a semver range (e.g.,^X.Y.Z) before runningnpm installfor non-react-pinned examples.Written by Cursor Bugbot for commit 39fbbb6. Configure here.
Summary by CodeRabbit