shakapacker 8.0.2 → 9.2.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 (198) hide show
  1. checksums.yaml +4 -4
  2. data/.eslintignore +1 -0
  3. data/.eslintrc.fast.js +40 -0
  4. data/.eslintrc.js +48 -0
  5. data/.github/STATUS.md +1 -0
  6. data/.github/workflows/claude-code-review.yml +54 -0
  7. data/.github/workflows/claude.yml +50 -0
  8. data/.github/workflows/dummy.yml +9 -4
  9. data/.github/workflows/generator.yml +32 -10
  10. data/.github/workflows/node.yml +23 -1
  11. data/.github/workflows/ruby.yml +33 -2
  12. data/.github/workflows/test-bundlers.yml +170 -0
  13. data/.gitignore +20 -0
  14. data/.husky/pre-commit +2 -0
  15. data/.npmignore +56 -0
  16. data/.prettierignore +3 -0
  17. data/.rubocop.yml +1 -0
  18. data/.yalcignore +26 -0
  19. data/CHANGELOG.md +302 -16
  20. data/CLAUDE.md +29 -0
  21. data/CONTRIBUTING.md +138 -20
  22. data/Gemfile.lock +83 -89
  23. data/README.md +343 -105
  24. data/Rakefile +39 -4
  25. data/TODO.md +50 -0
  26. data/TODO_v9.md +87 -0
  27. data/bin/export-bundler-config +11 -0
  28. data/conductor-setup.sh +70 -0
  29. data/conductor.json +7 -0
  30. data/docs/cdn_setup.md +379 -0
  31. data/docs/common-upgrades.md +615 -0
  32. data/docs/css-modules-export-mode.md +512 -0
  33. data/docs/deployment.md +62 -9
  34. data/docs/optional-peer-dependencies.md +198 -0
  35. data/docs/peer-dependencies.md +60 -0
  36. data/docs/react.md +6 -14
  37. data/docs/releasing.md +197 -0
  38. data/docs/rspack.md +190 -0
  39. data/docs/rspack_migration_guide.md +305 -0
  40. data/docs/subresource_integrity.md +54 -0
  41. data/docs/transpiler-migration.md +209 -0
  42. data/docs/transpiler-performance.md +179 -0
  43. data/docs/troubleshooting.md +157 -22
  44. data/docs/typescript-migration.md +379 -0
  45. data/docs/typescript.md +99 -0
  46. data/docs/using_esbuild_loader.md +3 -3
  47. data/docs/using_swc_loader.md +112 -10
  48. data/docs/v6_upgrade.md +10 -0
  49. data/docs/v8_upgrade.md +3 -5
  50. data/docs/v9_upgrade.md +458 -0
  51. data/gemfiles/Gemfile-rails.6.0.x +2 -1
  52. data/gemfiles/Gemfile-rails.6.1.x +1 -1
  53. data/gemfiles/Gemfile-rails.7.0.x +2 -2
  54. data/gemfiles/Gemfile-rails.7.1.x +1 -2
  55. data/gemfiles/Gemfile-rails.7.2.x +11 -0
  56. data/gemfiles/Gemfile-rails.8.0.x +11 -0
  57. data/lib/install/bin/export-bundler-config +11 -0
  58. data/lib/install/bin/shakapacker +4 -6
  59. data/lib/install/bin/shakapacker-dev-server +1 -1
  60. data/lib/install/config/rspack/rspack.config.js +6 -0
  61. data/lib/install/config/rspack/rspack.config.ts +7 -0
  62. data/lib/install/config/shakapacker.yml +25 -5
  63. data/lib/install/config/webpack/webpack.config.ts +7 -0
  64. data/lib/install/package.json +38 -0
  65. data/lib/install/template.rb +194 -44
  66. data/lib/shakapacker/bundler_switcher.rb +329 -0
  67. data/lib/shakapacker/compiler.rb +2 -1
  68. data/lib/shakapacker/compiler_strategy.rb +2 -2
  69. data/lib/shakapacker/configuration.rb +173 -2
  70. data/lib/shakapacker/dev_server_runner.rb +29 -8
  71. data/lib/shakapacker/digest_strategy.rb +2 -1
  72. data/lib/shakapacker/doctor.rb +905 -0
  73. data/lib/shakapacker/helper.rb +64 -16
  74. data/lib/shakapacker/manifest.rb +10 -3
  75. data/lib/shakapacker/mtime_strategy.rb +1 -1
  76. data/lib/shakapacker/railtie.rb +4 -4
  77. data/lib/shakapacker/rspack_runner.rb +19 -0
  78. data/lib/shakapacker/runner.rb +159 -10
  79. data/lib/shakapacker/swc_migrator.rb +384 -0
  80. data/lib/shakapacker/utils/manager.rb +15 -2
  81. data/lib/shakapacker/version.rb +1 -1
  82. data/lib/shakapacker/version_checker.rb +2 -2
  83. data/lib/shakapacker/webpack_runner.rb +6 -43
  84. data/lib/shakapacker.rb +22 -11
  85. data/lib/tasks/shakapacker/doctor.rake +8 -0
  86. data/lib/tasks/shakapacker/export_bundler_config.rake +72 -0
  87. data/lib/tasks/shakapacker/install.rake +12 -2
  88. data/lib/tasks/shakapacker/migrate_to_swc.rake +13 -0
  89. data/lib/tasks/shakapacker/switch_bundler.rake +82 -0
  90. data/lib/tasks/shakapacker.rake +2 -0
  91. data/package/.npmignore +4 -0
  92. data/package/babel/preset.ts +56 -0
  93. data/package/config.ts +175 -0
  94. data/package/configExporter/cli.ts +683 -0
  95. data/package/configExporter/configDocs.ts +102 -0
  96. data/package/configExporter/fileWriter.ts +92 -0
  97. data/package/configExporter/index.ts +5 -0
  98. data/package/configExporter/types.ts +36 -0
  99. data/package/configExporter/yamlSerializer.ts +266 -0
  100. data/package/{dev_server.js → dev_server.ts} +8 -5
  101. data/package/env.ts +92 -0
  102. data/package/environments/__type-tests__/rspack-plugin-compatibility.ts +30 -0
  103. data/package/environments/{base.js → base.ts} +56 -60
  104. data/package/environments/development.ts +90 -0
  105. data/package/environments/production.ts +80 -0
  106. data/package/environments/test.ts +53 -0
  107. data/package/environments/types.ts +98 -0
  108. data/package/esbuild/index.ts +42 -0
  109. data/package/index.d.ts +3 -60
  110. data/package/index.ts +55 -0
  111. data/package/loaders.d.ts +28 -0
  112. data/package/optimization/rspack.ts +36 -0
  113. data/package/optimization/webpack.ts +57 -0
  114. data/package/plugins/rspack.ts +103 -0
  115. data/package/plugins/webpack.ts +62 -0
  116. data/package/rspack/index.ts +64 -0
  117. data/package/rules/{babel.js → babel.ts} +2 -2
  118. data/package/rules/{coffee.js → coffee.ts} +1 -1
  119. data/package/rules/css.ts +3 -0
  120. data/package/rules/{erb.js → erb.ts} +1 -1
  121. data/package/rules/esbuild.ts +10 -0
  122. data/package/rules/file.ts +40 -0
  123. data/package/rules/{jscommon.js → jscommon.ts} +4 -4
  124. data/package/rules/{less.js → less.ts} +4 -4
  125. data/package/rules/raw.ts +25 -0
  126. data/package/rules/rspack.ts +176 -0
  127. data/package/rules/{sass.js → sass.ts} +7 -3
  128. data/package/rules/{stylus.js → stylus.ts} +4 -8
  129. data/package/rules/swc.ts +10 -0
  130. data/package/rules/webpack.ts +16 -0
  131. data/package/swc/index.ts +56 -0
  132. data/package/types/README.md +88 -0
  133. data/package/types/index.ts +61 -0
  134. data/package/types.ts +108 -0
  135. data/package/utils/configPath.ts +6 -0
  136. data/package/utils/debug.ts +49 -0
  137. data/package/utils/defaultConfigPath.ts +4 -0
  138. data/package/utils/errorCodes.ts +219 -0
  139. data/package/utils/errorHelpers.ts +143 -0
  140. data/package/utils/getStyleRule.ts +64 -0
  141. data/package/utils/helpers.ts +85 -0
  142. data/package/utils/{inliningCss.js → inliningCss.ts} +3 -3
  143. data/package/utils/pathValidation.ts +139 -0
  144. data/package/utils/requireOrError.ts +15 -0
  145. data/package/utils/snakeToCamelCase.ts +5 -0
  146. data/package/utils/typeGuards.ts +342 -0
  147. data/package/utils/validateDependencies.ts +61 -0
  148. data/package/webpack-types.d.ts +33 -0
  149. data/package/webpackDevServerConfig.ts +117 -0
  150. data/package-lock.json +13047 -0
  151. data/package.json +154 -18
  152. data/scripts/remove-use-strict.js +45 -0
  153. data/scripts/type-check-no-emit.js +27 -0
  154. data/test/helpers.js +1 -1
  155. data/test/package/config.test.js +43 -0
  156. data/test/package/env.test.js +42 -7
  157. data/test/package/environments/base.test.js +5 -1
  158. data/test/package/rules/babel.test.js +16 -0
  159. data/test/package/rules/esbuild.test.js +1 -1
  160. data/test/package/rules/raw.test.js +40 -7
  161. data/test/package/rules/swc.test.js +1 -1
  162. data/test/package/rules/webpack.test.js +35 -0
  163. data/test/package/staging.test.js +4 -3
  164. data/test/package/transpiler-defaults.test.js +127 -0
  165. data/test/peer-dependencies.sh +85 -0
  166. data/test/scripts/remove-use-strict.test.js +125 -0
  167. data/test/typescript/build.test.js +118 -0
  168. data/test/typescript/environments.test.js +107 -0
  169. data/test/typescript/pathValidation.test.js +142 -0
  170. data/test/typescript/securityValidation.test.js +182 -0
  171. data/tools/README.md +124 -0
  172. data/tools/css-modules-v9-codemod.js +179 -0
  173. data/tsconfig.eslint.json +16 -0
  174. data/tsconfig.json +38 -0
  175. data/yarn.lock +4165 -2706
  176. metadata +129 -41
  177. data/package/babel/preset.js +0 -37
  178. data/package/config.js +0 -54
  179. data/package/env.js +0 -48
  180. data/package/environments/development.js +0 -13
  181. data/package/environments/production.js +0 -88
  182. data/package/environments/test.js +0 -3
  183. data/package/esbuild/index.js +0 -40
  184. data/package/index.js +0 -40
  185. data/package/rules/css.js +0 -3
  186. data/package/rules/esbuild.js +0 -10
  187. data/package/rules/file.js +0 -29
  188. data/package/rules/index.js +0 -20
  189. data/package/rules/raw.js +0 -5
  190. data/package/rules/swc.js +0 -10
  191. data/package/swc/index.js +0 -50
  192. data/package/utils/configPath.js +0 -4
  193. data/package/utils/defaultConfigPath.js +0 -2
  194. data/package/utils/getStyleRule.js +0 -40
  195. data/package/utils/helpers.js +0 -58
  196. data/package/utils/snakeToCamelCase.js +0 -5
  197. data/package/webpackDevServerConfig.js +0 -71
  198. data/test/package/rules/index.test.js +0 -16
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shakapacker",
3
- "version": "8.0.2",
3
+ "version": "9.2.0",
4
4
  "description": "Use webpack to manage app-like JavaScript modules in Rails",
5
5
  "homepage": "https://github.com/shakacode/shakapacker",
6
6
  "bugs": {
@@ -14,65 +14,201 @@
14
14
  "author": "David Heinemeier Hansson <david@basecamp.com>, Justin Gordon <justin@shakacode.com>",
15
15
  "main": "package/index.js",
16
16
  "types": "package/index.d.ts",
17
+ "exports": {
18
+ ".": "./package/index.js",
19
+ "./types": "./package/types/index.js",
20
+ "./webpack": "./package/webpack/index.js",
21
+ "./rspack": "./package/rspack/index.js",
22
+ "./swc": "./package/swc/index.js",
23
+ "./esbuild": "./package/esbuild/index.js",
24
+ "./configExporter": "./package/configExporter/index.js",
25
+ "./package.json": "./package.json",
26
+ "./package/babel/preset.js": "./package/babel/preset.js",
27
+ "./package/*": "./package/*"
28
+ },
17
29
  "files": [
18
30
  "package",
19
31
  "lib/install/config/shakapacker.yml"
20
32
  ],
21
33
  "scripts": {
34
+ "clean:ts": "find package -name '*.ts' -not -name '*.d.ts' | sed 's/\\.ts$//' | xargs -I {} rm -f {}.js {}.d.ts {}.d.ts.map {}.js.map",
35
+ "build": "tsc && node scripts/remove-use-strict.js && yarn prettier --write 'package/**/*.js'",
36
+ "build:types": "tsc",
22
37
  "lint": "eslint .",
23
- "test": "jest"
38
+ "lint:fast": "eslint . --ext .js,.jsx,.ts,.tsx --config .eslintrc.fast.js",
39
+ "test": "jest",
40
+ "type-check": "tsc --noEmit",
41
+ "prepublishOnly": "yarn build && yarn type-check"
24
42
  },
25
43
  "dependencies": {
26
44
  "js-yaml": "^4.1.0",
27
- "path-complete-extname": "^1.0.0"
45
+ "path-complete-extname": "^1.0.0",
46
+ "webpack-merge": "^5.8.0"
28
47
  },
29
48
  "devDependencies": {
49
+ "@rspack/cli": "^1.4.11",
50
+ "@rspack/core": "^1.4.11",
51
+ "@swc/core": "^1.3.0",
52
+ "@types/babel__core": "^7.20.5",
53
+ "@types/js-yaml": "^4.0.9",
54
+ "@types/node": "^24.5.2",
55
+ "@types/path-complete-extname": "^1.0.3",
56
+ "@types/webpack": "^5.28.5",
57
+ "@types/webpack-dev-server": "^4.7.2",
58
+ "@types/webpack-merge": "^5.0.0",
59
+ "@typescript-eslint/eslint-plugin": "^8.45.0",
60
+ "@typescript-eslint/parser": "^8.45.0",
30
61
  "babel-loader": "^8.2.4",
31
62
  "compression-webpack-plugin": "^9.0.0",
63
+ "css-loader": "^7.1.2",
32
64
  "esbuild-loader": "^2.18.0",
33
65
  "eslint": "^8.0.0",
34
- "eslint-config-airbnb": "^19.0.0",
66
+ "eslint-config-airbnb": "^19.0.4",
35
67
  "eslint-config-prettier": "^9.0.0",
36
- "eslint-plugin-import": "^2.24.2",
68
+ "eslint-plugin-import": "^2.31.0",
37
69
  "eslint-plugin-jest": "^27.9.0",
38
- "eslint-plugin-jsx-a11y": "^6.4.1",
39
- "eslint-plugin-prettier": "^5.1.3",
40
- "eslint-plugin-react": "^7.26.0",
70
+ "eslint-plugin-jsx-a11y": "^6.10.2",
71
+ "eslint-plugin-prettier": "^5.2.6",
72
+ "eslint-plugin-react": "^7.37.5",
41
73
  "eslint-plugin-react-hooks": "^4.6.0",
42
- "jest": "^28.1.3",
74
+ "husky": "^9.1.7",
75
+ "jest": "^29.7.0",
76
+ "lint-staged": "^15.2.10",
43
77
  "memory-fs": "^0.5.0",
78
+ "mini-css-extract-plugin": "^2.9.4",
44
79
  "prettier": "^3.2.5",
80
+ "rspack-manifest-plugin": "^5.0.3",
81
+ "sass-loader": "^16.0.5",
45
82
  "swc-loader": "^0.1.15",
46
83
  "thenify": "^3.3.1",
47
- "webpack": "^5.72.0",
84
+ "typescript": "^5.9.2",
85
+ "webpack": "5.93.0",
48
86
  "webpack-assets-manifest": "^5.0.6",
49
- "webpack-merge": "^5.8.0"
87
+ "webpack-subresource-integrity": "^5.1.0"
50
88
  },
51
89
  "peerDependencies": {
52
90
  "@babel/core": "^7.17.9",
53
91
  "@babel/plugin-transform-runtime": "^7.17.0",
54
92
  "@babel/preset-env": "^7.16.11",
55
93
  "@babel/runtime": "^7.17.9",
94
+ "@rspack/cli": "^1.0.0",
95
+ "@rspack/core": "^1.0.0",
96
+ "@rspack/plugin-react-refresh": "^1.0.0",
97
+ "@swc/core": "^1.3.0",
56
98
  "@types/babel__core": "^7.0.0",
57
99
  "@types/webpack": "^5.0.0",
58
- "babel-loader": "^8.2.4 || ^9.0.0",
59
- "compression-webpack-plugin": "^9.0.0 || ^10.0.0|| ^11.0.0",
100
+ "babel-loader": "^8.2.4 || ^9.0.0 || ^10.0.0",
101
+ "compression-webpack-plugin": "^9.0.0 || ^10.0.0 || ^11.0.0",
102
+ "css-loader": "^6.8.1 || ^7.0.0",
103
+ "esbuild": "^0.14.0 || ^0.15.0 || ^0.16.0 || ^0.17.0 || ^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0",
104
+ "esbuild-loader": "^2.0.0 || ^3.0.0 || ^4.0.0",
105
+ "mini-css-extract-plugin": "^2.0.0",
106
+ "rspack-manifest-plugin": "^5.0.0",
107
+ "sass": "^1.50.0",
108
+ "sass-loader": "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",
109
+ "swc-loader": "^0.1.15 || ^0.2.0",
60
110
  "terser-webpack-plugin": "^5.3.1",
61
- "webpack": "^5.72.0",
62
- "webpack-assets-manifest": "^5.0.6",
63
- "webpack-cli": "^4.9.2 || ^5.0.0",
64
- "webpack-dev-server": "^4.9.0 || ^5.0.0",
65
- "webpack-merge": "^5.8.0 || ^6.0.0"
111
+ "webpack": "^5.76.0",
112
+ "webpack-assets-manifest": "^5.0.6 || ^6.0.0",
113
+ "webpack-cli": "^4.9.2 || ^5.0.0 || ^6.0.0",
114
+ "webpack-dev-server": "^4.15.2 || ^5.2.2",
115
+ "webpack-subresource-integrity": "^5.1.0"
66
116
  },
67
117
  "peerDependenciesMeta": {
118
+ "@babel/core": {
119
+ "optional": true
120
+ },
121
+ "@babel/plugin-transform-runtime": {
122
+ "optional": true
123
+ },
124
+ "@babel/preset-env": {
125
+ "optional": true
126
+ },
127
+ "@babel/runtime": {
128
+ "optional": true
129
+ },
130
+ "@rspack/core": {
131
+ "optional": true
132
+ },
133
+ "@rspack/cli": {
134
+ "optional": true
135
+ },
136
+ "@rspack/plugin-react-refresh": {
137
+ "optional": true
138
+ },
139
+ "@swc/core": {
140
+ "optional": true
141
+ },
68
142
  "@types/babel__core": {
69
143
  "optional": true
70
144
  },
71
145
  "@types/webpack": {
72
146
  "optional": true
147
+ },
148
+ "babel-loader": {
149
+ "optional": true
150
+ },
151
+ "compression-webpack-plugin": {
152
+ "optional": true
153
+ },
154
+ "css-loader": {
155
+ "optional": true
156
+ },
157
+ "esbuild": {
158
+ "optional": true
159
+ },
160
+ "esbuild-loader": {
161
+ "optional": true
162
+ },
163
+ "mini-css-extract-plugin": {
164
+ "optional": true
165
+ },
166
+ "rspack-manifest-plugin": {
167
+ "optional": true
168
+ },
169
+ "sass": {
170
+ "optional": true
171
+ },
172
+ "sass-loader": {
173
+ "optional": true
174
+ },
175
+ "swc-loader": {
176
+ "optional": true
177
+ },
178
+ "terser-webpack-plugin": {
179
+ "optional": true
180
+ },
181
+ "webpack": {
182
+ "optional": true
183
+ },
184
+ "webpack-assets-manifest": {
185
+ "optional": true
186
+ },
187
+ "webpack-cli": {
188
+ "optional": true
189
+ },
190
+ "webpack-dev-server": {
191
+ "optional": true
192
+ },
193
+ "webpack-subresource-integrity": {
194
+ "optional": true
73
195
  }
74
196
  },
75
197
  "packageManager": "yarn@1.22.22",
198
+ "lint-staged": {
199
+ "*.{js,jsx}": [
200
+ "eslint --fix",
201
+ "prettier --write"
202
+ ],
203
+ "*.{ts,tsx}": [
204
+ "eslint --fix",
205
+ "prettier --write",
206
+ "node scripts/type-check-no-emit.js"
207
+ ],
208
+ "*.{json,yml,yaml,md}": [
209
+ "prettier --write"
210
+ ]
211
+ },
76
212
  "engines": {
77
213
  "node": ">= 14",
78
214
  "yarn": ">=1 <5"
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+ const fs = require("fs")
3
+ const path = require("path")
4
+
5
+ // Recursively find all .js files in a directory
6
+ function findJsFiles(dir) {
7
+ const files = []
8
+ const items = fs.readdirSync(dir, { withFileTypes: true })
9
+
10
+ items.forEach((item) => {
11
+ const fullPath = path.join(dir, item.name)
12
+ if (item.isDirectory()) {
13
+ files.push(...findJsFiles(fullPath))
14
+ } else if (item.isFile() && item.name.endsWith(".js")) {
15
+ files.push(fullPath)
16
+ }
17
+ })
18
+
19
+ return files
20
+ }
21
+
22
+ // Find all .js files in package directory
23
+ const files = findJsFiles("package")
24
+
25
+ files.forEach((file) => {
26
+ let content = fs.readFileSync(file, "utf8")
27
+
28
+ // Remove "use strict" directive with various quote styles and formatting
29
+ // Handles: optional whitespace, single/double/unicode quotes, optional semicolon,
30
+ // and any trailing whitespace/newline sequences
31
+ content = content.replace(
32
+ /^\s*["'\u2018\u2019\u201C\u201D]use\s+strict["'\u2018\u2019\u201C\u201D]\s*;?\s*[\r\n]*/,
33
+ ""
34
+ )
35
+
36
+ // Ensure file ends with exactly one newline
37
+ if (!content.endsWith("\n")) {
38
+ content += "\n"
39
+ }
40
+
41
+ fs.writeFileSync(file, content, "utf8")
42
+ })
43
+
44
+ // eslint-disable-next-line no-console
45
+ console.log(`Removed "use strict" from ${files.length} files`)
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Type-check script for lint-staged
5
+ *
6
+ * This script runs TypeScript type checking without emitting files.
7
+ * It ignores any arguments passed by lint-staged to ensure tsc uses
8
+ * the project's tsconfig.json rather than trying to compile individual files.
9
+ *
10
+ * Without this wrapper, lint-staged would pass staged file paths as arguments
11
+ * to tsc, causing it to ignore tsconfig.json and fail type checking.
12
+ */
13
+
14
+ const { execSync } = require("child_process")
15
+
16
+ try {
17
+ // Run tsc with no arguments (ignoring any passed by lint-staged)
18
+ // This ensures it uses tsconfig.json properly
19
+ execSync("npx tsc --noEmit", {
20
+ stdio: "inherit",
21
+ cwd: process.cwd()
22
+ })
23
+ process.exit(0)
24
+ } catch (error) {
25
+ // Type checking failed
26
+ process.exit(1)
27
+ }
data/test/helpers.js CHANGED
@@ -9,7 +9,7 @@ const createTrackLoader = () => {
9
9
  filesTracked,
10
10
  (source) => {
11
11
  filesTracked[source.resource] = true
12
- return source
12
+ return "" // Fix #567
13
13
  }
14
14
  ]
15
15
  }
@@ -54,4 +54,47 @@ describe("Config", () => {
54
54
  resolve("app/javascript/manifest.json")
55
55
  )
56
56
  })
57
+
58
+ test("should have integrity disabled by default", () => {
59
+ const config = require("../../package/config")
60
+ expect(config.integrity.enabled).toBe(false)
61
+ })
62
+
63
+ test("should have sha384 as default hash function", () => {
64
+ const config = require("../../package/config")
65
+ expect(config.integrity.hash_functions).toStrictEqual(["sha384"])
66
+ })
67
+
68
+ test("should have anonymous as default crossorigin", () => {
69
+ const config = require("../../package/config")
70
+ expect(config.integrity.cross_origin).toBe("anonymous")
71
+ })
72
+
73
+ test("should allow enabling integrity", () => {
74
+ process.env.RAILS_ENV = "production"
75
+ process.env.SHAKAPACKER_CONFIG = "config/shakapacker_integrity.yml"
76
+ const config = require("../../package/config")
77
+
78
+ expect(config.integrity.enabled).toBe(true)
79
+ })
80
+
81
+ test("should allow configuring hash functions", () => {
82
+ process.env.RAILS_ENV = "production"
83
+ process.env.SHAKAPACKER_CONFIG = "config/shakapacker_integrity.yml"
84
+ const config = require("../../package/config")
85
+
86
+ expect(config.integrity.hash_functions).toStrictEqual([
87
+ "sha384",
88
+ "sha256",
89
+ "sha512"
90
+ ])
91
+ })
92
+
93
+ test("should allow configuring crossorigin", () => {
94
+ process.env.RAILS_ENV = "production"
95
+ process.env.SHAKAPACKER_CONFIG = "config/shakapacker_integrity.yml"
96
+ const config = require("../../package/config")
97
+
98
+ expect(config.integrity.cross_origin).toBe("use-credentials")
99
+ })
57
100
  })
@@ -24,6 +24,18 @@ describe("Env", () => {
24
24
  delete process.env.NODE_ENV
25
25
  expect(require("../../package/env")).toStrictEqual({
26
26
  railsEnv: "development",
27
+ nodeEnv: "development",
28
+ isProduction: false,
29
+ isDevelopment: true,
30
+ runningWebpackDevServer: false
31
+ })
32
+ })
33
+
34
+ test("with undefined NODE_ENV and RAILS_ENV set to production", () => {
35
+ process.env.RAILS_ENV = "production"
36
+ delete process.env.NODE_ENV
37
+ expect(require("../../package/env")).toStrictEqual({
38
+ railsEnv: "production",
27
39
  nodeEnv: "production",
28
40
  isProduction: true,
29
41
  isDevelopment: false,
@@ -35,10 +47,10 @@ describe("Env", () => {
35
47
  delete process.env.NODE_ENV
36
48
  delete process.env.RAILS_ENV
37
49
  expect(require("../../package/env")).toStrictEqual({
38
- railsEnv: "production",
39
- nodeEnv: "production",
40
- isProduction: true,
41
- isDevelopment: false,
50
+ railsEnv: "development",
51
+ nodeEnv: "development",
52
+ isProduction: false,
53
+ isDevelopment: true,
42
54
  runningWebpackDevServer: false
43
55
  })
44
56
  })
@@ -48,10 +60,33 @@ describe("Env", () => {
48
60
  process.env.NODE_ENV = "staging"
49
61
  expect(require("../../package/env")).toStrictEqual({
50
62
  railsEnv: "staging",
51
- nodeEnv: "production",
52
- isProduction: true,
53
- isDevelopment: false,
63
+ nodeEnv: "development",
64
+ isProduction: false,
65
+ isDevelopment: true,
66
+ runningWebpackDevServer: false
67
+ })
68
+ })
69
+
70
+ test("rejects malicious NODE_ENV values and uses default", () => {
71
+ process.env.RAILS_ENV = "development"
72
+ process.env.NODE_ENV = "../../../etc/passwd"
73
+ expect(require("../../package/env")).toStrictEqual({
74
+ railsEnv: "development",
75
+ nodeEnv: "development",
76
+ isProduction: false,
77
+ isDevelopment: true,
54
78
  runningWebpackDevServer: false
55
79
  })
56
80
  })
81
+
82
+ test("warns when NODE_ENV is invalid", () => {
83
+ const consoleSpy = jest.spyOn(console, "warn").mockImplementation()
84
+ process.env.NODE_ENV = "invalid"
85
+ delete process.env.RAILS_ENV
86
+ require("../../package/env")
87
+ expect(consoleSpy).toHaveBeenCalledWith(
88
+ expect.stringContaining("Invalid NODE_ENV value: invalid")
89
+ )
90
+ consoleSpy.mockRestore()
91
+ })
57
92
  })
@@ -7,6 +7,10 @@ const { chdirTestApp, resetEnv } = require("../../helpers")
7
7
  const rootPath = process.cwd()
8
8
  chdirTestApp()
9
9
 
10
+ // Set NODE_ENV before requiring modules to ensure contenthash is enabled
11
+ // Base config tests expect production-like behavior with contenthash
12
+ process.env.NODE_ENV = "production"
13
+
10
14
  const baseConfig = require("../../../package/environments/base")
11
15
  const config = require("../../../package/config")
12
16
 
@@ -79,7 +83,7 @@ describe("Base config", () => {
79
83
  })
80
84
 
81
85
  test("should return default loader rules for each file in config/loaders", () => {
82
- const rules = require("../../../package/rules")
86
+ const rules = require("../../../package/rules/webpack")
83
87
 
84
88
  const defaultRules = Object.keys(rules)
85
89
  const configRules = baseConfig.module.rules
@@ -11,6 +11,7 @@ jest.mock("../../../package/config", () => {
11
11
  const original = jest.requireActual("../../../package/config")
12
12
  return {
13
13
  ...original,
14
+ javascript_transpiler: "babel", // Force babel for this test
14
15
  additional_paths: [...original.additional_paths, "node_modules/included"]
15
16
  }
16
17
  })
@@ -32,6 +33,21 @@ const createWebpackConfig = (file, use) => ({
32
33
  })
33
34
 
34
35
  describe("babel", () => {
36
+ // Mock validateBabelDependencies to avoid actual dependency checking in tests
37
+ beforeAll(() => {
38
+ jest.mock("../../../package/utils/helpers", () => {
39
+ const original = jest.requireActual("../../../package/utils/helpers")
40
+ return {
41
+ ...original,
42
+ validateBabelDependencies: jest.fn() // Mock to do nothing
43
+ }
44
+ })
45
+ })
46
+
47
+ afterAll(() => {
48
+ jest.unmock("../../../package/utils/helpers")
49
+ })
50
+
35
51
  test("process source path", async () => {
36
52
  const normalPath = `${pathToAppJavascript}/a.js`
37
53
  const [tracked, loader] = createTrackLoader()
@@ -11,7 +11,7 @@ jest.mock("../../../package/config", () => {
11
11
  const original = jest.requireActual("../../../package/config")
12
12
  return {
13
13
  ...original,
14
- webpack_loader: "esbuild",
14
+ javascript_transpiler: "esbuild",
15
15
  additional_paths: [...original.additional_paths, "node_modules/included"]
16
16
  }
17
17
  })
@@ -1,12 +1,45 @@
1
- const raw = require("../../../package/rules/raw")
2
-
3
1
  describe("raw", () => {
4
- test("expected file types", () => {
5
- expect(raw.test.test(".html")).toBe(true)
2
+ describe("rspack bundler", () => {
3
+ beforeEach(() => {
4
+ jest.resetModules()
5
+ jest.doMock("../../../package/config", () => ({
6
+ assets_bundler: "rspack"
7
+ }))
8
+ })
9
+
10
+ afterEach(() => {
11
+ jest.dontMock("../../../package/config")
12
+ })
13
+
14
+ test("uses resourceQuery for any file with ?raw", () => {
15
+ const raw = require("../../../package/rules/raw")
16
+ expect(raw.resourceQuery).toStrictEqual(/raw/)
17
+ expect(raw.type).toBe("asset/source")
18
+ })
6
19
  })
7
20
 
8
- test("exclude expected file types", () => {
9
- const types = [".js", ".mjs", ".jsx", ".ts", ".tsx"]
10
- types.forEach((type) => expect(raw.exclude.test(type)).toBe(true))
21
+ describe("webpack bundler", () => {
22
+ beforeEach(() => {
23
+ jest.resetModules()
24
+ jest.doMock("../../../package/config", () => ({
25
+ assets_bundler: "webpack"
26
+ }))
27
+ })
28
+
29
+ afterEach(() => {
30
+ jest.dontMock("../../../package/config")
31
+ })
32
+
33
+ test("supports ?raw query and .html fallback with oneOf", () => {
34
+ const raw = require("../../../package/rules/raw")
35
+ expect(raw.oneOf).toHaveLength(2)
36
+ // First rule: any file with ?raw
37
+ expect(raw.oneOf[0].resourceQuery).toStrictEqual(/raw/)
38
+ expect(raw.oneOf[0].type).toBe("asset/source")
39
+ // Second rule: .html files without query
40
+ expect(raw.oneOf[1].test.test(".html")).toBe(true)
41
+ expect(raw.oneOf[1].exclude.test(".js")).toBe(true)
42
+ expect(raw.oneOf[1].type).toBe("asset/source")
43
+ })
11
44
  })
12
45
  })
@@ -11,7 +11,7 @@ jest.mock("../../../package/config", () => {
11
11
  const original = jest.requireActual("../../../package/config")
12
12
  return {
13
13
  ...original,
14
- webpack_loader: "swc",
14
+ javascript_transpiler: "swc",
15
15
  additional_paths: [...original.additional_paths, "node_modules/included"]
16
16
  }
17
17
  })
@@ -0,0 +1,35 @@
1
+ const rules = require("../../../package/rules/webpack")
2
+
3
+ jest.mock("../../../package/utils/helpers", () => {
4
+ const original = jest.requireActual("../../../package/utils/helpers")
5
+ const moduleExists = () => false
6
+ return {
7
+ ...original,
8
+ moduleExists
9
+ }
10
+ })
11
+
12
+ describe("index", () => {
13
+ test("rule tests are regexes or oneOf arrays", () => {
14
+ const rulesWithTest = rules.filter((rule) => !rule.oneOf)
15
+ const rulesWithOneOf = rules.filter((rule) => rule.oneOf)
16
+
17
+ // Verify all non-oneOf rules have test property
18
+ rulesWithTest.forEach((rule) => {
19
+ expect(rule.test).toBeInstanceOf(RegExp)
20
+ })
21
+
22
+ // Verify all oneOf rules are properly structured
23
+ rulesWithOneOf.forEach((rule) => {
24
+ expect(Array.isArray(rule.oneOf)).toBe(true)
25
+ rule.oneOf.forEach((subRule) => {
26
+ // Each subRule must have either a test or resourceQuery property (RegExp)
27
+ const matchers = [
28
+ subRule.test instanceof RegExp,
29
+ subRule.resourceQuery instanceof RegExp
30
+ ]
31
+ expect(matchers.some(Boolean)).toBe(true)
32
+ })
33
+ })
34
+ })
35
+ })
@@ -19,7 +19,7 @@ describe("Custom environment", () => {
19
19
  describe("generateWebpackConfig", () => {
20
20
  beforeEach(() => jest.resetModules())
21
21
 
22
- test("should use staging config and default production environment", () => {
22
+ test("should use staging config and default development environment", () => {
23
23
  process.env.RAILS_ENV = "staging"
24
24
  delete process.env.NODE_ENV
25
25
 
@@ -31,9 +31,10 @@ describe("Custom environment", () => {
31
31
  resolve("public", "packs-staging")
32
32
  )
33
33
  expect(webpackConfig.output.publicPath).toBe("/packs-staging/")
34
+ // With the NODE_ENV fix, staging now defaults to development environment
35
+ // instead of production, providing better DX for staging environments
34
36
  expect(webpackConfig).toMatchObject({
35
- devtool: "source-map",
36
- stats: "normal"
37
+ devtool: "cheap-module-source-map"
37
38
  })
38
39
  })
39
40
  })