Skip to content

Restructure supplemental package dependencies (#1131) - #1133

Merged
justin808 merged 13 commits into
mainfrom
jg-conductor/issue-1131-review
May 24, 2026
Merged

Restructure supplemental package dependencies (#1131)#1133
justin808 merged 13 commits into
mainfrom
jg-conductor/issue-1131-review

Conversation

@justin808

@justin808 justin808 commented May 23, 2026

Copy link
Copy Markdown
Member

Summary

Addresses #1131 (G-Rath) on the v10.1.0-rc.1 supplemental packages. Three structural changes:

  1. Bundler singletons → required peer dependencies. shakapacker-webpack moves webpack, webpack-cli, and webpack-assets-manifest from dependencies to required peerDependencies. shakapacker-rspack does the same for @rspack/core, @rspack/cli, and rspack-manifest-plugin. Eliminates the silent duplicate-bundler failure mode where an app or transitive dep pinning a different bundler version could end up with two webpacks in the tree (and silently broken instanceof checks in plugins/loaders).

  2. ~^ for all non-shakapacker deps. Tilde locked the supplemental to the upstream patch range, forcing a coordinated release for every webpack/rspack/loader minor. Caret lets users pick up compatible upstream releases without waiting. shakapacker itself stays tilde-pinned because the wrapper imports core's internal package/config subpath and needs the matching minor.

  3. terser-webpack-plugin → direct dependency of shakapacker-webpack. package/optimization/webpack.ts does requireOrError("terser-webpack-plugin") for the default minimizer, so it's always required for any production build using shakapacker's defaults. Moves from optional peer (where npm doesn't warn when missing) to direct dep.

Plus one bonus cleanup raised in the issue thread: the supplemental peer ranges were tighter than main shakapacker's peer ranges (e.g., webpack-dev-server: ~5.2.3 vs main's ^5.2.2). Widened them to match — the supplemental should never narrow what bare core accepts.

Review confirmation: @rspack/plugin-react-refresh now intentionally uses ^1.0.0 || ^2.0.0 in both core and the rspack supplemental. Rspack 2.x is stable, so this excludes 2.0.0-* prereleases instead of preserving the older ^2.0.0-0 prerelease-compatible range.

Behavior on each package manager

Package manager Behavior after this change
npm 7+ Auto-installs required peers; wrapper-only install works
pnpm Strict app-level imports still need explicit deps; see migration guide
yarn 2+ (Berry) — PnP mode Strict app-level imports still need explicit deps; see migration guide
yarn 2+ (Berry) — nodeLinker: node-modules Hoisted node_modules mode behaves like npm 7+
yarn 1 (classic) Surfaces peer warnings listing missing packages

The Rails shakapacker:install task continues to write all required deps explicitly into the user's package.json (it doesn't use the supplemental packages), so installed apps still get true one-command UX on every package manager.

What this PR doesn't do

  • No installer code changes. The Rails installer (lib/install/template.rb) already provides true one-command UX by writing core + all peers explicitly. Switching the installer default to use supplemental packages is a behavior change beyond Dependency feedback for v10.1.0 #1131's scope — PR RFC: Tighten dependencies for Shakapacker v11 #1096 explicitly kept supplemental adoption opt-in for the v10.x line.
  • No changes to main shakapacker's package.json. It already follows these conventions (webpack as a peer with ^5.101.0, terser-webpack-plugin as a peer with ^5.3.1).
  • No v11 changes. This PR only touches v10.1 RC shape so the GA ships with the right dependency topology. The v11 roadmap (remove bundler peers from core) remains gated on bundler-specific runtime imports being fully owned by the supplemental packages — see docs/dependency-strategy.md.

Files changed

  • packages/shakapacker-webpack/package.json — restructure per recommendations
  • packages/shakapacker-rspack/package.json — restructure per recommendations
  • test/packages/package-metadata.test.js (new) — locks in the contract (singletons are required peers, terser-webpack-plugin is direct dep, no ~ outside shakapacker, supplemental peer ranges don't narrow main's)
  • docs/dependency-strategy.md — rewrite summary, per-package tables, Version Pinning Philosophy, examples, Resolved Decisions
  • docs/migration/v10.1-supplemental-packages.md — add PM support table, split "After" examples by PM
  • docs/blog/2026-05-10-shakapacker-10-1-supplemental-packages.md — revise 30-second pitch and What Changed
  • packages/shakapacker-{webpack,rspack}/README.md — rewrite Install sections with auto-peer-install vs yarn-1 commands
  • CHANGELOG.md — Unreleased entry documenting the change

Test plan

  • yarn jest test/packages/package-metadata.test.js — 12 new tests pass (asserts the contract above)
  • yarn jest — full JS suite: 514 tests, 53 suites, all pass
  • yarn lint — clean
  • yarn prettier --check — clean (after autofix)
  • Manual install smoke tests on npm 7+, pnpm, yarn 1, yarn 2+ — to be run on the published rc.2 tarball before promoting to GA

Fixes #1131


Note

Medium Risk
Medium risk because it changes published npm dependency topology for shakapacker-webpack/shakapacker-rspack, which can affect install/resolution behavior across package managers (npm vs pnpm/Yarn PnP) and production builds relying on terser-webpack-plugin.

Overview
Restructures the supplemental npm packages’ dependency model so bundler singletons are no longer bundled as direct deps.

shakapacker-webpack now declares webpack, webpack-cli, and webpack-assets-manifest as required peerDependencies (and moves terser-webpack-plugin to a direct dependency), while shakapacker-rspack similarly peers @rspack/core, @rspack/cli, and rspack-manifest-plugin. Version constraints are loosened from ~ to ^/broader ranges for non-shakapacker packages (including esbuild), and docs/migration guidance is updated to spell out npm 7+ auto-peer-install vs pnpm/Yarn PnP explicit-deps requirements.

Adds new Jest coverage (test/packages/package-metadata.test.js and an opt-in install-smoke.test.js) to lock in the peer/dependency contract and validate resolution behavior across npm/pnpm, plus updates the changelog/blog/design-rationale docs accordingly.

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

Summary by CodeRabbit

  • Chores

    • Supplemental bundler packages now declare the bundler as required peer dependencies; terser-webpack-plugin is a direct dependency for the webpack wrapper. Version ranges loosened (tilde → caret) for broader compatibility.
  • Documentation

    • Installation, migration, and blog guidance updated with npm/pnpm/Yarn PnP install behavior and one-command npm 7+ flows.
  • Tests

    • Added tests validating package metadata and install-resolution behaviors across npm and pnpm.

Review Change Stack

justin808 added 2 commits May 22, 2026 22:32
Move bundler singletons from `dependencies` to required `peerDependencies`
in both supplemental packages, and loosen `~` to `^` for all non-shakapacker
packages.

- shakapacker-webpack: webpack, webpack-cli, webpack-assets-manifest move
  to required peerDependencies; terser-webpack-plugin moves from optional
  peer to direct dependency (package/optimization/webpack.ts always
  requires it for the default minimizer).
- shakapacker-rspack: @rspack/core, @rspack/cli, rspack-manifest-plugin
  move to required peerDependencies.
- Both: shakapacker stays as a tilde-pinned direct dependency (lockstep
  with core); all other constraints use caret with floors aligned to main
  shakapacker's peer ranges, so the supplemental never narrows what bare
  shakapacker accepts.

Modern package managers (npm 7+, pnpm, yarn 2+) auto-install required
peers, preserving the one-command install experience. Yarn classic 1.x
users see a peer warning listing the required packages; the Rails
installer continues to write all required deps explicitly into the user's
package.json.

Adds test/packages/package-metadata.test.js to lock in the contract:
singletons are required peers, terser-webpack-plugin is a direct dep,
shakapacker uses tilde, no other tilde constraints exist, and supplemental
peer ranges don't narrow main's ranges unless intentionally curated.

Refs: #1131
Revises every place that described the supplemental packages as bundling
the bundler stack via direct `dependencies` to instead describe the new
required-peer-dependency shape. Adds yarn 1 caveats wherever the
one-command install claim previously held unconditionally.

- docs/dependency-strategy.md: rewrite the summary, per-package tables,
  Version Pinning Philosophy section, and What Each User Type Installs
  examples. Add the rationale for choosing peer deps over direct deps
  (singleton bundler instance invariant). Update Resolved Decisions and
  the release-sequencing note.
- docs/migration/v10.1-supplemental-packages.md: add a package-manager
  support table and split the "After" examples into modern-PM (auto-peer-
  install) and yarn-1 (explicit peer list) variants.
- docs/blog/2026-05-10-shakapacker-10-1-supplemental-packages.md: revise
  the 30-second pitch and "What changed" section.
- packages/shakapacker-{webpack,rspack}/README.md: rewrite Install sections
  with auto-peer-install vs yarn-1 commands.
- CHANGELOG.md: add an Unreleased entry describing the restructure with
  rationale and modern-PM behavior.

No installer changes — the Rails installer (lib/install/template.rb)
already writes core + all peers explicitly into the user's package.json,
which gives true one-command UX on every package manager. Switching the
installer to use supplemental packages instead would be a behavior change
beyond the scope of issue #1131 (PR #1096 kept supplemental adoption
opt-in for the v10.x line).

Refs: #1131
@coderabbitai

coderabbitai Bot commented May 23, 2026

Copy link
Copy Markdown

Walkthrough

The PR restructures supplemental packages so bundler singletons become required peer dependencies (caret ranges), keeps shakapacker as a tilde-pinned direct dependency, moves terser-webpack-plugin to a direct dependency for webpack, and updates docs and tests to reflect installer behavior across npm/pnpm/Yarn.

Changes

Peer-Dependency Strategy and Implementation

Layer / File(s) Summary
Dependency strategy specification and rationale
docs/dependency-strategy.md
Core policy document specifies the v11 three-package model: bundlers declared as required peer dependencies with caret ranges, shakapacker as a tilde-pinned direct dependency, with rationale, sequencing, and installer semantics.
Package manifest restructuring
packages/shakapacker-webpack/package.json, packages/shakapacker-rspack/package.json
Move bundler packages from direct dependencies into required peerDependencies for rspack; move terser-webpack-plugin into dependencies for webpack; broaden peer version ranges from ~ to ^/OR alternatives and remove optional peer metadata for terser.
Installation and migration guidance
docs/migration/v10.1-supplemental-packages.md, docs/blog/2026-05-10-shakapacker-10-1-supplemental-packages.md, packages/shakapacker-webpack/README.md, packages/shakapacker-rspack/README.md
Update blog, migration docs, and READMEs to explain required peer deps, npm 7+ auto-install vs pnpm/Yarn PnP explicit-dep expectations, install-command wording, and updated example constraints (including webpack-assets-manifest and terser-webpack-plugin notes).
Dependency metadata validation
test/packages/package-metadata.test.js, test/packages/install-smoke.test.js
Add Jest tests verifying required-peer declarations, terser-webpack-plugin as direct dependency for webpack, shakapacker tilde pinning, cross-manifest peer-range consistency, and an opt-in install-smoke harness for npm/pnpm resolution behaviors.
Changelog and announcement
CHANGELOG.md, docs/blog/2026-05-10-shakapacker-10-1-supplemental-packages.md
Release notes and blog post document the dependency-model changes, caret-range loosenings, terser relocation for webpack, and installer behavior differences across package managers.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • shakacode/shakapacker#1096: Prior PR that modified supplemental package manifests and dependency wiring; closely related to these dependency/peer-dependency adjustments.

Suggested labels

documentation, p2

Poem

🐰 A hop through peer deps, tidy and bright,

Bundlers aligned, no duplicates in sight.
Tilde for the core, carets all round,
Tests and docs updated—safe ground. 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Restructure supplemental package dependencies (#1131)' clearly and specifically summarizes the main change: restructuring how dependencies are organized in supplemental packages.
Linked Issues check ✅ Passed The PR comprehensively addresses all three coding requirements from #1131: bundler singletons moved to required peerDependencies, version ranges relaxed from ~ to ^ for non-shakapacker deps, and terser-webpack-plugin made a direct dependency.
Out of Scope Changes check ✅ Passed All changes are directly related to the three core objectives from #1131. Documentation updates, tests, and README changes support the dependency restructuring and are within scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jg-conductor/issue-1131-review

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.

@justin808

Copy link
Copy Markdown
Member Author

@G-Rath this is the PR addressing your feedback on #1131. Would appreciate a sanity check on the per-package range choices in particular (looking at packages/shakapacker-webpack/package.json and packages/shakapacker-rspack/package.json).

Comment thread test/packages/package-metadata.test.js
Comment thread packages/shakapacker-rspack/package.json
Comment thread packages/shakapacker-webpack/package.json Outdated
Comment thread test/packages/package-metadata.test.js Outdated
@claude

claude Bot commented May 23, 2026

Copy link
Copy Markdown
Contributor

Code Review — PR #1133: Restructure supplemental package dependencies

Summary

The dependency topology change is well-motivated and correctly implemented. Moving bundler singletons (webpack, @rspack/core, etc.) to required peer dependencies eliminates the duplicate-webpack failure mode, and the CHANGELOG/docs are thorough. The new contract test is a good addition. Three issues are worth addressing before GA.


Issues

1. Missing rspack peer-alignment test (moderate)

The supplemental peer ranges align with main shakapacker describe block only tests the webpack supplemental — rspackManifestPath is never passed to collectMismatches. The rspack supplemental already has undocumented range differences vs. main (@rspack/core/@rspack/cli drop v1.x; @rspack/plugin-react-refresh drops the -0 pre-release suffix). Without a parallel test, future maintainers have no guard against accidental drift. See inline comment on line 133.

2. @rspack/plugin-react-refresh range differs from main (minor)

packages/shakapacker-rspack/package.json declares "^1.0.0 || ^2.0.0" while main shakapacker declares "^1.0.0 || ^2.0.0-0". The -0 suffix in main admits pre-release 2.x builds (e.g. 2.0.0-rc.1). A user pinned to such a version against bare shakapacker would hit a peer conflict after adopting the supplemental. Either add -0 to match main, or document it as an intentional narrowing in the new rspack alignment test. See inline comment on line 35 of the rspack package.json.

3. webpack-cli floor is ^7.0.2 rather than ^7.0.0 (minor)

The stated curation intent is "drops v4–v6", implying v7.0.0 as the floor. The ^7.0.2 floor is not documented anywhere (unlike the webpack-assets-manifest v6 floor, which has a clear ENOENT rationale). If 7.0.0 or 7.0.1 had a regression that motivated the floor, add a note; otherwise prefer ^7.0.0. See inline comment on line 34 of the webpack package.json.


Minor / Non-blocking

  • collectMismatches string-splitting (m.split(":")[0]) to extract the package name for knownIntentionalNarrowings works today but is fragile if the format string changes. Returning { name, supplementalRange, mainRange } objects would be more robust. See inline comment on line 130.
  • RC version strings in migration docs — the yarn-1 examples in docs/migration/v10.1-supplemental-packages.md show "shakapacker-rspack": "~10.1.0" (no -rc.1), while the actual package.json uses ~10.1.0-rc.1. Not a bug — the doc is forward-looking to GA — but worth a note so the GA cut checklist does not miss it.

What looks good

  • Dependency topology reasoning is sound — bundler singletons as required peers is the correct npm primitive for this use case.
  • The peerDependenciesMeta updates are correct: singleton required peers have no entry (correctly absent = required), optional peers are marked optional: true.
  • The terser-webpack-plugin promotion to a direct dep is well-justified given package/optimization/webpack.ts's unconditional requireOrError call.
  • Caret ranges for everything except shakapacker itself is the right call — tilde-locking non-lockstep deps would have created exactly the release-cadence trap described in Dependency feedback for v10.1.0 #1131.
  • The collectTildeOffenders helper and test.each usage are clean and the test coverage for the singleton contract is solid.

@greptile-apps

greptile-apps Bot commented May 23, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Restructures shakapacker-webpack and shakapacker-rspack dependency declarations per issue #1131: bundler singletons (webpack, @rspack/core, etc.) move from direct dependencies to required peerDependencies, tilde constraints widen to caret for all non-shakapacker packages, and terser-webpack-plugin becomes a direct dependency of shakapacker-webpack.

  • packages/shakapacker-{webpack,rspack}/package.json — three structural changes: singletons as required peers, ~^, terser-webpack-plugin as direct dep on the webpack package.
  • test/packages/package-metadata.test.js (new) — 12-test contract suite covering singletons, tilde constraints, lockstep shakapacker pin, and webpack peer-range alignment with main; the rspack peer-range alignment counterpart is absent and collectTildeOffenders only detects leading tildes.
  • docs/** / CHANGELOG.md — documentation and migration guide updated to reflect the peer-dep model, PM support table, and yarn-1 manual-install path.

Confidence Score: 4/5

The package.json restructuring is correct and the documentation is thorough; the new test file guards the key contracts well for the webpack supplemental but leaves the rspack supplemental's peer-range narrowings without test coverage.

The core dependency changes are structurally sound and webpack test coverage is solid. The rspack peer-range alignment test is missing — the helper function is parameterized but was never wired up for rspack — leaving three current range divergences from main undocumented and unguarded against future drift. The collectTildeOffenders OR-range gap is a smaller future trap.

test/packages/package-metadata.test.js — the rspack peer-range alignment test is absent; packages/shakapacker-rspack/package.json — the intentional narrowings (rspack v1 dropped, react-refresh missing -0) have no test documentation.

Important Files Changed

Filename Overview
packages/shakapacker-webpack/package.json Moves webpack/webpack-cli/webpack-assets-manifest to required peerDependencies and terser-webpack-plugin to direct dependency; widens all non-shakapacker ranges from ~ to ^.
packages/shakapacker-rspack/package.json Moves @rspack/core, @rspack/cli, rspack-manifest-plugin to required peerDependencies; widens all non-shakapacker ranges from ~ to ^. rspack narrowed to v2+ only (drops v1).
test/packages/package-metadata.test.js New test file locking in the dependency contract. Webpack singletons, tilde constraints, and peer-range alignment tests are solid, but the rspack peer-range alignment test is absent and collectTildeOffenders misses tildes in OR ranges.
docs/dependency-strategy.md Docs updated to reflect the new peer-dependency model with range tables, pinning philosophy, and yarn-1 caveats.
docs/migration/v10.1-supplemental-packages.md Adds PM support table and splits After examples by package manager; yarn-1 path is now explicit with manual peer install commands.
CHANGELOG.md Unreleased entry added documenting the three structural changes.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[npm 7+ / pnpm / yarn 2+
yarn add shakapacker-webpack] --> B[shakapacker-webpack installed]
    B --> C[Required peers auto-installed:
webpack ^5.101.0
webpack-cli ^7.0.2
webpack-assets-manifest ^6.0.0]
    B --> D[Direct deps installed:
shakapacker ~10.1.0-rc.1
terser-webpack-plugin ^5.3.1]
    E[yarn 1
yarn add shakapacker-webpack] --> F[shakapacker-webpack installed]
    F --> G[Direct deps installed:
shakapacker ~10.1.0-rc.1
terser-webpack-plugin ^5.3.1]
    F --> H[Peer warning printed:
webpack, webpack-cli,
webpack-assets-manifest missing]
    H --> I[User adds peers manually]
    J[npm 7+ / pnpm / yarn 2+
yarn add shakapacker-rspack] --> K[shakapacker-rspack installed]
    K --> L[Required peers auto-installed:
@rspack/core ^2.0.0
@rspack/cli ^2.0.0
rspack-manifest-plugin ^5.0.0]
    K --> M[Direct dep installed:
shakapacker ~10.1.0-rc.1]
Loading

Reviews (1): Last reviewed commit: "Update docs and CHANGELOG for supplement..." | Re-trigger Greptile

Comment thread test/packages/package-metadata.test.js
Comment thread test/packages/package-metadata.test.js

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 (2)
packages/shakapacker-webpack/package.json (1)

98-98: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add trailing newline.

The file must end with a trailing newline character. As per coding guidelines, all files matching **/* should end with a trailing newline character.

📝 Proposed fix
   "publishConfig": {
     "access": "public"
   }
 }
+
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shakapacker-webpack/package.json` at line 98, Add a single trailing
newline character at the end of the package.json file so the file ends with a
newline; update the file content to ensure the final character is '\n' (no other
changes required).
packages/shakapacker-rspack/package.json (1)

61-61: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add trailing newline.

The file must end with a trailing newline character. As per coding guidelines, all files matching **/* should end with a trailing newline character.

📝 Proposed fix
   "publishConfig": {
     "access": "public"
   }
 }
+
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shakapacker-rspack/package.json` at line 61, Add a single trailing
newline character at the end of the shakapacker-rspack package.json file so the
file ends with a newline (ensure the final byte is '\n'); this satisfies the
repository's requirement that all files matching **/* end with a trailing
newline.
🧹 Nitpick comments (1)
docs/blog/2026-05-10-shakapacker-10-1-supplemental-packages.md (1)

24-24: 💤 Low value

Consider simplifying the redundant phrase.

The phrase "duplicate copies" is redundant since "duplicate" already implies "copies." Consider using "duplicates", "duplicate instances", or "multiple copies" for more concise writing.

✍️ Suggested simplification
-10.1 shifts that responsibility to the supplemental packages. Each one declares the singleton bundler stack as **required peer dependencies** so modern package managers auto-install them while still surfacing version conflicts as warnings rather than silently installing duplicate copies of webpack:
+10.1 shifts that responsibility to the supplemental packages. Each one declares the singleton bundler stack as **required peer dependencies** so modern package managers auto-install them while still surfacing version conflicts as warnings rather than silently installing duplicates of webpack:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/blog/2026-05-10-shakapacker-10-1-supplemental-packages.md` at line 24,
Replace the redundant phrase "duplicate copies of webpack" with a more concise
alternative such as "duplicate instances of webpack", "duplicates of webpack",
or "multiple copies of webpack" in the sentence that currently reads "Each one
declares the singleton bundler stack as **required peer dependencies** so modern
package managers auto-install them while still surfacing version conflicts as
warnings rather than silently installing duplicate copies of webpack:"; update
the phrase "duplicate copies of webpack" accordingly to improve clarity.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.md`:
- Line 14: The CHANGELOG entry is missing the PR number and contributor
attribution; update the leading sentence of the existing entry (the line
starting "- **Restructured `shakapacker-webpack` and `shakapacker-rspack`
dependency declarations**") to include the PR link and author in the required
format — e.g. prepend " [PR
`#1133`](https://github.com/shakacode/shakapacker/pull/1133) by
[justin808](https://github.com/justin808)" immediately after the bold title so
the line reads like the provided example.

---

Outside diff comments:
In `@packages/shakapacker-rspack/package.json`:
- Line 61: Add a single trailing newline character at the end of the
shakapacker-rspack package.json file so the file ends with a newline (ensure the
final byte is '\n'); this satisfies the repository's requirement that all files
matching **/* end with a trailing newline.

In `@packages/shakapacker-webpack/package.json`:
- Line 98: Add a single trailing newline character at the end of the
package.json file so the file ends with a newline; update the file content to
ensure the final character is '\n' (no other changes required).

---

Nitpick comments:
In `@docs/blog/2026-05-10-shakapacker-10-1-supplemental-packages.md`:
- Line 24: Replace the redundant phrase "duplicate copies of webpack" with a
more concise alternative such as "duplicate instances of webpack", "duplicates
of webpack", or "multiple copies of webpack" in the sentence that currently
reads "Each one declares the singleton bundler stack as **required peer
dependencies** so modern package managers auto-install them while still
surfacing version conflicts as warnings rather than silently installing
duplicate copies of webpack:"; update the phrase "duplicate copies of webpack"
accordingly to improve clarity.
🪄 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: 4e9ac6f7-3cce-449c-a68f-cb4362fff728

📥 Commits

Reviewing files that changed from the base of the PR and between e5c9cd4 and 2375bb6.

📒 Files selected for processing (9)
  • CHANGELOG.md
  • docs/blog/2026-05-10-shakapacker-10-1-supplemental-packages.md
  • docs/dependency-strategy.md
  • docs/migration/v10.1-supplemental-packages.md
  • packages/shakapacker-rspack/README.md
  • packages/shakapacker-rspack/package.json
  • packages/shakapacker-webpack/README.md
  • packages/shakapacker-webpack/package.json
  • test/packages/package-metadata.test.js

Comment thread CHANGELOG.md Outdated
- CHANGELOG: add PR #1133 link and author attribution per project format
- shakapacker-rspack: align @rspack/plugin-react-refresh peer with main
  (add `-0` prerelease suffix so 2.0.0-rc.x still resolves)
- shakapacker-webpack: lower webpack-cli floor from ^7.0.2 to ^7.0.0
  (no documented reason for the patch-level floor)
- package-metadata test: detect tildes inside OR ranges (e.g.
  "^1.0.0 || ~2.0.0"), return structured mismatch objects so
  filtering is robust to format changes, and add a parallel
  rspack alignment test with intentional-narrowing exceptions
  for @rspack/core and @rspack/cli
@claude

claude Bot commented May 23, 2026

Copy link
Copy Markdown
Contributor

Code Review

Overview

The approach is sound — moving bundler singletons to required peer dependencies is the correct fix for the duplicate-webpack issue in #1131. Bundler packages are singletons by design (instanceof checks break if two copies exist in the tree), so required peers is the right model. The tilde-only-for-shakapacker + caret-for-everything-else split is clearly motivated, and the new test file does a good job of locking in the contract so it doesn't regress silently.

Findings

Bug: ^2.0.0-0 allows pre-release versions of @rspack/plugin-react-refresh

In packages/shakapacker-rspack/package.json the range is:

"@rspack/plugin-react-refresh": "^1.0.0 || ^2.0.0-0"

In npm semver, ^2.0.0-0 resolves to >=2.0.0-0 <3.0.0, which includes pre-release tags like 2.0.0-alpha.1 and 2.0.0-rc.2. This is almost certainly unintentional — the stable equivalent is ^2.0.0. The docs table already shows ^1.0.0 || ^2.0.0 as the intended range, so the -0 suffix appears to be a typo. See inline comment.

Maintenance concern: esbuild OR-chain needs extending with every new minor

packages/shakapacker-webpack/package.json declares esbuild as:

"^0.14.0 || ^0.15.0 || ... || ^0.27.0"

This is technically correct (npm semver treats ^0.x.y as patch-only in the 0.x range), but it'll need a new entry every time esbuild ships a new minor. >=0.14.0 <1.0.0 is semantically equivalent and doesn't require updates. See inline comment.

Minor: webpack-cli floor mismatch between package.json and docs

packages/shakapacker-webpack/package.json declares "webpack-cli": "^7.0.0", but docs/dependency-strategy.md and docs/migration/v10.1-supplemental-packages.md both show ^7.0.2. The rc.1 shape used ~7.0.2, so 7.0.2 is the tested floor. At minimum the docs should match the actual range; ideally the package.json floor should be raised to ^7.0.2 to stay honest about what's been tested.

Test: knownIntentionalNarrowings covers both narrowings and widenings

The name is slightly misleading — the set also catches cases where the supplemental is wider than main (e.g. if a supplemental peer accepts older versions main doesn't). Not a bug (the Set-based exclusion works correctly), but renaming to knownIntentionalDifferences or similar would make the intent clearer to future maintainers.

What looks good

  • Structural changes to both package.json files are consistent with each other and with the documented rationale.
  • terser-webpack-plugin correctly moved from optional peer to direct dep — requireOrError in package/optimization/webpack.ts makes it unconditionally required.
  • The peerDependenciesMeta cleanup is correct: singletons have no entry (required by default), optional features remain marked optional: true.
  • Test assertions are precise — checking both positive (toHaveProperty) and negative (not.toHaveProperty) sides, and the collectMismatches alignment test will catch accidental divergence in future PRs.
  • CHANGELOG entry is clear and follows the project format.

Comment thread packages/shakapacker-rspack/package.json Outdated
Comment thread packages/shakapacker-webpack/package.json Outdated
Comment thread packages/shakapacker-webpack/package.json

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Pnpm docs omit terser-webpack-plugin
    • Updated pnpm and Yarn webpack migration/install docs to require explicit terser-webpack-plugin so core Shakapacker can resolve it under strict package managers.

Create PR

Or push these changes by commenting:

@cursor push b661ea9fac
Preview (b661ea9fac)
diff --git a/docs/migration/v10.1-supplemental-packages.md b/docs/migration/v10.1-supplemental-packages.md
--- a/docs/migration/v10.1-supplemental-packages.md
+++ b/docs/migration/v10.1-supplemental-packages.md
@@ -15,14 +15,14 @@
 | yarn 2+ (Berry)  | **No** — see below    |
 | yarn 1 (classic) | **No** — see below    |
 
-pnpm and Yarn PnP keep dependency boundaries strict: packages imported by your app's config files must be listed directly in your app's `package.json`. The default generated configs import `shakapacker`, and many customized webpack configs import `webpack`, so keep those direct dependencies alongside the supplemental package:
+pnpm and Yarn PnP keep dependency boundaries strict: packages imported by your app's config files or resolved by core Shakapacker must be listed directly in your app's `package.json`. The default generated configs import `shakapacker`, many customized webpack configs import `webpack`, and production webpack builds resolve `terser-webpack-plugin`, so keep those direct dependencies alongside the supplemental package:
 
 ```sh
 # webpack, pnpm
-pnpm add --save-dev shakapacker-webpack shakapacker webpack webpack-cli webpack-assets-manifest
+pnpm add --save-dev shakapacker-webpack shakapacker terser-webpack-plugin webpack webpack-cli webpack-assets-manifest
 
 # webpack, yarn
-yarn add --dev shakapacker-webpack shakapacker webpack webpack-cli webpack-assets-manifest
+yarn add --dev shakapacker-webpack shakapacker terser-webpack-plugin webpack webpack-cli webpack-assets-manifest
 
 # rspack, pnpm
 pnpm add --save-dev shakapacker-rspack shakapacker @rspack/core @rspack/cli rspack-manifest-plugin
@@ -124,6 +124,7 @@
   "devDependencies": {
     "shakapacker-webpack": "~10.1.0",
     "shakapacker": "~10.1.0",
+    "terser-webpack-plugin": "^5.3.1",
     "webpack": "^5.101.0",
     "webpack-cli": "^7.0.0",
     "webpack-assets-manifest": "^6.0.0"

diff --git a/packages/shakapacker-webpack/README.md b/packages/shakapacker-webpack/README.md
--- a/packages/shakapacker-webpack/README.md
+++ b/packages/shakapacker-webpack/README.md
@@ -11,14 +11,14 @@
 npm install --save-dev shakapacker-webpack

-pnpm and Yarn PnP keep dependency boundaries strict: packages imported by your app's config files must be listed directly in your app's package.json. The default generated webpack config imports shakapacker, and many customized configs import webpack, so keep those direct dependencies alongside the supplemental package:
+pnpm and Yarn PnP keep dependency boundaries strict: packages imported by your app's config files or resolved by core Shakapacker must be listed directly in your app's package.json. The default generated webpack config imports shakapacker, many customized configs import webpack, and production builds resolve terser-webpack-plugin, so keep those direct dependencies alongside the supplemental package:

# pnpm
-pnpm add --save-dev shakapacker-webpack shakapacker webpack webpack-cli webpack-assets-manifest
+pnpm add --save-dev shakapacker-webpack shakapacker terser-webpack-plugin webpack webpack-cli webpack-assets-manifest

# yarn
-yarn add --dev shakapacker-webpack shakapacker webpack webpack-cli webpack-assets-manifest
+yarn add --dev shakapacker-webpack shakapacker terser-webpack-plugin webpack webpack-cli webpack-assets-manifest

(The Rails shakapacker:install task writes all required deps into your package.json regardless of package manager.)
@@ -52,7 +52,7 @@
}


-Optional peers (transpilers, `webpack-dev-server`, `mini-css-extract-plugin`, CSS preprocessors, etc.) stay only if your app uses those features. Run `yarn install` (or the npm/pnpm equivalent) and the lockfile collapses to the managed stack. npm 7+ can auto-install the required peers; pnpm and Yarn users should keep `shakapacker`, `webpack`, `webpack-cli`, and `webpack-assets-manifest` as explicit `devDependencies` unless their config imports the wrapper package directly.
+Optional peers (transpilers, `webpack-dev-server`, `mini-css-extract-plugin`, CSS preprocessors, etc.) stay only if your app uses those features. Run `yarn install` (or the npm/pnpm equivalent) and the lockfile collapses to the managed stack. npm 7+ can auto-install the required peers; pnpm and Yarn users should keep `shakapacker`, `terser-webpack-plugin`, `webpack`, `webpack-cli`, and `webpack-assets-manifest` as explicit `devDependencies` so core Shakapacker and app config imports resolve under strict dependency boundaries.

### Migrating from core's webpack peer set

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit 7a00207. Configure here.

Comment thread docs/migration/v10.1-supplemental-packages.md Outdated
@justin808

Copy link
Copy Markdown
Member Author

Reviewed the two follow-up commits (6a920352, 7a002074). The substantive correctness issues from the earlier review are addressed — version mismatches aligned, Yarn-Berry-auto-installs claim removed, pnpm/PnP migration path honestly documented with two-path "After" examples, metadata test strengthened (now catches tilde inside ||-compound ranges and asserts rspack-side range alignment). 13 tests pass.

A few smaller things worth tracking, none blocking merge:

  1. No automated test for the pnpm/Yarn-PnP migration path. The docs now make specific claims (npm 7+ wrapper-only works; pnpm/Yarn need explicit deps because of strict resolution), but nothing in CI exercises them. A test/packages/install-smoke.test.js that packs shakapacker-webpack, installs it under pnpm strict + Yarn Berry into temp app fixtures, and asserts that (a) the npm 7+ minimal manifest resolves require("shakapacker") from app code, and (b) the pnpm/Yarn minimal manifest does not — would lock the documented behavior in. Probably ~30 min of work plus CI runtime budget. Worth doing as a follow-up before v10.1 GA so the next package.json edit can't silently break the pnpm story.

  2. The wrapper-import path is mentioned but never shown. docs/dependency-strategy.md refers to it ("unless their config imports the wrapper package directly") and packages/shakapacker-{webpack,rspack}/README.md repeats it, but the migration guide doesn't show a worked example. A short snippet showing const { generateWebpackConfig } = require("shakapacker-webpack") (the wrappers do module.exports = require("shakapacker")/require("shakapacker/rspack") so this is a name change, not a behavior change) would clarify the alternative for users who want fewer explicit deps.

  3. Honest framing of the wrapper-import path's actual value. The wrapper-import alternative only removes shakapacker from the explicit dep list, not the bundler. Most webpack configs that do anything custom import webpack directly (new webpack.DefinePlugin(...), etc.), so on pnpm/PnP those configs still need webpack declared explicitly even after changing the shakapacker import. Worth noting somewhere so users don't expect this to give them a true one-line devDependencies.

  4. Generated config templates still require("shakapacker"). Out of scope here per PR RFC: Tighten dependencies for Shakapacker v11 #1096's opt-in stance for v10.x. Tied to whether the installer ever switches to writing the supplemental package — likely a v11 conversation.

The current state is a clear improvement over rc.1 and the doc story is now accurate. 👍 from my end, with item 1 as a meaningful follow-up before GA.

Comment thread test/packages/package-metadata.test.js
Comment thread docs/migration/v10.1-supplemental-packages.md Outdated
Comment thread packages/shakapacker-rspack/package.json Outdated
@claude

claude Bot commented May 23, 2026

Copy link
Copy Markdown
Contributor

Code Review

Overall: The restructuring is technically correct and addresses #1131 well. Moving bundler singletons to required peer dependencies is the right call — the duplicate-webpack silent failure mode is a real bug class. The contract tests in test/packages/package-metadata.test.js are a nice addition that lock in the new shape.


Substantive issues

Yarn 2+ (Berry) nodeLinker: node-modules is misclassified

The PM support table marks all of Yarn 2+ (Berry) as No for wrapper-only installs. This is only true for PnP mode (the Berry default). Yarn 2/3/4 with nodeLinker: "node-modules" in .yarnrc.yml hoists packages and does auto-install required peers the same way npm 7+ does. Many Berry users are on node-modules mode, so they'd incorrectly follow the "list everything explicitly" path. A footnote or a split row would help. See the inline comment on the migration doc.

Alignment test's knownIntentionalNarrowings creates a silent allowlist

The collectMismatches function uses string equality (!==) rather than semver semantics. This means:

  • Any future bump to main's peer range floors (e.g. webpack-dev-server: ^5.2.2^5.3.0) will fail this test, requiring a coordinated supplemental update — the intended behavior, but future contributors will be surprised.
  • The knownIntentionalNarrowings set has no forward assertion: if a narrowing is later removed from the packages but left in the set, the test stays green and silently stops enforcing anything. Adding expect(knownIntentionalNarrowings.size).toBe(collectMismatches(...).length + unintended.length) or similar would catch stale entries.

See the inline comment on the test file for detail.


Minor observations

terser-webpack-plugin install footprint: Making it a direct dep means every shakapacker-webpack user downloads it, even those who set minimize: false or bring a custom minimizer. The PR body acknowledges this and the rationale (requireOrError for the default minimizer) is sound. Calling it out so it's a documented conscious decision.

Unchecked smoke tests: The "Manual install smoke tests on npm 7+, pnpm, yarn 1, yarn 2+" item is unchecked. This is the highest-risk part of the change (dependency topology shifts are hard to fully exercise in unit tests) — marking it as a gate before the GA publish is the right call.

esbuild peer range verbosity: The 14-operand OR chain is correct given esbuild's pre-1.0 versioning (each minor is breaking, so ^0.x only covers patches). It matches main exactly, so no action needed — just confirming it's intentional.


Summary

No blocking issues. The two substantive points worth addressing before GA:

  1. Clarify the Yarn 2+ Berry table (PnP vs node-modules mode)
  2. Consider hardening the knownIntentionalNarrowings check against stale entries

Everything else is implementation detail or acknowledged tradeoff.

justin808 added 2 commits May 23, 2026 12:12
Locks in the resolution matrix that docs/migration/v10.1-supplemental-packages.md
now documents:

- npm wrapper-only:  shakapacker resolves from app root (npm hoisting)
- npm explicit-deps: shakapacker resolves
- pnpm wrapper-only: shakapacker does NOT resolve (strict isolation under .pnpm/)
- pnpm explicit-deps: shakapacker resolves

The pnpm wrapper-only assertion is the exact failure mode that issue #1131
review feedback flagged — a user adopting `shakapacker-webpack` alone on
pnpm hits `Cannot find module 'shakapacker'` because the default generated
config does `require("shakapacker")` from app code.

Implementation:
- Packs `shakapacker` core and `shakapacker-webpack` into tarballs in a
  tmpdir
- Builds two temp app fixtures (wrapper-only, explicit-deps) using `file:`
  references + `overrides`/`pnpm.overrides`/`resolutions` so the
  supplemental's `dependencies.shakapacker: "~10.1.0-rc.1"` resolves to
  the local tarball regardless of what's on the registry
- Runs `npm install` and `pnpm install` against each fixture
- Verifies resolution via `node -e "require.resolve('shakapacker')"` from
  the app CWD (not the jest CWD — pnpm's strict layout only shows up when
  you actually probe from inside the installed app)

Opt-in via `RUN_INSTALL_SMOKE=1` because it shells out to package
managers, hits the npm registry for bundler peers, and takes ~15s
end-to-end on a warm cache. Auto-skips with a visible `test.todo`
placeholder when the env var is unset, when shakapacker hasn't been built
(no `package/index.js`), or when neither npm nor pnpm is on PATH. The
PM probe spawns from `os.tmpdir()` so corepack's project-spec
interception doesn't false-negative pnpm in workspaces that pin a
different `packageManager`.

Refs: #1131
Adds a worked example to docs/migration/v10.1-supplemental-packages.md
showing how to change `require("shakapacker")` to
`require("shakapacker-webpack")` (and the rspack equivalent) so apps on
pnpm or Yarn PnP can drop `shakapacker` from their explicit
`devDependencies` without breaking the default config's import.

Also adds an honest framing note about the path's limits: the wrapper
re-exports core but does not proxy webpack itself, so configs that
import webpack directly (e.g., `new webpack.DefinePlugin(...)`) still
need webpack declared as a direct dep on strict-resolution PMs. The
wrapper-import path saves exactly one direct dep (`shakapacker`); it is
not a path to a truly minimal `devDependencies` block.

Refs: #1131
Comment thread test/packages/package-metadata.test.js Outdated
Comment thread test/packages/install-smoke.test.js
Comment thread packages/shakapacker-webpack/package.json Outdated
Comment thread test/packages/install-smoke.test.js
Comment thread test/packages/install-smoke.test.js
Comment thread test/packages/install-smoke.test.js Outdated
Comment thread docs/migration/v10.1-supplemental-packages.md
@claude

claude Bot commented May 24, 2026

Copy link
Copy Markdown
Contributor

Review: Restructure supplemental package dependencies (#1131)

The structural shift is technically correct and addresses the issue well. The documentation is thorough, the package manager compatibility table is accurate, and the package-metadata contract tests are a good addition that will catch future regressions.

Correctness

terser-webpack-plugin resolution under pnpm: Making it a direct dep of shakapacker-webpack helps npm users because npm hoists it to the flat top-level node_modules, where shakapacker's require("terser-webpack-plugin") can find it. Under pnpm, however, the require originates from inside shakapacker and pnpm resolves from the requirer's private store — shakapacker-webpack's direct dep gives pnpm users nothing; they still need to declare it at app level. The docs cover this correctly. Worth keeping in mind that the pnpm wrapper-only build will also fail at runtime even if the app-root resolution test passes.

Required peer semantics: Omitting an entry from peerDependenciesMeta correctly defaults to optional: false (required). Both supplemental packages only list optional peers in peerDependenciesMeta. Correct shape. ✓

Version ranges: Singleton floors are well-chosen. The esbuild: ">=0.14.0 <1.0.0" form is more explicit than "^0.14.0" (which would stop at <0.15.0 due to the 0.x semver rule) and aligns with main's identical range. ✓

Tests

The package-metadata.test.js suite is well-structured. Testing the contract at the metadata level is the right abstraction, and the expectOnlyKnownMismatches / stale-allowlist double-check is a particularly nice pattern.

Inline comments flag a few smoke-test issues:

  1. npm wrapper-only test missing terser-webpack-plugin assertion — the test proves npm hoists shakapacker transitively but doesn't verify terser-webpack-plugin is also hoisted, which is the core claim for npm users not needing to list it explicitly.
  2. node-linker=isolated written to .npmrc for npm test runs — harmless (npm ignores it) but potentially misleading to future readers.
  3. beforeAll timeout of 120s for three npm pack invocations — may be tight on slow CI while individual tests each get 180s.

Documentation

Good. One pre-GA issue: the pnpm/yarn "After" examples in the migration guide use "~10.1.0" (stable) rather than "~10.1.0-rc.1" (current). A user copying those snippets before GA would get an unresolvable range. Flagged inline.

Summary

The dependency topology decision is sound and the documentation accurately captures the package-manager nuances. The issues are all in the test/docs layer — nothing structurally wrong with the package.json changes themselves. Addressing the smoke-test gap and the version string in the migration examples would make this ready to merge.

- Assert appRootAssertions in the npm wrapper-only install-smoke test
  so npm's flat hoisting of shakapacker-webpack direct deps
  (terser-webpack-plugin) is exercised explicitly.
- Bump beforeAll timeout from 120s to 180s to match per-test budget;
  three sequential npm pack invocations can push past the smaller
  limit on cold CI.
- Note that node-linker=isolated and auto-install-peers=true are
  pnpm-only keys (npm silently ignores them) so the shared .npmrc
  isn't misread as affecting npm resolution.
- Add an rc-pin callout to the supplemental packages migration guide
  so users copying examples during the 10.1 rc window pin to
  ~10.1.0-rc.1 rather than the unpublished ~10.1.0 stable range.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@justin808

Copy link
Copy Markdown
Member Author

Review follow-up summary (63ee61b)

Four actionable review threads addressed in this commit:

  • npm wrapper-only test now exercises hoisted direct deps. The npm wrapper-only case in test/packages/install-smoke.test.js now iterates spec.appRootAssertions, so terser-webpack-plugin is asserted resolvable from the app root. Closes the loop: npm's flat hoisting is the reason that dep can be direct on shakapacker-webpack (rather than peer); the test now proves the claim. (PRRT_kwDOBReUZ86EWIcL)
  • .npmrc comment clarified. Added a one-line note that node-linker=isolated and auto-install-peers=true are pnpm-only keys (npm silently ignores them). Kept the shared .npmrc rather than splitting per-installer so the comment carries the rationale without extra branching. (PRRT_kwDOBReUZ86EWIc_)
  • beforeAll timeout bumped from 120s to 180s. Matches the per-test budget. Three sequential npm pack calls on cold CI shouldn't have a tighter limit than the integration tests they feed. (PRRT_kwDOBReUZ86EWIeJ)
  • rc-pin callout added to the migration guide. A note near the top explains the examples show the GA shape (~10.1.0) and tells users to pin to ~10.1.0-rc.1 while the prerelease is the published version. Avoided rewriting every example so the doc won't need a second sweep at GA. (PRRT_kwDOBReUZ86EWIjg)

All 19 previously-unresolved threads on this PR are now resolved:

  • 15 had been addressed in prior commits (162786ba, 8cf42438, bf44f094, e49ea345) with replies pointing at those SHAs; resolving now that the fixes are in main.
  • 4 are addressed by this commit.

Local checks before push: bin/conductor-exec yarn lint test/packages/install-smoke.test.js, bin/conductor-exec yarn jest test/packages/ (6 suites pass, 42 tests + 1 todo).

@claude

claude Bot commented May 24, 2026

Copy link
Copy Markdown
Contributor

Code Review

Overview

Solid architectural fix. Moving bundler singletons (webpack, @rspack/core, etc.) from dependencies to required peerDependencies is the right call — it prevents the silent duplicate-instance failure mode where instanceof webpack.Compiler checks break in plugins/loaders. The ~^ loosening for non-shakapacker packages is appropriate: it removes the release-cadence trap identified in #1131. The terser-webpack-plugin promotion from optional peer to direct dep is well-motivated given package/optimization/webpack.ts always requires it.

Issues

1. Doc/code mismatch — @rspack/plugin-react-refresh range (blocking)

docs/dependency-strategy.md:197 still shows `^1.0.0 \|\| ^2.0.0-0` for @rspack/plugin-react-refresh, but both packages/shakapacker-rspack/package.json and the root package.json now declare ^1.0.0 || ^2.0.0 (prerelease suffix dropped). The PR description explicitly calls out this intent ("Rspack 2.x is stable, so this excludes 2.0.0-* prereleases"), but the doc table was left on the old range. Inline comment filed.

2. install-smoke.test.js — pnpm wrapper-only test only covers the failure case

The pnpm wrapper-only test correctly asserts that shakapacker is not resolvable from app root under pnpm's strict isolation. But it doesn't assert that the required peers (webpack, @rspack/core, etc.) are resolvable — which is the positive outcome of declaring them as required peers with auto-install-peers=true. Without this assertion, the test only proves the documented failure mode but not that peerDependenciesMeta + auto-peer-install is actually working as intended. Inline comment filed.

3. package-metadata.test.jshasTilde operand check

The hasTilde helper splits on \s*\|\|\s* (which correctly strips surrounding whitespace around ||), so individual operands like ">=0.14.0 <1.0.0" correctly do not start with ~. This is fine — just worth confirming the regex handles all actual range formats in the manifest, which it does.

Nits

  • The pnpm + Yarn install examples in docs/migration/v10.1-supplemental-packages.md include terser-webpack-plugin in the webpack list with a good explanation. The explanation could note that core shakapacker (not the wrapper) is the actual caller — this clarifies why the wrapper's direct dep doesn't help under strict isolation.
  • docs/dependency-strategy.md still shows shakapacker: ~10.1.0-rc.1 in the dependency tables — that's correct for now (RC state), but a TODO note in the doc to update to ~10.1.0 at GA wouldn't hurt since this doc will be referenced by GA release prep.

What's correct

  • package-metadata.test.js locking in the required-peer contract is excellent — it will catch future regressions when someone moves a singleton back to dependencies.
  • The known-narrowing allowlist pattern in the alignment test is clever: stale entries fail loudly, so the test stays accurate as the manifest evolves.
  • The install-smoke opt-in design (RUN_INSTALL_SMOKE=1, test.todo placeholder, version guards) is well-considered.
  • CHANGELOG entry is appropriately detailed.

Verdict

One concrete doc/code mismatch to fix (the @rspack/plugin-react-refresh range in docs/dependency-strategy.md). The test coverage gap is lower priority (the smoke test is opt-in and manual smoke tests are planned pre-GA), but worth addressing before this stabilizes. Everything else is clean.

Comment thread docs/dependency-strategy.md Outdated
| sass-loader | `16.0.7` | Paired with sass |
| Package | Range | When needed |
| ---------------------------- | ------------------------------------------------ | ---------------- |
| @rspack/plugin-react-refresh | `^1.0.0 \|\| ^2.0.0-0` | React HMR |

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.

Doc/code mismatch: the table shows ^1.0.0 || ^2.0.0-0 (prerelease-compatible) but packages/shakapacker-rspack/package.json now declares ^1.0.0 || ^2.0.0 (stable-only). The PR description explicitly says the -0 suffix was removed because Rspack 2.x is stable — the doc table should match.

Suggested change
| @rspack/plugin-react-refresh | `^1.0.0 \|\| ^2.0.0-0` | React HMR |
| @rspack/plugin-react-refresh | `^1.0.0 \|\| ^2.0.0` | React HMR |

// pnpm isolates shakapacker under .pnpm/, so app-level
// `require("shakapacker")` (e.g. from webpack.config.js) cannot find it.
expect(resolvesFromAppRoot(dir, "shakapacker")).toBe(false)
}, 180000)

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 test validates the failure case documented in the migration guide (shakapacker is transitive and not reachable under pnpm's strict isolation). But it doesn't validate the success case: that the required peers (webpack, @rspack/core, etc.) do resolve from the app root after pnpm's auto-install-peers installs them.

That positive assertion is the key outcome of moving singletons to required peerDependencies — without it, this test can pass even if auto-install-peers silently failed to install the required peers.

Consider adding a resolvesFromAppRoot check for spec.packageName's required peers (available on the spec as the keys of spec.explicitDeps, minus terser-webpack-plugin which is a direct dep not a peer). For example:

// Required peers should be auto-installed at app root by pnpm 8+ with auto-install-peers=true
const requiredPeers =
  spec.name === "webpack"
    ? ["webpack", "webpack-cli", "webpack-assets-manifest"]
    : ["@rspack/core", "@rspack/cli", "rspack-manifest-plugin"]
requiredPeers.forEach((mod) => {
  expect(resolvesFromAppRoot(dir, mod)).toBe(true)
})

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

Copy link
Copy Markdown

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: 761f7db72f

ℹ️ 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".

})

const packTarball = (cwd, destDir, spawn = spawnSync) => {
const r = spawn("npm", ["pack", "--json", "--pack-destination", destDir], {

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 Guard npm pack flags behind npm 7+ check

shouldRun only requires that npm exists, but beforeAll always calls packTarball(), which executes npm pack --json --pack-destination. Those flags are not available in npm 6 (the v6 npm pack synopsis only supports --dry-run), so an environment with pnpm >=7 plus npm 6 will enter the suite and fail before any pnpm assertions run. Add an npm-version guard (or a fallback packing path) so the smoke test can skip cleanly instead of hard-failing on older npm.

Useful? React with 👍 / 👎.

Comment thread test/packages/install-smoke.test.js Outdated
"-e",
`const { createRequire } = require("module"); ` +
`const path = require("path"); ` +
`const [packageName, mod] = process.argv.slice(1); ` +

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.

Bug: off-by-one in process.argv slice

When Node runs with the -e flag, process.argv[1] is an empty string (not the first user argument), so slice(1) yields ['', packageName, mod]. The destructuring then assigns packageName = '' and mod = <the actual packageName>, causing appRequire.resolve("" + "/package.json") to try to resolve the absolute path /package.json, which throws and makes the function always return false.

This would cause the pnpm wrapper-only assertion (expect(resolvesFromInstalledPackage(...)).toBe(true)) to fail whenever RUN_INSTALL_SMOKE=1 is set.

Suggested change
`const [packageName, mod] = process.argv.slice(1); ` +
`const [packageName, mod] = process.argv.slice(2); ` +

@claude

claude Bot commented May 24, 2026

Copy link
Copy Markdown
Contributor

Code Review

Overview

This PR correctly addresses #1131 by restructuring the supplemental packages so bundler singletons are required peer dependencies rather than direct dependencies. The core design — avoiding duplicate webpack/rspack instances and the silent instanceof check breakage that causes — is sound and well-justified.


Bug

process.argv.slice(1) off-by-one in resolvesFromInstalledPackage (line 164)

When Node runs with -e, process.argv[1] is an empty string (no script file), so slice(1) yields ['', packageName, mod]. The destructuring binds packageName = '' and mod = <the actual packageName>. The subsequent appRequire.resolve('' + '/package.json') attempts to resolve the absolute path /package.json, throws, and makes the function always return false.

This means the pnpm wrapper-only assertion expect(resolvesFromInstalledPackage(dir, spec.packageName, mod)).toBe(true) would fail whenever RUN_INSTALL_SMOKE=1 is set. Fix: change to process.argv.slice(2).


What's working well

  • package-metadata.test.js — The test design is excellent: it locks in the structural contract (singletons are required peers, terser-webpack-plugin is a direct dep, no ~ outside shakapacker, supplemental peers don't narrow main's ranges). The knownIntentionalNarrowings allowlist with a stale-entry check is a nice pattern that prevents silent drift.

  • Version range rationale — The ~^ change is well-motivated. Tilde-pinning supplemental packages to every upstream minor release would create an unsustainable release cadence obligation.

  • terser-webpack-plugin promotion — Moving it from optional peer to direct dependency of shakapacker-webpack is the right call. The docs correctly explain why pnpm/Yarn PnP users still need to list it directly (the require call happens inside the shakapacker package boundary, not shakapacker-webpack's).

  • @rspack/plugin-react-refresh prerelease exclusion — Tightening from ^2.0.0-0 to ^2.0.0 is appropriate now that rspack 2.x is stable.

  • esbuild range consolidation>=0.14.0 <1.0.0 is cleaner and adds forward-compatibility for future 0.x minors without a coordinated release.

  • Smoke test opt-in design — Gating on RUN_INSTALL_SMOKE=1 and registering a test.todo placeholder in the normal case is the right UX.


Minor notes

  • GA release-prep note in docs — The inline note in docs/dependency-strategy.md to update ~10.1.0-rc.1 rows before GA is helpful; consider pairing it with a checklist item in the release process so it doesn't get missed.

  • Smoke test requires npm even for pnpm-only runs — The dependency on npm pack for tarball creation means pnpmUsable alone is insufficient to trigger the suite. This is documented in the file header, but an environment with only pnpm available will produce a potentially confusing skip. Not a blocker, just worth noting for CI job setup.

  • Yarn 1 upgrade path from rc.1 — Any rc.1 adopter who relied on transitive hoisting of webpack/@rspack/core from the old dependencies block will see those disappear on upgrade. Yarn 1 surfaces peer warnings but won't auto-install. The docs cover this in the PM support table, though a brief note in CHANGELOG about the rc.1 → rc.2 behavioral delta could help.


Summary

The structural changes are correct and well-tested. One real bug in the smoke test (slice(1)slice(2)) would cause all pnpm wrapper-only assertions to fail when the opt-in suite is enabled. Everything else is in good shape.

@claude

claude Bot commented May 24, 2026

Copy link
Copy Markdown
Contributor

Code Review: Restructure supplemental package dependencies (#1131)

Overall: Solid change. The design is correct, the documentation is thorough, and the test coverage is the best I have seen for a package metadata change.


What the PR does

Moves bundler singletons (webpack, webpack-cli, webpack-assets-manifest / @rspack/core, @rspack/cli, rspack-manifest-plugin) from dependencies → required peerDependencies in the supplemental packages, widens version constraints from ~ to ^, and moves terser-webpack-plugin from optional peer to direct dependency of shakapacker-webpack. Documentation and tests are updated throughout.


Design consideration: terser-webpack-plugin as direct dep vs. required peer

This is the most substantive design question in the PR. Making it a direct dep works for npm 7+ (flat hoisting puts it at app root) but produces a worse outcome than a required peer for pnpm users:

Placement npm 7+ pnpm (auto-install-peers=true, pnpm 8+ default)
Direct dep (current) App root via hoisting ✓ Inside .pnpm/shakapacker-webpack.../ — NOT accessible to shakapacker
Required peer App root via auto-install ✓ App root via auto-install ✓

The call site is requireOrError("terser-webpack-plugin") inside shakapacker (not shakapacker-webpack). Under pnpm's strict layout, shakapacker-webpack's direct dep lives in an isolated subtree that shakapacker cannot reach. If it were a required peer, pnpm's auto-install-peers=true would install it at app root where shakapacker can find it — same result as npm, without any explicit app-level declaration.

The migration docs correctly document the current pnpm workaround (add terser-webpack-plugin explicitly), but a required peer would make it automatic on modern pnpm. The one downside is yarn 1, which only warns about missing peers rather than installing them — but the migration guide already tells yarn 1 users to add explicit deps anyway.

Worth considering before GA.


Other observations

Version ranges

  • webpack: "^5.101.0" is a relatively high floor but matches main shakapacker's peer range, so consistent.
  • esbuild: ">=0.14.0 <1.0.0" is a clean simplification of the previous per-minor OR chain.
  • @rspack/plugin-react-refresh: "^1.0.0 || ^2.0.0" (dropping ^2.0.0-0) correctly excludes 2.x pre-releases as documented.

Tests

The package-metadata.test.js design is excellent. collectTildeOffenders correctly handles OR-separated ranges ("^1.0.0 || ~2.0.0"), and expectOnlyKnownMismatches catches stale allowlist entries as well as unexpected regressions.

The install-smoke.test.js opt-in pattern (RUN_INSTALL_SMOKE=1) is the right call for a test that shells out to package managers. The .npmrc approach to pin node-linker=isolated explicitly defends against a caller environment with shamefully-hoist=true masking the pnpm failure mode.

RC version strings

packages/shakapacker-webpack/package.json and packages/shakapacker-rspack/package.json both carry "shakapacker": "~10.1.0-rc.1" in their dependencies. The docs note these must be updated to "~10.1.0" before GA publish. A release script assertion or CI check on this would prevent an RC pin shipping in the GA package.


Minor issues are attached as inline comments.

"webpack": "~5.106.2",
"webpack-cli": "~7.0.2",
"webpack-assets-manifest": "~6.5.1"
"terser-webpack-plugin": "^5.3.1"

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.

Design question: Under pnpm with auto-install-peers=true (pnpm 8+ default), a required peer of shakapacker-webpack would be installed at the app root — where shakapacker's requireOrError("terser-webpack-plugin") can find it. As a direct dep, it lands inside .pnpm/shakapacker-webpack.../node_modules/, which pnpm's strict isolation keeps invisible to shakapacker. The end result is that pnpm users still need to declare terser-webpack-plugin explicitly (correctly documented), even though auto-install-peers=true would handle it automatically if this were a required peer. The only case where a direct dep is strictly better than a required peer is yarn 1, which only warns about missing peers — but the migration guide already tells yarn 1 users to add explicit deps anyway.

return Object.entries(peers)
.filter(
([name, supplementalRange]) =>
mainPeers[name] && mainPeers[name] !== supplementalRange

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 equality check (mainPeers[name] !== supplementalRange) flags any deviation from main, including cases where the supplemental has a wider range than main (e.g., supplemental adds an OR arm that main doesn't have). The comment on line 106 says "should never be stricter", but the test is more conservative than that — it catches drift in either direction. This is probably the right call (prevents silent widening too), but the comment slightly undersells what the test actually enforces. Consider updating the comment to "supplemental peer ranges should match main exactly, except for known intentional narrowings".

"terser-webpack-plugin": "^5.3.1"
},
requiredPeers: ["webpack", "webpack-cli", "webpack-assets-manifest"],
appRootAssertions: ["terser-webpack-plugin"]

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 appRootAssertions array (["terser-webpack-plugin"] for webpack) is checked in the npm wrapper-only test and the pnpm explicit-deps test, but not in the pnpm wrapper-only test (line ~1129 in the diff only checks requiredPeers). Adding a negative assertion there — expect(resolvesFromAppRoot(dir, "terser-webpack-plugin")).toBe(false) — would document the exact pnpm failure mode that the migration guide warns about, and prevent a future pnpm version change (e.g., a shamefully-hoist policy shift) from silently masking the documented behaviour.

@justin808
justin808 merged commit ba495cd into main May 24, 2026
63 checks passed
@justin808
justin808 deleted the jg-conductor/issue-1131-review branch May 24, 2026 03:09
justin808 added a commit that referenced this pull request May 24, 2026
…coverage-v1

* origin/main:
  Teach /update-changelog to drop RC-only regression fixes (#1125)
  Restructure supplemental package dependencies (#1131) (#1133)
  [codex] Add Shakapacker brand assets (#1135)
  [codex] Fix shakapacker config helper binstubs (#1132)
  Surface v10.1 supplemental packages from README and key docs (#1134)
  CHANGELOG: merge PR #1120 into PR #1110 entry for v10.1.0-rc.1 (#1126)

# Conflicts:
#	lib/install/bin/diff-bundler-config
#	lib/install/bin/shakapacker-config
#	package/configExporter/cli.ts
#	spec/dummy/bin/shakapacker-config
#	spec/shakapacker/binstub_sync_spec.rb
#	spec/shakapacker/helper_binstubs_spec.rb
justin808 added a commit that referenced this pull request May 24, 2026
### Summary

Updates `CHANGELOG.md` for `v10.1.0-rc.2` by collapsing the prior
`v10.1.0-rc.1` prerelease notes into the new section, moving PR #1133
from Unreleased into Changed, and adding the PR #1132 config helper
binstub/export task fix.
The release compare links now point Unreleased from `v10.1.0-rc.2` and
compare `v10.0.0...v10.1.0-rc.2`.

### Pull Request checklist

- [x] ~Add/update test to cover these changes~
- [x] ~Update documentation~
- [x] Update CHANGELOG file

### Other Information

Validated with `yarn lint`, `git diff --check`, and review of `git diff
origin/main...`.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Documentation-only change updating release notes and compare links; no
runtime code or dependency changes in this PR.
> 
> **Overview**
> Updates `CHANGELOG.md` to publish `v10.1.0-rc.2` by moving the prior
prerelease content under the new version header, adding the
`shakapacker:export_bundler_config`/binstub fix entry (PR #1132), and
relocating the dependency declaration change (PR #1133) from
*Unreleased* into `Changed`.
> 
> Also updates the bottom compare links so `Unreleased` now compares
from `v10.1.0-rc.2` and adds the `v10.1.0-rc.2` tag comparison link.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
4d4dfea. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
@justin808 justin808 mentioned this pull request May 25, 2026
5 tasks
@coderabbitai coderabbitai Bot mentioned this pull request May 27, 2026
3 tasks
justin808 added a commit that referenced this pull request May 27, 2026
## Summary

Stamps the v10.1.0 stable release header (was `v10.1.0-rc.2`) and
curates the prerelease entries per `/update-changelog` guidance.

## Curation applied

- **Merged PR #1133 → PR #1096** (Added: supplemental npm packages).
#1133 restructured the supplemental deps from direct → peer in rc.2, so
the original entry's "bundle the managed-build stack as direct
dependencies" description was wrong for stable consumers. Rewrote to
describe the final peer-deps shape (auto-installed on npm 7+) and credit
both PRs. The #1133 standalone Changed entry was removed.
- **Merged PR #1132 → PR #1104** (Fixed: ESM binstubs). #1132 fixed the
`export_bundler_config` dispatch regression that #1104 introduced in
rc.1 when switching from JS to Ruby binstubs; stable users never see the
bug, so the fix is RC-only churn that belongs folded into the original
entry. Also folded in #1132's minor improvements (NODE_ENV from
RAILS_ENV mapping, Node lookup without execution, friendlier non-Error
CLI messages).
- **Merged PR #1106 → PR #1086** (Fixed: local-path version detection).
Both shipped in rc.0 as iterations of the same regex fix; merged into a
single entry covering `../`, `./`, and `file:` patterns.
- **Lightened Migration Notes**: removed "bundled stack" language now
that supplementals declare required peers instead of direct deps.
- **Skipped post-rc.2 commit**: PR #1138 (skill tooling guidance) is
internal — not user-visible.
- **Compare links**: `[Unreleased]` now points from `v10.1.0`; replaced
`[v10.1.0-rc.2]` with `[v10.1.0]: v10.0.0...v10.1.0`.

## Test plan

- [x] `yarn lint` passes
- [x] `yarn prettier --check CHANGELOG.md` passes
- [x] File ends with trailing newline
- [x] No duplicate section headings
- [x] All merged PRs credited

After merge, run `bundle exec rake release` (no args) — the rake task
picks up `v10.1.0` from CHANGELOG.md and auto-creates the GitHub
release.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Documentation-only changelog edits with no runtime or dependency
behavior changes.
> 
> **Overview**
> **Curates `CHANGELOG.md` for the v10.1.0 stable release** (replacing
the `v10.1.0-rc.2` header dated May 25, 2026) so release notes match
what stable users actually ship.
> 
> Prerelease churn is folded into fewer, accurate bullets: supplemental
npm packages (**#1096** + **#1133**) now describe **required peer
dependencies** (not direct “bundled” deps), with the standalone
**#1133** *Changed* entry removed; ESM/config binstub fixes (**#1104** +
**#1132**) and local-path version detection (**#1086** + **#1106**) are
single *Fixed* items. **Migration Notes** no longer imply a direct
bundled stack on supplementals.
> 
> Footer compare links: **`[Unreleased]`** from `v10.1.0`,
**`[v10.1.0]`** replaces the rc.2 link (`v10.0.0...v10.1.0`).
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
2b57d5f. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
justin808 added a commit that referenced this pull request Jun 9, 2026
…nalyze-webpack-output

* origin/main: (164 commits)
  Make Rspack the default bundler for new installs (#1150)
  Add "Why Shakapacker with Rspack" comparison guide (#1155)
  Support sass-loader v17 (#1141)
  [codex] Fix Claude workflow permissions (#1153)
  Fix README CI badges and add prominent docs link (#1152)
  Trim README into a docs-site index; relocate content to docs/ and fix deep links (#1148)
  Tighten helper binstub parity and PATH lookup (#1128)
  Optimize CI: minimal version matrix on PRs, full matrix on main (#1151)
  Fix instance-scoped compiler strategies (#1147)
  Fix Rspack devServer in static watch mode (#1142)
  Add homepage links for supplemental npm packages (#1145)
  Release 10.1.0
  Update CHANGELOG.md for v10.1.0 (#1139)
  Expand prerelease-to-stable changelog coalesce guidance in /update-changelog (#1138)
  Release 10.1.0-rc.2
  [codex] Update changelog for v10.1.0-rc.2 (#1136)
  Teach /update-changelog to drop RC-only regression fixes (#1125)
  Restructure supplemental package dependencies (#1131) (#1133)
  [codex] Add Shakapacker brand assets (#1135)
  [codex] Fix shakapacker config helper binstubs (#1132)
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation p2 Medium: enhancements, docs, quality improvements

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dependency feedback for v10.1.0

1 participant