shakapacker 10.0.0 → 10.1.0.rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +37 -1
  3. data/README.md +23 -14
  4. data/lib/install/package.json +2 -2
  5. data/lib/shakapacker/compiler.rb +29 -0
  6. data/lib/shakapacker/doctor.rb +325 -13
  7. data/lib/shakapacker/version.rb +1 -1
  8. data/lib/shakapacker/version_checker.rb +24 -3
  9. data/shakapacker.gemspec +2 -6
  10. metadata +3 -269
  11. data/.claude/commands/address-review.md +0 -234
  12. data/.claude/commands/update-changelog.md +0 -354
  13. data/.claude/commands/verify.md +0 -12
  14. data/.claude/rules/coding-style.md +0 -7
  15. data/.claude/rules/git-workflow.md +0 -6
  16. data/.claude/rules/open-source.md +0 -7
  17. data/.claude/rules/testing.md +0 -9
  18. data/.github/FUNDING.yml +0 -1
  19. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -21
  20. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
  21. data/.github/PULL_REQUEST_TEMPLATE.md +0 -21
  22. data/.github/STATUS.md +0 -1
  23. data/.github/actionlint-matcher.json +0 -17
  24. data/.github/workflows/claude-code-review.yml +0 -45
  25. data/.github/workflows/claude.yml +0 -55
  26. data/.github/workflows/dummy.yml +0 -48
  27. data/.github/workflows/eslint-validation.yml +0 -46
  28. data/.github/workflows/generator.yml +0 -86
  29. data/.github/workflows/node.yml +0 -171
  30. data/.github/workflows/ruby.yml +0 -132
  31. data/.github/workflows/test-bundlers.yml +0 -183
  32. data/.github/workflows/trigger-docs-site.yml +0 -24
  33. data/.gitignore +0 -46
  34. data/.husky/pre-commit +0 -2
  35. data/.node-version +0 -1
  36. data/.npmignore +0 -56
  37. data/.prettierignore +0 -8
  38. data/.rspec +0 -1
  39. data/.rubocop.yml +0 -231
  40. data/.yalcignore +0 -26
  41. data/CLAUDE.md +0 -74
  42. data/CONTRIBUTING.md +0 -379
  43. data/ESLINT_TECHNICAL_DEBT.md +0 -165
  44. data/Gemfile +0 -5
  45. data/Gemfile.development_dependencies +0 -15
  46. data/Rakefile +0 -88
  47. data/TODO.md +0 -58
  48. data/TODO_v9.md +0 -97
  49. data/__mocks__/nonexistent/package.json +0 -4
  50. data/__mocks__/sass-loader/package.json +0 -4
  51. data/bin/conductor-exec +0 -24
  52. data/bin/diff-bundler-config +0 -64
  53. data/bin/setup +0 -36
  54. data/bin/shakapacker-config +0 -11
  55. data/conductor-setup.sh +0 -147
  56. data/conductor.json +0 -9
  57. data/config/README.md +0 -3
  58. data/config/shakapacker.yml +0 -1
  59. data/docs/api-reference.md +0 -497
  60. data/docs/cdn_setup.md +0 -384
  61. data/docs/common-upgrades.md +0 -695
  62. data/docs/config-diff.md +0 -159
  63. data/docs/configuration.md +0 -845
  64. data/docs/css-modules-export-mode.md +0 -525
  65. data/docs/customizing_babel_config.md +0 -89
  66. data/docs/deployment.md +0 -195
  67. data/docs/developing_shakapacker.md +0 -35
  68. data/docs/early_hints.md +0 -433
  69. data/docs/early_hints_manual_api.md +0 -454
  70. data/docs/feature_testing.md +0 -492
  71. data/docs/node_package_api.md +0 -70
  72. data/docs/optional-peer-dependencies.md +0 -205
  73. data/docs/peer-dependencies.md +0 -77
  74. data/docs/precompile_hook.md +0 -487
  75. data/docs/preventing_fouc.md +0 -132
  76. data/docs/react.md +0 -83
  77. data/docs/releasing.md +0 -293
  78. data/docs/rspack.md +0 -218
  79. data/docs/rspack_migration_guide.md +0 -849
  80. data/docs/sprockets.md +0 -11
  81. data/docs/style_loader_vs_mini_css.md +0 -48
  82. data/docs/subresource_integrity.md +0 -60
  83. data/docs/transpiler-migration.md +0 -212
  84. data/docs/transpiler-performance.md +0 -200
  85. data/docs/troubleshooting.md +0 -521
  86. data/docs/typescript-migration.md +0 -388
  87. data/docs/typescript.md +0 -103
  88. data/docs/using_esbuild_loader.md +0 -128
  89. data/docs/using_swc_loader.md +0 -258
  90. data/docs/v6_upgrade.md +0 -195
  91. data/docs/v7_upgrade.md +0 -61
  92. data/docs/v8_upgrade.md +0 -193
  93. data/docs/v9_upgrade.md +0 -618
  94. data/eslint.config.fast.js +0 -254
  95. data/eslint.config.js +0 -309
  96. data/gemfiles/Gemfile-rails-edge +0 -12
  97. data/gemfiles/Gemfile-rails.6.0.x +0 -10
  98. data/gemfiles/Gemfile-rails.6.1.x +0 -12
  99. data/gemfiles/Gemfile-rails.7.0.x +0 -12
  100. data/gemfiles/Gemfile-rails.7.1.x +0 -11
  101. data/gemfiles/Gemfile-rails.7.2.x +0 -11
  102. data/gemfiles/Gemfile-rails.8.0.x +0 -11
  103. data/jest.config.js +0 -12
  104. data/knip.ts +0 -69
  105. data/package/.npmignore +0 -4
  106. data/package/babel/preset.ts +0 -59
  107. data/package/config.ts +0 -189
  108. data/package/configExporter/buildValidator.ts +0 -906
  109. data/package/configExporter/cli.ts +0 -1748
  110. data/package/configExporter/configDocs.ts +0 -102
  111. data/package/configExporter/configFile.ts +0 -663
  112. data/package/configExporter/fileWriter.ts +0 -112
  113. data/package/configExporter/index.ts +0 -15
  114. data/package/configExporter/types.ts +0 -159
  115. data/package/configExporter/yamlSerializer.ts +0 -391
  116. data/package/dev_server.ts +0 -27
  117. data/package/env.ts +0 -92
  118. data/package/environments/__type-tests__/rspack-plugin-compatibility.ts +0 -36
  119. data/package/environments/base.ts +0 -147
  120. data/package/environments/development.ts +0 -88
  121. data/package/environments/production.ts +0 -82
  122. data/package/environments/test.ts +0 -55
  123. data/package/environments/types.ts +0 -98
  124. data/package/esbuild/index.ts +0 -40
  125. data/package/index.d.ts +0 -72
  126. data/package/index.d.ts.template +0 -72
  127. data/package/index.ts +0 -104
  128. data/package/loaders.d.ts +0 -28
  129. data/package/optimization/rspack.ts +0 -36
  130. data/package/optimization/webpack.ts +0 -55
  131. data/package/plugins/envFilter.ts +0 -82
  132. data/package/plugins/rspack.ts +0 -119
  133. data/package/plugins/webpack.ts +0 -82
  134. data/package/rspack/index.ts +0 -91
  135. data/package/rules/babel.ts +0 -19
  136. data/package/rules/coffee.ts +0 -6
  137. data/package/rules/css.ts +0 -3
  138. data/package/rules/erb.ts +0 -21
  139. data/package/rules/esbuild.ts +0 -10
  140. data/package/rules/file.ts +0 -41
  141. data/package/rules/jscommon.ts +0 -27
  142. data/package/rules/less.ts +0 -22
  143. data/package/rules/raw.ts +0 -28
  144. data/package/rules/rspack.ts +0 -174
  145. data/package/rules/sass.ts +0 -21
  146. data/package/rules/stylus.ts +0 -22
  147. data/package/rules/swc.ts +0 -10
  148. data/package/rules/webpack.ts +0 -15
  149. data/package/swc/index.ts +0 -54
  150. data/package/types/README.md +0 -90
  151. data/package/types/index.ts +0 -69
  152. data/package/types.ts +0 -105
  153. data/package/utils/bundlerUtils.ts +0 -232
  154. data/package/utils/configPath.ts +0 -6
  155. data/package/utils/debug.ts +0 -45
  156. data/package/utils/defaultConfigPath.ts +0 -7
  157. data/package/utils/ensureManifestExists.ts +0 -17
  158. data/package/utils/errorCodes.ts +0 -249
  159. data/package/utils/errorHelpers.ts +0 -152
  160. data/package/utils/getStyleRule.ts +0 -75
  161. data/package/utils/helpers.ts +0 -99
  162. data/package/utils/inliningCss.ts +0 -8
  163. data/package/utils/pathValidation.ts +0 -207
  164. data/package/utils/requireOrError.ts +0 -24
  165. data/package/utils/snakeToCamelCase.ts +0 -5
  166. data/package/utils/typeGuards.ts +0 -388
  167. data/package/utils/validateDependencies.ts +0 -61
  168. data/package/webpack-types.d.ts +0 -33
  169. data/package/webpackDevServerConfig.ts +0 -151
  170. data/package.json +0 -228
  171. data/prettier.config.js +0 -4
  172. data/scripts/remove-use-strict.js +0 -44
  173. data/scripts/type-check-no-emit.js +0 -27
  174. data/test/configExporter/buildValidator.test.js +0 -1295
  175. data/test/configExporter/configFile.test.js +0 -393
  176. data/test/configExporter/integration.test.js +0 -262
  177. data/test/helpers.js +0 -67
  178. data/test/package/bundlerUtils.rspack.test.js +0 -149
  179. data/test/package/bundlerUtils.test.js +0 -97
  180. data/test/package/config.test.js +0 -111
  181. data/test/package/configExporter/cli.test.js +0 -440
  182. data/test/package/configExporter/types.test.js +0 -163
  183. data/test/package/configExporter.test.js +0 -491
  184. data/test/package/dev_server.test.js +0 -44
  185. data/test/package/development.test.js +0 -52
  186. data/test/package/env.test.js +0 -92
  187. data/test/package/environments/base.test.js +0 -124
  188. data/test/package/environments/development.test.js +0 -59
  189. data/test/package/environments/production.test.js +0 -115
  190. data/test/package/helpers.test.js +0 -11
  191. data/test/package/index.test.js +0 -54
  192. data/test/package/plugins/envFiltering.test.js +0 -453
  193. data/test/package/plugins/webpackSubresourceIntegrity.test.js +0 -89
  194. data/test/package/production.test.js +0 -41
  195. data/test/package/rspack/index.test.js +0 -293
  196. data/test/package/rspack/optimization.test.js +0 -86
  197. data/test/package/rspack/plugins.test.js +0 -185
  198. data/test/package/rspack/rules.test.js +0 -229
  199. data/test/package/rules/babel.test.js +0 -85
  200. data/test/package/rules/esbuild.test.js +0 -68
  201. data/test/package/rules/file.test.js +0 -87
  202. data/test/package/rules/raw.test.js +0 -45
  203. data/test/package/rules/sass-version-parsing.test.js +0 -71
  204. data/test/package/rules/sass.test.js +0 -28
  205. data/test/package/rules/sass1.test.js +0 -26
  206. data/test/package/rules/sass16.test.js +0 -24
  207. data/test/package/rules/swc.test.js +0 -70
  208. data/test/package/rules/webpack.test.js +0 -35
  209. data/test/package/staging.test.js +0 -41
  210. data/test/package/test.test.js +0 -37
  211. data/test/package/transpiler-defaults.test.js +0 -169
  212. data/test/package/utils/ensureManifestExists.test.js +0 -51
  213. data/test/package/webpackDevServerConfig.test.js +0 -55
  214. data/test/package/yamlSerializer.test.js +0 -204
  215. data/test/peer-dependencies.sh +0 -85
  216. data/test/resolver.js +0 -44
  217. data/test/scripts/remove-use-strict.test.js +0 -125
  218. data/test/typescript/build.test.js +0 -118
  219. data/test/typescript/environments.test.js +0 -107
  220. data/test/typescript/pathValidation.test.js +0 -186
  221. data/test/typescript/requireOrError.test.js +0 -49
  222. data/test/typescript/securityValidation.test.js +0 -182
  223. data/tools/README.md +0 -134
  224. data/tools/css-modules-v9-codemod.js +0 -179
  225. data/tsconfig.eslint.json +0 -9
  226. data/tsconfig.json +0 -38
  227. data/yarn.lock +0 -6806
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '08b8b40c44fa65d3a426d14596e15d7e5da7e486f0659c03822aa34a7f7e5474'
4
- data.tar.gz: eadf1441efba9b99b1e9918075d12eb5cb08f4d71c63bb00d8c58c93d36d4ab6
3
+ metadata.gz: 632a8c1773b8b7168ac13d0b0675e886c3d1359635ddd695b32e12218748708c
4
+ data.tar.gz: 8dcd25b6593ff26d8b277c01c54a1df970e69ced4166a5a918c9123fc04bdea1
5
5
  SHA512:
6
- metadata.gz: c50b9fe63645ef4f2890ed6e0ddeeb73a21afe51e827ed39d5daebafda00517bf5299e72ad056ecf74650214d21407f0de948a876a7a5ac789de1581240f8e64
7
- data.tar.gz: 6651deb390179edc5aea7f0abe9db2a6065b5674be9e4d8eee13de5642d590f600673eab5f7178caa12d14547cce9e1ad376e784ebee2c1377205e9112e38a05
6
+ metadata.gz: 461f192f52757479deb8251b434b34b7c1839f923ae02725917f85ba96b47e1a1f9d45cc1dabf989f9fa33156dbcfa479d613143f2f2e0b95152a589be9bb852
7
+ data.tar.gz: a6070c988e4a317f91210f642eb25471fc2827c67e7cdad659a0c040839c0cd2c3adb1b9293eceb17a7586fd2128c4d46a776a8b387f5322bd53a348ec5ece82
data/CHANGELOG.md CHANGED
@@ -9,6 +9,41 @@
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [v10.1.0-rc.0] - May 20, 2026
13
+
14
+ ### Added
15
+
16
+ - **Added supplemental npm packages `shakapacker-webpack` and `shakapacker-rspack`**. [PR #1096](https://github.com/shakacode/shakapacker/pull/1096) by [justin808](https://github.com/justin808). Optional packages that lockstep with core and bundle the managed-build stack as direct `dependencies` (so a single `yarn add shakapacker-webpack` pulls in `shakapacker`, `webpack`, `webpack-cli`, and `webpack-assets-manifest`; the rspack package bundles `shakapacker`, `@rspack/core`, `@rspack/cli`, and `rspack-manifest-plugin`). Optional features (transpilers, dev-server, CSS preprocessors, react-refresh) remain as opt-in `peerDependencies` so SCSS/native-binding bloat isn't forced on every install. The wrappers emit structured warnings (`SHAKAPACKER_BUNDLER_MISMATCH`, `SHAKAPACKER_NO_TRANSPILER`) when `config.assets_bundler` or `javascript_transpiler` doesn't match the installed peers. See the [v10.1 migration guide](docs/migration/v10.1-supplemental-packages.md) for adoption steps and [`docs/dependency-strategy.md`](docs/dependency-strategy.md) for the design rationale and v11 roadmap.
17
+ - **Added `shakapacker:doctor` check for disabled Rspack cache**. [PR #1100](https://github.com/shakacode/shakapacker/pull/1100) by [justin808](https://github.com/justin808). The doctor now inspects the Rspack config file for an explicit `cache: false`, warns when found (disabling cache causes significantly slower builds), and also flags Rspack v1 installs (where persistent cache is experimental) with a recommendation to upgrade to v2.
18
+ - **Added a `shakapacker:doctor` hint to compiler output**. [PR #1100](https://github.com/shakacode/shakapacker/pull/1100) by [justin808](https://github.com/justin808). The compiler now logs a one-time tip suggesting `bundle exec rake shakapacker:doctor` after a failed compilation, so healthy build loops stay quiet.
19
+
20
+ ### Migration Notes
21
+
22
+ - **Simplify your `package.json` by adopting a supplemental package**. Existing apps can drop the explicit managed-build deps from `devDependencies` and rely on the bundled stack:
23
+ - **Rspack apps** can replace `shakapacker` + `@rspack/core` + `@rspack/cli` + `rspack-manifest-plugin` with a single `shakapacker-rspack`. See `packages/shakapacker-rspack/README.md` §"Simplifying an existing rspack install" for the before/after.
24
+ - **Webpack apps** can replace `shakapacker` + `webpack` + `webpack-cli` + `webpack-assets-manifest` with a single `shakapacker-webpack`. See `packages/shakapacker-webpack/README.md` §"Simplifying an existing webpack install" for the before/after.
25
+ - Optional peers (transpilers, `webpack-dev-server`, CSS preprocessors, react-refresh) stay only if your app uses those features.
26
+ - Adoption is opt-in: leaving your `package.json` untouched on v10.1 also continues to work.
27
+
28
+ - **Adopting `shakapacker-webpack` requires `webpack-assets-manifest@^6.0.0`**. Core `shakapacker` still accepts both v5 and v6 (`^5.0.6 || ^6.0.0`), but `shakapacker-webpack` pins `~6.5.1`. Apps still on `webpack-assets-manifest@5.x` must upgrade when switching to the supplemental package; v6 fixed an ENOENT crash on clean builds with `merge: true` and dropped a Node 14 install path. See [the v5→v6 release notes](https://github.com/webdeveric/webpack-assets-manifest/releases) and `packages/shakapacker-webpack/README.md` for details.
29
+
30
+ ### ⚠️ Breaking Changes
31
+
32
+ - **Breaking: tightened `package.json` `engines.node` to `^20.19.0 || >=22.12.0`**. [PR #1099](https://github.com/shakacode/shakapacker/pull/1099) by [justin808](https://github.com/justin808). Raised from `>= 20`, dropping support for Node 20.0.0–20.18.x and Node 21.x to match `@rspack/core@2.0.0-rc.0`. Consumers on those versions will hit an engine error with `--engine-strict` or yarn workspaces and need to upgrade. The PR also bumps `.node-version` to `22.20.0` and updates `conductor-setup.sh` to enforce the same disjoint range up front, so contributors get a clear error before `yarn install` fails with a confusing engine mismatch.
33
+
34
+ ### Changed
35
+
36
+ - **Changed `shakapacker:install` to default fresh Rspack installs to v2 (`^2.0.0-0`)**. [PR #1091](https://github.com/shakacode/shakapacker/pull/1091) by [ihabadham](https://github.com/ihabadham). `lib/install/package.json` now declares `@rspack/core` and `@rspack/cli` as `^1.0.0 || ^2.0.0-0`; fresh installs pick the v2 range. Existing apps are unaffected. Note: Rspack v2 requires Node.js 20.19.0+.
37
+ - **Slimmed the published gem from ~486K (294 files) to ~121K (75 files)**. [PR #1110](https://github.com/shakacode/shakapacker/pull/1110) by [justin808](https://github.com/justin808). Replaced the broad `git ls-files` gem manifest with an explicit runtime/install allowlist (`CHANGELOG.md`, `MIT-LICENSE`, `README.md`, gemspec, `lib`, `sig`), excluding repo-only docs, tests, JavaScript package source, CI/tooling files, and `test_files` metadata from the published gem. Fixes [#987](https://github.com/shakacode/shakapacker/issues/987).
38
+
39
+ ### Fixed
40
+
41
+ - **Fixed Rspack React Refresh plugin loading with `@rspack/plugin-react-refresh` v2**. [PR #1116](https://github.com/shakacode/shakapacker/pull/1116) by [justin808](https://github.com/justin808). Shakapacker now reads the v2 named `ReactRefreshRspackPlugin` export while retaining compatibility with v1 direct/default CommonJS export shapes, preventing `TypeError: ReactRefreshRspackPlugin is not a constructor` during rspack dev-server startup.
42
+ - **Widened `@rspack/plugin-react-refresh` peer range to `^1.0.0 || ^2.0.0-0`**. [PR #1091](https://github.com/shakacode/shakapacker/pull/1091) by [ihabadham](https://github.com/ihabadham). Fixes the `ERESOLVE` conflict when installing `@rspack/plugin-react-refresh@^2.0.0` alongside `shakapacker@10.0.0`.
43
+ - **Fixed `NodePackageVersion#find_version` for local-path `shakapacker` installs (e.g. `yalc`, `file:`, relative paths)**. [PR #1086](https://github.com/shakacode/shakapacker/pull/1086) by [justin808](https://github.com/justin808). The version check now consults `package.json` first and short-circuits on `../` or `file:` dependencies, so stale lockfile semvers no longer trigger false gem↔node version mismatches. `package_json_dependency` also consults `devDependencies` in addition to `dependencies`. The `LOCAL_PATH_REGEX` constant replaces a duplicated inline regex and anchors both alternatives to the start of the string, removing a latent false-positive on version strings containing `..` mid-value.
44
+ - **Detected single-dot (`./...`) local-path declarations in `NodePackageVersion#find_version`**. [PR #1106](https://github.com/shakacode/shakapacker/pull/1106) by [justin808](https://github.com/justin808). Extended `LOCAL_PATH_REGEX` to treat `./vendor/shakapacker`-style declarations as local-path installs (alongside the `../` and `file:` patterns added in [#1086](https://github.com/shakacode/shakapacker/pull/1086)), so version checks short-circuit before consulting potentially stale lockfile semvers. Fixes [#1103](https://github.com/shakacode/shakapacker/issues/1103).
45
+ - **Fix rspack setup not reusing certain shared webpack-rspack config settings**. [PR #1085](https://github.com/shakacode/shakapacker/pull/1085) by [brunodccarvalho](https://github.com/brunodccarvalho). Default config changes include `optimization.splitChunks.chunks="all"`, `optimization.runtimeChunk="single"`, the webpack compression plugin in production, and the removal of minimization plugins in development. Fixes [#984](https://github.com/shakacode/shakapacker/issues/984).
46
+
12
47
  ## [v10.0.0] - April 8, 2026
13
48
 
14
49
  ### Added
@@ -909,7 +944,8 @@ Note: [Rubygem is 6.3.0.pre.rc.1](https://rubygems.org/gems/shakapacker/versions
909
944
 
910
945
  See [CHANGELOG.md in rails/webpacker (up to v5.4.3)](https://github.com/rails/webpacker/blob/master/CHANGELOG.md)
911
946
 
912
- [Unreleased]: https://github.com/shakacode/shakapacker/compare/v10.0.0...main
947
+ [Unreleased]: https://github.com/shakacode/shakapacker/compare/v10.1.0-rc.0...main
948
+ [v10.1.0-rc.0]: https://github.com/shakacode/shakapacker/compare/v10.0.0...v10.1.0-rc.0
913
949
  [v10.0.0]: https://github.com/shakacode/shakapacker/compare/v9.7.0...v10.0.0
914
950
  [v9.7.0]: https://github.com/shakacode/shakapacker/compare/v9.6.1...v9.7.0
915
951
  [v9.6.1]: https://github.com/shakacode/shakapacker/compare/v9.6.0...v9.6.1
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
- # Shakapacker (v9)
1
+ # Shakapacker (v10)
2
2
 
3
3
  ---
4
4
 
5
- _🚀 Shakapacker 9 supports [Rspack](https://rspack.rs/)! 10x faster than webpack!_
5
+ _🚀 Shakapacker 10 supports [Rspack](https://rspack.rs/) up to 17x faster than webpack per [upstream benchmarks](./docs/transpiler-performance.md#published-benchmarks)!_
6
6
 
7
7
  _📖 **Full documentation at [shakapacker.com](https://shakapacker.com)**_
8
8
 
@@ -11,7 +11,8 @@ _📖 **Full documentation at [shakapacker.com](https://shakapacker.com)**_
11
11
  _Official, actively maintained successor to [rails/webpacker](https://github.com/rails/webpacker). ShakaCode stands behind the long-term maintenance and development of this project for the Rails community._
12
12
 
13
13
  - ⚠️ See the [6-stable](https://github.com/shakacode/shakapacker/tree/6-stable) branch for Shakapacker v6.x code and documentation. :warning:
14
- - **See [V9 Upgrade](./docs/v9_upgrade.md) for upgrading from the v8 release.**
14
+ - **See the [v10.0.0 release notes](https://github.com/shakacode/shakapacker/releases/tag/v10.0.0) for upgrading from v9 to v10.**
15
+ - **See [V9 Upgrade](./docs/v9_upgrade.md) for upgrading from v8 to v9.**
15
16
  - See [V8 Upgrade](./docs/v8_upgrade.md) for upgrading from the v7 release.
16
17
  - See [V7 Upgrade](./docs/v7_upgrade.md) for upgrading from the v6 release.
17
18
  - See [V6 Upgrade](./docs/v6_upgrade.md) for upgrading from v5 or prior v6 releases.
@@ -21,7 +22,7 @@ _Official, actively maintained successor to [rails/webpacker](https://github.com
21
22
  [![Rubocop](https://github.com/shakacode/shakapacker/workflows/Rubocop/badge.svg)](https://github.com/shakacode/shakapacker/actions)
22
23
  [![JS lint](https://github.com/shakacode/shakapacker/workflows/JS%20lint/badge.svg)](https://github.com/shakacode/shakapacker/actions)
23
24
 
24
- [![node.js](https://img.shields.io/badge/node-%3E%3D%2020-brightgreen.svg)](https://www.npmjs.com/package/shakapacker)
25
+ [![node.js](https://img.shields.io/badge/node-%5E20.19.0%20%7C%7C%20%3E%3D22.12.0-brightgreen.svg)](https://www.npmjs.com/package/shakapacker)
25
26
  [![Gem](https://img.shields.io/gem/v/shakapacker.svg)](https://rubygems.org/gems/shakapacker)
26
27
  [![npm version](https://badge.fury.io/js/shakapacker.svg)](https://badge.fury.io/js/shakapacker)
27
28
 
@@ -133,7 +134,7 @@ Here's a testimonial from Jon Rajavuori of [Academia.edu](https://www.academia.e
133
134
 
134
135
  - Ruby 2.7+
135
136
  - Rails 5.2+
136
- - Node.js 20+
137
+ - Node.js `^20.19.0` or `>=22.12.0`
137
138
 
138
139
  ## Features
139
140
 
@@ -159,6 +160,8 @@ _Requires extra packages to be installed._
159
160
 
160
161
  ## Installation
161
162
 
163
+ See the [Installation guide](./docs/installation.md) for a step-by-step walkthrough.
164
+
162
165
  ### Rails v6+
163
166
 
164
167
  With Rails v6+, skip JavaScript for a new app and follow below Manual Installation Steps to manually add the `shakapacker` gem to your Gemfile.
@@ -238,7 +241,7 @@ Depending on your setup, you'll need different subsets of the optional peer depe
238
241
  ```json
239
242
  {
240
243
  "dependencies": {
241
- "shakapacker": "^9.0.0",
244
+ "shakapacker": "^10.0.0",
242
245
  "@babel/core": "^7.17.9",
243
246
  "@babel/plugin-transform-runtime": "^7.17.0",
244
247
  "@babel/preset-env": "^7.16.11",
@@ -261,7 +264,7 @@ Depending on your setup, you'll need different subsets of the optional peer depe
261
264
  ```json
262
265
  {
263
266
  "dependencies": {
264
- "shakapacker": "^9.0.0",
267
+ "shakapacker": "^10.0.0",
265
268
  "@swc/core": "^1.3.0",
266
269
  "swc-loader": "^0.2.0",
267
270
  "compression-webpack-plugin": "^9.0.0",
@@ -274,14 +277,14 @@ Depending on your setup, you'll need different subsets of the optional peer depe
274
277
  }
275
278
  ```
276
279
 
277
- **For Rspack + SWC (10x faster bundling):**
280
+ **For Rspack + SWC (largest end-to-end speedup — see [transpiler-performance guide](./docs/transpiler-performance.md#published-benchmarks)):**
278
281
 
279
282
  ```json
280
283
  {
281
284
  "dependencies": {
282
- "shakapacker": "^9.0.0",
283
- "@rspack/core": "^1.0.0",
284
- "@rspack/cli": "^1.0.0",
285
+ "shakapacker": "^10.0.0",
286
+ "@rspack/core": "^2.0.0-0",
287
+ "@rspack/cli": "^2.0.0-0",
285
288
  "@swc/core": "^1.3.0",
286
289
  "swc-loader": "^0.2.0",
287
290
  "rspack-manifest-plugin": "^5.0.0"
@@ -923,7 +926,7 @@ You can also change your Babel configuration by removing these lines in your `pa
923
926
 
924
927
  ### SWC configuration
925
928
 
926
- SWC is the recommended JavaScript transpiler in Shakapacker v9+ (20x faster than Babel). New installations use SWC by default via the installation template. You can read more at [SWC usage docs](./docs/using_swc_loader.md).
929
+ SWC is the recommended JavaScript transpiler in Shakapacker v10 (this default was introduced in v9). SWC's own benchmark reports being [20x faster than Babel on a single thread and 70x faster on four cores](https://swc.rs/); end-to-end Shakapacker speedups are typically smaller but still substantial. New installations use SWC by default via the installation template. You can read more at [SWC usage docs](./docs/using_swc_loader.md).
927
930
 
928
931
  **Note on defaults**: The installation template explicitly sets `javascript_transpiler: "swc"` for new projects. However, for backward compatibility, webpack's runtime default (when no explicit config exists) remains `"babel"`. Rspack always defaults to `"swc"`.
929
932
 
@@ -1272,13 +1275,19 @@ npm install shakapacker@next
1272
1275
 
1273
1276
  Also, consult the [CHANGELOG](./CHANGELOG.md) for additional upgrade links.
1274
1277
 
1278
+ #### Automating Updates with Dependabot
1279
+
1280
+ Shakapacker is shipped as both a Ruby gem and an npm package, so they must be
1281
+ upgraded together. See [Dependabot configuration for Shakapacker](./docs/dependabot.md)
1282
+ for a `.github/dependabot.yml` example that updates both in a single PR.
1283
+
1275
1284
  #### Common Upgrade Scenarios
1276
1285
 
1277
1286
  For step-by-step guides on common migrations, see the [Common Upgrades Guide](./docs/common-upgrades.md):
1278
1287
 
1279
1288
  - [Migrating Package Managers](./docs/common-upgrades.md#migrating-package-managers) (Yarn ↔ npm, pnpm)
1280
- - [Migrating from Babel to SWC](./docs/common-upgrades.md#migrating-from-babel-to-swc) (20-70x faster builds)
1281
- - [Migrating from Webpack to Rspack](./docs/common-upgrades.md#migrating-from-webpack-to-rspack) (5-10x faster builds)
1289
+ - [Migrating from Babel to SWC](./docs/common-upgrades.md#migrating-from-babel-to-swc) (upstream: ~20x faster transpilation)
1290
+ - [Migrating from Webpack to Rspack](./docs/common-upgrades.md#migrating-from-webpack-to-rspack) (upstream: ~8–17x faster bundler)
1282
1291
 
1283
1292
  ### Paths
1284
1293
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "rspack": {
3
- "@rspack/cli": "^1.0.0",
4
- "@rspack/core": "^1.0.0",
3
+ "@rspack/cli": "^1.0.0 || ^2.0.0-0",
4
+ "@rspack/core": "^1.0.0 || ^2.0.0-0",
5
5
  "rspack-manifest-plugin": "^5.0.0"
6
6
  },
7
7
  "webpack": {
@@ -9,6 +9,14 @@ class Shakapacker::Compiler
9
9
  # Shakapacker::Compiler.env['FRONTEND_API_KEY'] = 'your_secret_key'
10
10
  cattr_accessor(:env) { {} }
11
11
 
12
+ # Class-level state keeps the compile-time doctor hint once-per-process.
13
+ @doctor_hint_shown = false
14
+ DOCTOR_HINT_MUTEX = Mutex.new
15
+
16
+ class << self
17
+ attr_accessor :doctor_hint_shown
18
+ end
19
+
12
20
  delegate :config, :logger, :strategy, to: :instance
13
21
  delegate :fresh?, :stale?, :after_compile_hook, to: :strategy
14
22
 
@@ -184,6 +192,7 @@ class Shakapacker::Compiler
184
192
  else
185
193
  non_empty_streams = [stdout, stderr].delete_if(&:empty?)
186
194
  logger.error "\nCOMPILATION FAILED:\nEXIT STATUS: #{status}\nOUTPUTS:\n#{non_empty_streams.join("\n\n")}"
195
+ show_doctor_hint_once
187
196
  end
188
197
 
189
198
  status.success?
@@ -201,4 +210,24 @@ class Shakapacker::Compiler
201
210
  def bin_shakapacker_path
202
211
  config.root_path.join("bin/shakapacker")
203
212
  end
213
+
214
+ # Fires only after a failed compile, so users in a healthy loop never see the tip.
215
+ def show_doctor_hint_once
216
+ return if self.class.doctor_hint_shown
217
+
218
+ DOCTOR_HINT_MUTEX.synchronize do
219
+ return if self.class.doctor_hint_shown
220
+
221
+ begin
222
+ logger.info "Tip: run 'bundle exec rake shakapacker:doctor' to diagnose configuration issues."
223
+ rescue StandardError => _e
224
+ # Non-critical tip; never abort a build because the logger failed.
225
+ # Named (but unused) variable makes the deliberate swallow explicit.
226
+ return
227
+ end
228
+
229
+ # Assignment is outside the rescue so a flag-setter failure propagates rather than being silenced.
230
+ self.class.doctor_hint_shown = true
231
+ end
232
+ end
204
233
  end
@@ -60,6 +60,16 @@ module Shakapacker
60
60
  add_warning(message, CATEGORY_INFO)
61
61
  end
62
62
 
63
+ # Marks the warning as a Fix sub-item; the renderer owns the "Fix: " prefix and indentation.
64
+ # The stored category mirrors the parent warning so a fix attached to an action-required
65
+ # item is itself action-required (it's only rendered alongside its parent, but the data
66
+ # stays consistent for any downstream consumer).
67
+ def add_fix_hint(message)
68
+ parent = @warnings.reverse_each.find { |w| !w[:fix] }
69
+ category = parent ? parent[:category] : CATEGORY_RECOMMENDED
70
+ @warnings << { category: category, message: message, fix: true }
71
+ end
72
+
63
73
  def print_help
64
74
  puts <<~HELP
65
75
  Shakapacker Doctor - Diagnostic tool for Shakapacker configuration
@@ -110,6 +120,7 @@ module Shakapacker
110
120
  check_css_dependencies
111
121
  check_css_modules_configuration
112
122
  check_bundler_dependencies if config_exists?
123
+ check_rspack_cache_configuration if config_exists?
113
124
  check_file_type_dependencies if config_exists?
114
125
  check_sri_dependencies if config_exists?
115
126
  check_peer_dependencies
@@ -200,7 +211,7 @@ module Shakapacker
200
211
  # Match "bundler:" at start of line or preceded by non-underscore character
201
212
  if config_file.match?(/^\s*bundler:/m) || config_file.match?(/[^_]bundler:/)
202
213
  add_action_required("Deprecated config: 'bundler' should be renamed to 'assets_bundler' in #{config_relative_path}.")
203
- add_action_required(" Fix: Open #{config_relative_path} and change 'bundler:' to 'assets_bundler:'.")
214
+ add_fix_hint("Open #{config_relative_path} and change 'bundler:' to 'assets_bundler:'.")
204
215
  end
205
216
  rescue => e
206
217
  # Ignore read errors as config file check already handles missing file
@@ -407,7 +418,7 @@ module Shakapacker
407
418
 
408
419
  unless missing_binstubs.empty?
409
420
  add_action_required("Missing binstubs: #{missing_binstubs.join(', ')}.")
410
- add_action_required(" Fix: Run 'bundle exec rake shakapacker:binstubs' to create them.")
421
+ add_fix_hint("Run 'bundle exec rake shakapacker:binstubs' to create them.")
411
422
  end
412
423
  end
413
424
 
@@ -480,7 +491,7 @@ module Shakapacker
480
491
  else "npm uninstall swc-loader"
481
492
  end
482
493
  add_warning("swc-loader is not needed with Rspack (SWC is built-in). Rspack includes SWC transpilation natively, so this package is redundant.")
483
- add_warning(" Fix: Remove it with: #{remove_cmd}.")
494
+ add_fix_hint("Remove it with: #{remove_cmd}.")
484
495
  end
485
496
  end
486
497
  end
@@ -520,7 +531,7 @@ module Shakapacker
520
531
  babel_files << "package.json" if babel_in_package_json
521
532
  babel_files_str = babel_files.join(", ")
522
533
  add_warning("Babel configuration files found (#{babel_files_str}) but javascript_transpiler is '#{transpiler}'. These Babel configs are ignored by Shakapacker (though they may still be used by ESLint or other tools).")
523
- add_warning(" Fix: Remove Babel config files if not needed, or set javascript_transpiler: 'babel' in shakapacker.yml to use Babel for transpilation.")
534
+ add_fix_hint("Remove Babel config files if not needed, or set javascript_transpiler: 'babel' in shakapacker.yml to use Babel for transpilation.")
524
535
  end
525
536
 
526
537
  # Check for redundant dependencies
@@ -681,6 +692,307 @@ module Shakapacker
681
692
  end
682
693
  end
683
694
 
695
+ def check_rspack_cache_configuration
696
+ return unless config.rspack?
697
+
698
+ rspack_major = rspack_major_version
699
+
700
+ if rspack_major == 1
701
+ add_warning("Rspack v1 detected: persistent caching is experimental in v1 and requires manual opt-in. " \
702
+ "Upgrading to Rspack v2 enables stable persistent caching out of the box for significantly faster rebuilds.")
703
+ add_fix_hint("Bump @rspack/core and @rspack/cli to ^2.0.0-0 in package.json. See https://rspack.rs/config/cache and docs/rspack.md for details.")
704
+ end
705
+
706
+ path = active_assets_bundler_config_path
707
+ return unless path
708
+
709
+ content = read_active_assets_bundler_config(path)
710
+ return unless content
711
+
712
+ return unless rspack_cache_disabled?(content)
713
+
714
+ relative = config_path_for_warning(path)
715
+ add_warning("Rspack cache appears to be disabled in #{relative} (found 'cache: false'). Disabling cache " \
716
+ "causes significantly slower builds, especially on rebuilds. Rspack v2 promotes filesystem " \
717
+ "caching from experimental to stable.")
718
+ add_fix_hint("Remove the 'cache: false' setting, or use 'cache: { type: \"filesystem\" }' for persistent caching. " \
719
+ "See https://rspack.rs/config/cache for options.")
720
+ end
721
+
722
+ # Returns the single active config path the runner would load, or nil. Mirrors the
723
+ # resolution order in Shakapacker::Runner#find_rspack_config_with_fallback so the
724
+ # doctor inspects the same file the build will actually use (and so unused sibling
725
+ # configs in the same directory don't trigger spurious warnings).
726
+ # NOTE: keep this candidate list in sync with Runner#find_rspack_config_with_fallback.
727
+ def active_assets_bundler_config_path
728
+ config_dir = config.assets_bundler_config_path.to_s
729
+
730
+ candidates = %w[ts js].map { |ext| Pathname.new(File.join(root_path.to_s, config_dir, "rspack.config.#{ext}")) }
731
+ candidates += %w[ts js].map { |ext| Pathname.new(File.join(root_path.to_s, config_dir, "webpack.config.#{ext}")) }
732
+ if default_rspack_config_dir?(config_dir)
733
+ candidates += %w[ts js].map { |ext| Pathname.new(File.join(root_path.to_s, "config/webpack", "webpack.config.#{ext}")) }
734
+ end
735
+
736
+ candidates.find(&:exist?)
737
+ end
738
+
739
+ def read_active_assets_bundler_config(path)
740
+ File.read(path)
741
+ rescue SystemCallError => e
742
+ add_info_warning("Unable to validate rspack cache configuration: #{e.message}")
743
+ nil
744
+ end
745
+
746
+ def default_rspack_config_dir?(config_dir)
747
+ # Intentionally exact-string match: mirrors the runner's own comparison,
748
+ # so a trailing slash or Pathname argument won't spuriously add the config/webpack fallback.
749
+ config_dir == "config/rspack"
750
+ end
751
+
752
+ def config_path_for_warning(path)
753
+ expanded_root = root_path.expand_path
754
+ expanded_path = path.expand_path
755
+
756
+ return path.to_s unless expanded_path.to_s == expanded_root.to_s ||
757
+ expanded_path.to_s.start_with?("#{expanded_root}#{File::SEPARATOR}")
758
+
759
+ path.relative_path_from(root_path)
760
+ rescue ArgumentError
761
+ path.to_s
762
+ end
763
+
764
+ def rspack_cache_disabled?(content)
765
+ stripped = stripped_rspack_config_content(content)
766
+
767
+ direct_export_cache_disabled?(stripped) || exported_variable_cache_disabled?(stripped)
768
+ end
769
+
770
+ def stripped_rspack_config_content(content)
771
+ # Normalize quoted property keys before stripping string literals; otherwise
772
+ # `'cache'` and `"cache"` collapse to `""` and the match below misses them.
773
+ # Lookahead is restricted to horizontal whitespace so a multiline ternary
774
+ # like `condition ? "cache"\n : false` doesn't fold across the newline
775
+ # and produce a spurious cache: false match.
776
+ stripped = content.gsub(/(['"])(\w+)\1(?=[ \t]*:)/, '\2')
777
+
778
+ stripped = strip_rspack_config_comments_and_literals(stripped)
779
+
780
+ # Regex literal stripping runs after comment removal. The line-comment
781
+ # pass above ignores escaped slashes so /https?:\/\/host/ stays intact
782
+ # until this pass removes the whole regex literal. The heuristic can
783
+ # false-match bare division like `a / b / c`, but rspack config files
784
+ # rarely contain arithmetic near `cache:`, so the risk is low.
785
+ stripped.gsub(%r{/(?:\\.|\[[^\]\n]*\]|[^/\n\\\[])+?/[gimsuy]*}, "")
786
+ end
787
+
788
+ def strip_rspack_config_comments_and_literals(content)
789
+ stripped = +""
790
+ index = 0
791
+
792
+ while index < content.length
793
+ char = content[index]
794
+ pair = content[index, 2]
795
+
796
+ if pair == "/*"
797
+ comment_end = content.index("*/", index + 2)
798
+ if comment_end
799
+ comment = content[index..comment_end + 1]
800
+ stripped << comment.gsub(/[^\n]/, " ")
801
+ index = comment_end + 2
802
+ else
803
+ stripped << content[index..].gsub(/[^\n]/, " ")
804
+ break
805
+ end
806
+ elsif pair == "//"
807
+ line_end = content.index("\n", index + 2) || content.length
808
+ stripped << content[index...line_end].gsub(/[^\n]/, " ")
809
+ index = line_end
810
+ elsif char == "'" || char == '"'
811
+ literal_end = index + 1
812
+ escaped = false
813
+
814
+ while literal_end < content.length
815
+ current = content[literal_end]
816
+ break if current == "\n"
817
+
818
+ if escaped
819
+ escaped = false
820
+ elsif current == "\\"
821
+ escaped = true
822
+ elsif current == char
823
+ literal_end += 1
824
+ break
825
+ end
826
+
827
+ literal_end += 1
828
+ end
829
+
830
+ literal = content[index...literal_end]
831
+ stripped << '""'
832
+ stripped << literal.gsub(/[^\n]/, "")
833
+ index = literal_end
834
+ elsif char == "`"
835
+ literal_end = index + 1
836
+ escaped = false
837
+ closed = false
838
+
839
+ while literal_end < content.length
840
+ current = content[literal_end]
841
+
842
+ if escaped
843
+ escaped = false
844
+ elsif current == "\\"
845
+ escaped = true
846
+ elsif current == "`"
847
+ literal_end += 1
848
+ closed = true
849
+ break
850
+ end
851
+
852
+ literal_end += 1
853
+ end
854
+
855
+ if closed
856
+ literal = content[index...literal_end]
857
+ stripped << '""'
858
+ stripped << literal.gsub(/[^\n]/, "")
859
+ index = literal_end
860
+ else
861
+ stripped << char
862
+ index += 1
863
+ end
864
+ else
865
+ stripped << char
866
+ index += 1
867
+ end
868
+ end
869
+
870
+ stripped
871
+ end
872
+
873
+ def direct_export_cache_disabled?(stripped)
874
+ # Match `cache: false` only near an exported config object at brace depth
875
+ # 1. This avoids warning on local base config objects while still
876
+ # catching the common direct export and generateRspackConfig patterns.
877
+ #
878
+ # Known false-positive gaps (rare; the "appears to be disabled" wording
879
+ # is the user-visible mitigation):
880
+ # * Named intermediate export — `export const helper = { cache: false }`
881
+ # at depth 1 is flagged even when `helper` is not the final config and
882
+ # a separate `export default { … }` provides the real configuration.
883
+ # * ASI + prior `module.exports` — in semicolon-free code, an earlier
884
+ # `module.exports = merge(…)` followed by a local helper literal can
885
+ # leak into `statement_prefix` because `pre.rindex(";")` returns nil
886
+ # and the prefix spans back to the start of the file.
887
+ stripped.to_enum(:scan, /\bcache\s*:\s*false\b/).any? do
888
+ pre = Regexp.last_match.pre_match
889
+ next false unless (pre.count("{") - pre.count("}")) == 1
890
+
891
+ statement_prefix = pre[(pre.rindex(";") || -1) + 1..]
892
+ # generateRspackConfig is Shakapacker's own helper; user-defined wrappers
893
+ # like makeRspackConfig/createConfig are a known false-negative gap.
894
+ statement_prefix.match?(/\bmodule\.exports\b|\bexport\s+default\b|\bexport\s+(?:const|let|var)\b|\bgenerateRspackConfig\b/)
895
+ end
896
+ end
897
+
898
+ # Catches the `const cfg = { cache: false }; module.exports = cfg` pattern.
899
+ # Composition via merge (`module.exports = merge(cfg, …)`) is a known
900
+ # false-negative since the variable is never referenced by name in the
901
+ # export statement. The `[^=;]+` type-annotation clause also stops at the
902
+ # first `=`, so TypeScript generics with default type parameters such as
903
+ # `Configuration<Opts = DefaultOpts>` are another known false-negative gap.
904
+ def exported_variable_cache_disabled?(stripped)
905
+ variable_declaration = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)(?:\s*:\s*[^=;]+)?\s*=\s*\{/
906
+
907
+ stripped.to_enum(:scan, variable_declaration).any? do
908
+ name = Regexp.last_match[1]
909
+ open_index = Regexp.last_match.end(0) - 1
910
+ close_index = matching_closing_brace(stripped, open_index)
911
+ next false unless close_index
912
+
913
+ object_source = stripped[open_index..close_index]
914
+ top_level_cache_false?(object_source) && exported_config_variable?(stripped, name)
915
+ end
916
+ end
917
+
918
+ def top_level_cache_false?(object_source)
919
+ object_source.to_enum(:scan, /\bcache\s*:\s*false\b/).any? do
920
+ pre = Regexp.last_match.pre_match
921
+ (pre.count("{") - pre.count("}")) == 1
922
+ end
923
+ end
924
+
925
+ def exported_config_variable?(stripped, name)
926
+ escaped_name = Regexp.escape(name)
927
+ stripped.match?(/\bmodule\.exports\s*=\s*#{escaped_name}\b/) ||
928
+ stripped.match?(/\bexport\s+default\s+#{escaped_name}\b/) ||
929
+ stripped.match?(/\bexport\s*\{[^}]*\b#{escaped_name}\s+as\s+default\b/)
930
+ end
931
+
932
+ def matching_closing_brace(content, open_index)
933
+ depth = 0
934
+
935
+ content[open_index..].each_char.with_index do |char, offset|
936
+ depth += 1 if char == "{"
937
+ depth -= 1 if char == "}"
938
+
939
+ return open_index + offset if depth.zero?
940
+ end
941
+
942
+ nil
943
+ end
944
+
945
+ def rspack_major_version
946
+ # Prefer the resolved version from node_modules, since package.json specifiers
947
+ # (ranges, git refs, file paths) often don't parse to a clean major number.
948
+ resolved = installed_rspack_major_version
949
+ return resolved unless resolved.nil?
950
+
951
+ %w[@rspack/core @rspack/cli].each do |package_name|
952
+ major = rspack_major_from_specifier(package_json_dependency_version(package_name))
953
+ return major unless major.nil?
954
+ end
955
+
956
+ nil
957
+ end
958
+
959
+ def rspack_major_from_specifier(version)
960
+ return nil unless version
961
+
962
+ # Only trust specifiers starting with a digit or ^/~ prefix followed by
963
+ # a digit. Skip git+, file:, npm: aliases, "latest", "*", or ranges like
964
+ # ">=1.5 <2". Accept shorthand forms (e.g. `^1`, `~1`, `1`, `1.x`) so
965
+ # we still emit the v1 advisory when node_modules isn't populated yet.
966
+ cleaned = version.strip
967
+ return nil unless cleaned.match?(/\A[\^~]?\d/)
968
+ return nil if cleaned.match?(/(\s|\|\||[<>=:]|\A(?:git|file|link|workspace|npm):)/)
969
+ return nil unless cleaned.match?(/\A[\^~]?\d+(?:\.(?:\d+|x|\*)){0,2}(?:-[0-9A-Za-z.-]+)?\z/i)
970
+
971
+ match = cleaned.sub(/\A[\^~]/, "").match(/\A(\d+)/)
972
+ match && match[1].to_i
973
+ end
974
+
975
+ def installed_rspack_major_version
976
+ rspack_pkg = root_path.join("node_modules/@rspack/core/package.json")
977
+ return nil unless rspack_pkg.exist?
978
+
979
+ version = JSON.parse(File.read(rspack_pkg))["version"]
980
+ match = version.to_s.match(/\A(\d+)\./)
981
+ match && match[1].to_i
982
+ rescue JSON::ParserError, SystemCallError
983
+ nil
984
+ end
985
+
986
+ def package_json_dependency_version(name)
987
+ return nil unless package_json_exists?
988
+
989
+ pkg = read_package_json
990
+ # Production dependencies take precedence on key conflict so the version
991
+ # actually shipped in production wins over a devDependencies override.
992
+ deps = (pkg["devDependencies"] || {}).merge(pkg["dependencies"] || {})
993
+ deps[name]
994
+ end
995
+
684
996
  def check_file_type_dependencies
685
997
  source_path = config.source_path
686
998
  return unless source_path.exist?
@@ -1064,15 +1376,14 @@ module Shakapacker
1064
1376
  end
1065
1377
 
1066
1378
  def print_warnings
1067
- # Count only main items (not sub-items)
1068
- main_item_count = doctor.warnings.count { |w| !w[:message].start_with?(" ") }
1379
+ main_item_count = doctor.warnings.count { |w| !subitem?(w) }
1069
1380
  puts "⚠️ Warnings (#{main_item_count}):"
1070
1381
  puts ""
1071
1382
 
1072
1383
  item_number = 0
1073
1384
  doctor.warnings.each do |warning|
1074
- if subitem?(warning[:message])
1075
- print_subitem(warning[:message])
1385
+ if subitem?(warning)
1386
+ print_subitem(warning)
1076
1387
  else
1077
1388
  item_number += 1
1078
1389
  print_main_item(item_number, warning)
@@ -1081,15 +1392,16 @@ module Shakapacker
1081
1392
  puts ""
1082
1393
  end
1083
1394
 
1084
- def subitem?(message)
1085
- message.start_with?(" ")
1395
+ def subitem?(warning)
1396
+ warning[:fix] || warning[:message].start_with?(" ")
1086
1397
  end
1087
1398
 
1088
- def print_subitem(message)
1399
+ def print_subitem(warning)
1089
1400
  # Fix instructions align at column 16 (length of "N. [RECOMMENDED] ")
1090
- # This ensures all Fix lines align vertically regardless of category
1401
+ # This keeps every Fix line aligned regardless of category.
1091
1402
  subitem_prefix = " " * 15
1092
- wrapped = wrap_text(message, 100, subitem_prefix)
1403
+ text = warning[:fix] ? "Fix: #{warning[:message]}" : warning[:message]
1404
+ wrapped = wrap_text(text, 100, subitem_prefix)
1093
1405
  puts wrapped
1094
1406
  end
1095
1407
 
@@ -1,4 +1,4 @@
1
1
  module Shakapacker
2
2
  # Change the version in package.json too, please!
3
- VERSION = "10.0.0".freeze
3
+ VERSION = "10.1.0.rc.0".freeze
4
4
  end