shakapacker 8.4.0 → 9.0.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 (166) 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 +8 -4
  9. data/.github/workflows/generator.yml +17 -14
  10. data/.github/workflows/node.yml +23 -1
  11. data/.github/workflows/ruby.yml +11 -0
  12. data/.github/workflows/test-bundlers.yml +170 -0
  13. data/.gitignore +17 -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 +156 -18
  20. data/CLAUDE.md +29 -0
  21. data/CONTRIBUTING.md +138 -20
  22. data/Gemfile.lock +3 -3
  23. data/README.md +130 -5
  24. data/Rakefile +39 -4
  25. data/TODO.md +50 -0
  26. data/TODO_v9.md +87 -0
  27. data/conductor-setup.sh +70 -0
  28. data/conductor.json +7 -0
  29. data/docs/cdn_setup.md +379 -0
  30. data/docs/css-modules-export-mode.md +512 -0
  31. data/docs/deployment.md +10 -1
  32. data/docs/optional-peer-dependencies.md +198 -0
  33. data/docs/peer-dependencies.md +60 -0
  34. data/docs/rspack.md +190 -0
  35. data/docs/rspack_migration_guide.md +202 -0
  36. data/docs/transpiler-migration.md +188 -0
  37. data/docs/transpiler-performance.md +179 -0
  38. data/docs/troubleshooting.md +5 -0
  39. data/docs/typescript-migration.md +378 -0
  40. data/docs/typescript.md +99 -0
  41. data/docs/using_esbuild_loader.md +3 -3
  42. data/docs/using_swc_loader.md +5 -3
  43. data/docs/v6_upgrade.md +10 -0
  44. data/docs/v9_upgrade.md +413 -0
  45. data/lib/install/bin/shakapacker +3 -5
  46. data/lib/install/config/rspack/rspack.config.js +6 -0
  47. data/lib/install/config/rspack/rspack.config.ts +7 -0
  48. data/lib/install/config/shakapacker.yml +12 -2
  49. data/lib/install/config/webpack/webpack.config.ts +7 -0
  50. data/lib/install/package.json +38 -0
  51. data/lib/install/template.rb +194 -44
  52. data/lib/shakapacker/configuration.rb +141 -0
  53. data/lib/shakapacker/dev_server_runner.rb +25 -5
  54. data/lib/shakapacker/doctor.rb +844 -0
  55. data/lib/shakapacker/manifest.rb +4 -2
  56. data/lib/shakapacker/rspack_runner.rb +19 -0
  57. data/lib/shakapacker/runner.rb +144 -4
  58. data/lib/shakapacker/swc_migrator.rb +376 -0
  59. data/lib/shakapacker/utils/manager.rb +2 -0
  60. data/lib/shakapacker/version.rb +1 -1
  61. data/lib/shakapacker/version_checker.rb +1 -1
  62. data/lib/shakapacker/webpack_runner.rb +4 -42
  63. data/lib/shakapacker.rb +2 -1
  64. data/lib/tasks/shakapacker/doctor.rake +8 -0
  65. data/lib/tasks/shakapacker/install.rake +12 -2
  66. data/lib/tasks/shakapacker/migrate_to_swc.rake +13 -0
  67. data/lib/tasks/shakapacker.rake +1 -0
  68. data/package/.npmignore +4 -0
  69. data/package/babel/preset.ts +56 -0
  70. data/package/config.ts +175 -0
  71. data/package/{dev_server.js → dev_server.ts} +8 -5
  72. data/package/env.ts +92 -0
  73. data/package/environments/base.ts +138 -0
  74. data/package/environments/development.ts +90 -0
  75. data/package/environments/production.ts +80 -0
  76. data/package/environments/test.ts +53 -0
  77. data/package/environments/types.ts +90 -0
  78. data/package/esbuild/index.ts +42 -0
  79. data/package/index.d.ts +3 -97
  80. data/package/index.ts +52 -0
  81. data/package/loaders.d.ts +28 -0
  82. data/package/optimization/rspack.ts +36 -0
  83. data/package/optimization/webpack.ts +57 -0
  84. data/package/plugins/rspack.ts +103 -0
  85. data/package/plugins/webpack.ts +62 -0
  86. data/package/rspack/index.ts +64 -0
  87. data/package/rules/{babel.js → babel.ts} +2 -2
  88. data/package/rules/{coffee.js → coffee.ts} +1 -1
  89. data/package/rules/css.ts +3 -0
  90. data/package/rules/{erb.js → erb.ts} +1 -1
  91. data/package/rules/esbuild.ts +10 -0
  92. data/package/rules/file.ts +40 -0
  93. data/package/rules/{jscommon.js → jscommon.ts} +4 -4
  94. data/package/rules/{less.js → less.ts} +4 -4
  95. data/package/rules/raw.ts +25 -0
  96. data/package/rules/rspack.ts +176 -0
  97. data/package/rules/{sass.js → sass.ts} +7 -3
  98. data/package/rules/{stylus.js → stylus.ts} +4 -8
  99. data/package/rules/swc.ts +10 -0
  100. data/package/rules/{index.js → webpack.ts} +1 -1
  101. data/package/swc/index.ts +54 -0
  102. data/package/types/README.md +87 -0
  103. data/package/types/index.ts +60 -0
  104. data/package/types.ts +108 -0
  105. data/package/utils/configPath.ts +6 -0
  106. data/package/utils/debug.ts +49 -0
  107. data/package/utils/defaultConfigPath.ts +4 -0
  108. data/package/utils/errorCodes.ts +219 -0
  109. data/package/utils/errorHelpers.ts +143 -0
  110. data/package/utils/getStyleRule.ts +64 -0
  111. data/package/utils/helpers.ts +85 -0
  112. data/package/utils/{inliningCss.js → inliningCss.ts} +3 -3
  113. data/package/utils/pathValidation.ts +139 -0
  114. data/package/utils/requireOrError.ts +15 -0
  115. data/package/utils/snakeToCamelCase.ts +5 -0
  116. data/package/utils/typeGuards.ts +342 -0
  117. data/package/utils/validateDependencies.ts +61 -0
  118. data/package/webpack-types.d.ts +33 -0
  119. data/package/webpackDevServerConfig.ts +117 -0
  120. data/package.json +134 -9
  121. data/scripts/remove-use-strict.js +45 -0
  122. data/scripts/type-check-no-emit.js +27 -0
  123. data/test/package/config.test.js +3 -0
  124. data/test/package/env.test.js +42 -7
  125. data/test/package/environments/base.test.js +5 -1
  126. data/test/package/rules/babel.test.js +16 -0
  127. data/test/package/rules/esbuild.test.js +1 -1
  128. data/test/package/rules/raw.test.js +40 -7
  129. data/test/package/rules/swc.test.js +1 -1
  130. data/test/package/rules/webpack.test.js +35 -0
  131. data/test/package/staging.test.js +4 -3
  132. data/test/package/transpiler-defaults.test.js +127 -0
  133. data/test/peer-dependencies.sh +85 -0
  134. data/test/scripts/remove-use-strict.test.js +125 -0
  135. data/test/typescript/build.test.js +118 -0
  136. data/test/typescript/environments.test.js +107 -0
  137. data/test/typescript/pathValidation.test.js +142 -0
  138. data/test/typescript/securityValidation.test.js +182 -0
  139. data/tools/README.md +124 -0
  140. data/tools/css-modules-v9-codemod.js +179 -0
  141. data/tsconfig.eslint.json +16 -0
  142. data/tsconfig.json +38 -0
  143. data/yarn.lock +2704 -767
  144. metadata +111 -41
  145. data/package/babel/preset.js +0 -48
  146. data/package/config.js +0 -56
  147. data/package/env.js +0 -48
  148. data/package/environments/base.js +0 -171
  149. data/package/environments/development.js +0 -13
  150. data/package/environments/production.js +0 -88
  151. data/package/environments/test.js +0 -3
  152. data/package/esbuild/index.js +0 -40
  153. data/package/index.js +0 -40
  154. data/package/rules/css.js +0 -3
  155. data/package/rules/esbuild.js +0 -10
  156. data/package/rules/file.js +0 -29
  157. data/package/rules/raw.js +0 -5
  158. data/package/rules/swc.js +0 -10
  159. data/package/swc/index.js +0 -50
  160. data/package/utils/configPath.js +0 -4
  161. data/package/utils/defaultConfigPath.js +0 -2
  162. data/package/utils/getStyleRule.js +0 -40
  163. data/package/utils/helpers.js +0 -62
  164. data/package/utils/snakeToCamelCase.js +0 -5
  165. data/package/webpackDevServerConfig.js +0 -71
  166. data/test/package/rules/index.test.js +0 -16
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Validates that required dependencies are installed for the selected bundler
3
+ */
4
+
5
+ const { moduleExists } = require("./helpers")
6
+ const { error } = require("./debug")
7
+
8
+ const validateRspackDependencies = (): void => {
9
+ const requiredDependencies = ["@rspack/core", "rspack-manifest-plugin"]
10
+
11
+ const missingDependencies = requiredDependencies.filter(
12
+ (dep) => !moduleExists(dep)
13
+ )
14
+
15
+ if (missingDependencies.length > 0) {
16
+ error(
17
+ `Missing required dependencies for RSpack:\n${missingDependencies
18
+ .map((dep) => ` - ${dep}`)
19
+ .join(
20
+ "\n"
21
+ )}\n\nPlease install them with:\n npm install ${missingDependencies.join(
22
+ " "
23
+ )}`
24
+ )
25
+ throw new Error(
26
+ `Missing RSpack dependencies: ${missingDependencies.join(", ")}`
27
+ )
28
+ }
29
+ }
30
+
31
+ const validateWebpackDependencies = (): void => {
32
+ const requiredDependencies = [
33
+ "webpack",
34
+ "webpack-cli",
35
+ "webpack-assets-manifest"
36
+ ]
37
+
38
+ const missingDependencies = requiredDependencies.filter(
39
+ (dep) => !moduleExists(dep)
40
+ )
41
+
42
+ if (missingDependencies.length > 0) {
43
+ error(
44
+ `Missing required dependencies for Webpack:\n${missingDependencies
45
+ .map((dep) => ` - ${dep}`)
46
+ .join(
47
+ "\n"
48
+ )}\n\nPlease install them with:\n npm install ${missingDependencies.join(
49
+ " "
50
+ )}`
51
+ )
52
+ throw new Error(
53
+ `Missing Webpack dependencies: ${missingDependencies.join(", ")}`
54
+ )
55
+ }
56
+ }
57
+
58
+ export = {
59
+ validateRspackDependencies,
60
+ validateWebpackDependencies
61
+ }
@@ -0,0 +1,33 @@
1
+ // @ts-ignore: webpack is an optional peer dependency (using type-only import)
2
+ import type { Configuration, RuleSetRule, RuleSetUseItem } from 'webpack'
3
+
4
+ export interface ShakapackerWebpackConfig extends Configuration {
5
+ module?: Configuration['module'] & {
6
+ rules?: RuleSetRule[]
7
+ }
8
+ }
9
+
10
+ export interface ShakapackerRule extends RuleSetRule {
11
+ test: RegExp
12
+ use: RuleSetUseItem[]
13
+ }
14
+
15
+ export interface ShakapackerLoaderOptions {
16
+ [key: string]: any
17
+ }
18
+
19
+ export interface ShakapackerLoader {
20
+ loader: string
21
+ options?: ShakapackerLoaderOptions
22
+ }
23
+
24
+ export type LoaderType = string | ShakapackerLoader
25
+
26
+ export interface LoaderUtils {
27
+ resolveLoader(name: string): string
28
+ createRule(test: RegExp, loaders: LoaderType[]): ShakapackerRule
29
+ getStyleLoader(extract?: boolean): LoaderType
30
+ getCssLoader(modules?: boolean): LoaderType
31
+ getPostCssLoader(): LoaderType
32
+ getSassLoader(): LoaderType
33
+ }
@@ -0,0 +1,117 @@
1
+ import { DevServerConfig } from "./types"
2
+ const snakeToCamelCase = require("./utils/snakeToCamelCase")
3
+
4
+ const shakapackerDevServerYamlConfig = require("./dev_server") as DevServerConfig
5
+ const { outputPath: contentBase, publicPath } = require("./config") as {
6
+ outputPath: string
7
+ publicPath: string
8
+ }
9
+
10
+ interface WebpackDevServerConfig {
11
+ devMiddleware?: {
12
+ publicPath?: string
13
+ }
14
+ hot?: boolean | string
15
+ liveReload?: boolean
16
+ historyApiFallback?: boolean | {
17
+ disableDotRule?: boolean
18
+ }
19
+ static?: {
20
+ publicPath?: string
21
+ [key: string]: unknown
22
+ }
23
+ client?: Record<string, unknown>
24
+ allowedHosts?: "all" | "auto" | string | string[]
25
+ bonjour?: boolean | Record<string, unknown>
26
+ compress?: boolean
27
+ headers?: Record<string, unknown> | (() => Record<string, unknown>)
28
+ host?: "local-ip" | "local-ipv4" | "local-ipv6" | string
29
+ http2?: boolean
30
+ https?: boolean | Record<string, unknown>
31
+ ipc?: boolean | string
32
+ magicHtml?: boolean
33
+ onAfterSetupMiddleware?: (devServer: unknown) => void
34
+ onBeforeSetupMiddleware?: (devServer: unknown) => void
35
+ open?: boolean | string | string[] | Record<string, unknown> | Record<string, unknown>[]
36
+ port?: "auto" | string | number
37
+ proxy?: unknown
38
+ server?: string | boolean | Record<string, unknown>
39
+ setupExitSignals?: boolean
40
+ setupMiddlewares?: (middlewares: unknown[], devServer: unknown) => unknown[]
41
+ watchFiles?: string | string[] | unknown
42
+ webSocketServer?: string | boolean | Record<string, unknown>
43
+ [key: string]: unknown
44
+ }
45
+
46
+ const webpackDevServerMappedKeys = new Set([
47
+ // client, server, liveReload, devMiddleware are handled separately
48
+ "allowedHosts",
49
+ "bonjour",
50
+ "compress",
51
+ "headers",
52
+ "historyApiFallback",
53
+ "host",
54
+ "hot",
55
+ "http2",
56
+ "https",
57
+ "ipc",
58
+ "magicHtml",
59
+ "onAfterSetupMiddleware",
60
+ "onBeforeSetupMiddleware",
61
+ "open",
62
+ "port",
63
+ "proxy",
64
+ "server",
65
+ "setupExitSignals",
66
+ "setupMiddlewares",
67
+ "watchFiles",
68
+ "webSocketServer"
69
+ ])
70
+
71
+ function createDevServerConfig(): WebpackDevServerConfig {
72
+ const devServerYamlConfig = { ...shakapackerDevServerYamlConfig } as DevServerConfig & Record<string, unknown>
73
+ const liveReload =
74
+ devServerYamlConfig.live_reload !== undefined
75
+ ? devServerYamlConfig.live_reload
76
+ : !devServerYamlConfig.hmr
77
+ delete devServerYamlConfig.live_reload
78
+
79
+ const config: WebpackDevServerConfig = {
80
+ devMiddleware: {
81
+ publicPath
82
+ },
83
+ hot: devServerYamlConfig.hmr,
84
+ liveReload,
85
+ historyApiFallback: {
86
+ disableDotRule: true
87
+ },
88
+ static: {
89
+ publicPath: contentBase
90
+ }
91
+ }
92
+ delete devServerYamlConfig.hmr
93
+
94
+ if (devServerYamlConfig.static) {
95
+ config.static = {
96
+ ...config.static,
97
+ ...(typeof devServerYamlConfig.static === 'object' ? devServerYamlConfig.static as Record<string, unknown> : {})
98
+ }
99
+ delete devServerYamlConfig.static
100
+ }
101
+
102
+ if (devServerYamlConfig.client) {
103
+ config.client = devServerYamlConfig.client
104
+ delete devServerYamlConfig.client
105
+ }
106
+
107
+ Object.keys(devServerYamlConfig).forEach((yamlKey) => {
108
+ const camelYamlKey = snakeToCamelCase(yamlKey)
109
+ if (webpackDevServerMappedKeys.has(camelYamlKey)) {
110
+ config[camelYamlKey] = devServerYamlConfig[yamlKey]
111
+ }
112
+ })
113
+
114
+ return config
115
+ }
116
+
117
+ export = createDevServerConfig
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shakapacker",
3
- "version": "8.4.0",
3
+ "version": "9.0.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,24 +14,54 @@
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
+ "./package.json": "./package.json",
25
+ "./package/babel/preset.js": "./package/babel/preset.js",
26
+ "./package/*": "./package/*"
27
+ },
17
28
  "files": [
18
29
  "package",
19
30
  "lib/install/config/shakapacker.yml"
20
31
  ],
21
32
  "scripts": {
33
+ "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",
34
+ "build": "tsc && node scripts/remove-use-strict.js && yarn prettier --write 'package/**/*.js'",
35
+ "build:types": "tsc",
22
36
  "lint": "eslint .",
23
- "test": "jest"
37
+ "lint:fast": "eslint . --ext .js,.jsx,.ts,.tsx --config .eslintrc.fast.js",
38
+ "test": "jest",
39
+ "type-check": "tsc --noEmit",
40
+ "prepublishOnly": "yarn build && yarn type-check"
24
41
  },
25
42
  "dependencies": {
26
43
  "js-yaml": "^4.1.0",
27
- "path-complete-extname": "^1.0.0"
44
+ "path-complete-extname": "^1.0.0",
45
+ "webpack-merge": "^5.8.0"
28
46
  },
29
47
  "devDependencies": {
48
+ "@rspack/cli": "^1.4.11",
49
+ "@rspack/core": "^1.4.11",
50
+ "@types/babel__core": "^7.20.5",
51
+ "@types/js-yaml": "^4.0.9",
52
+ "@types/node": "^24.5.2",
53
+ "@types/path-complete-extname": "^1.0.3",
54
+ "@types/webpack": "^5.28.5",
55
+ "@types/webpack-dev-server": "^4.7.2",
56
+ "@types/webpack-merge": "^5.0.0",
57
+ "@typescript-eslint/eslint-plugin": "^8.45.0",
58
+ "@typescript-eslint/parser": "^8.45.0",
30
59
  "babel-loader": "^8.2.4",
31
60
  "compression-webpack-plugin": "^9.0.0",
61
+ "css-loader": "^7.1.2",
32
62
  "esbuild-loader": "^2.18.0",
33
63
  "eslint": "^8.0.0",
34
- "eslint-config-airbnb": "^19.0.0",
64
+ "eslint-config-airbnb": "^19.0.4",
35
65
  "eslint-config-prettier": "^9.0.0",
36
66
  "eslint-plugin-import": "^2.31.0",
37
67
  "eslint-plugin-jest": "^27.9.0",
@@ -39,45 +69,140 @@
39
69
  "eslint-plugin-prettier": "^5.2.6",
40
70
  "eslint-plugin-react": "^7.37.5",
41
71
  "eslint-plugin-react-hooks": "^4.6.0",
72
+ "husky": "^9.1.7",
42
73
  "jest": "^29.7.0",
74
+ "lint-staged": "^15.2.10",
43
75
  "memory-fs": "^0.5.0",
76
+ "mini-css-extract-plugin": "^2.9.4",
44
77
  "prettier": "^3.2.5",
78
+ "rspack-manifest-plugin": "^5.0.3",
79
+ "sass-loader": "^16.0.5",
45
80
  "swc-loader": "^0.1.15",
46
81
  "thenify": "^3.3.1",
82
+ "typescript": "^5.9.2",
47
83
  "webpack": "5.93.0",
48
84
  "webpack-assets-manifest": "^5.0.6",
49
- "webpack-subresource-integrity": "^5.1.0",
50
- "webpack-merge": "^5.8.0"
85
+ "webpack-subresource-integrity": "^5.1.0"
51
86
  },
52
87
  "peerDependencies": {
53
88
  "@babel/core": "^7.17.9",
54
89
  "@babel/plugin-transform-runtime": "^7.17.0",
55
90
  "@babel/preset-env": "^7.16.11",
56
91
  "@babel/runtime": "^7.17.9",
92
+ "@rspack/cli": "^1.0.0",
93
+ "@rspack/core": "^1.0.0",
94
+ "@rspack/plugin-react-refresh": "^1.0.0",
57
95
  "@types/babel__core": "^7.0.0",
58
96
  "@types/webpack": "^5.0.0",
59
97
  "babel-loader": "^8.2.4 || ^9.0.0 || ^10.0.0",
60
- "compression-webpack-plugin": "^9.0.0 || ^10.0.0|| ^11.0.0",
98
+ "compression-webpack-plugin": "^9.0.0 || ^10.0.0 || ^11.0.0",
99
+ "css-loader": "^6.8.1 || ^7.0.0",
100
+ "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",
101
+ "esbuild-loader": "^2.0.0 || ^3.0.0 || ^4.0.0",
102
+ "mini-css-extract-plugin": "^2.0.0",
103
+ "rspack-manifest-plugin": "^5.0.0",
104
+ "sass": "^1.50.0",
105
+ "sass-loader": "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",
106
+ "swc-loader": "^0.1.15 || ^0.2.0",
61
107
  "terser-webpack-plugin": "^5.3.1",
62
108
  "webpack": "^5.76.0",
63
109
  "webpack-assets-manifest": "^5.0.6 || ^6.0.0",
64
- "webpack-subresource-integrity": "^5.1.0",
65
110
  "webpack-cli": "^4.9.2 || ^5.0.0 || ^6.0.0",
66
111
  "webpack-dev-server": "^4.15.2 || ^5.2.2",
67
- "webpack-merge": "^5.8.0 || ^6.0.0"
112
+ "webpack-subresource-integrity": "^5.1.0"
68
113
  },
69
114
  "peerDependenciesMeta": {
115
+ "@babel/core": {
116
+ "optional": true
117
+ },
118
+ "@babel/plugin-transform-runtime": {
119
+ "optional": true
120
+ },
121
+ "@babel/preset-env": {
122
+ "optional": true
123
+ },
124
+ "@babel/runtime": {
125
+ "optional": true
126
+ },
127
+ "@rspack/core": {
128
+ "optional": true
129
+ },
130
+ "@rspack/cli": {
131
+ "optional": true
132
+ },
133
+ "@rspack/plugin-react-refresh": {
134
+ "optional": true
135
+ },
70
136
  "@types/babel__core": {
71
137
  "optional": true
72
138
  },
73
139
  "@types/webpack": {
74
140
  "optional": true
75
141
  },
142
+ "babel-loader": {
143
+ "optional": true
144
+ },
145
+ "compression-webpack-plugin": {
146
+ "optional": true
147
+ },
148
+ "css-loader": {
149
+ "optional": true
150
+ },
151
+ "esbuild": {
152
+ "optional": true
153
+ },
154
+ "esbuild-loader": {
155
+ "optional": true
156
+ },
157
+ "mini-css-extract-plugin": {
158
+ "optional": true
159
+ },
160
+ "rspack-manifest-plugin": {
161
+ "optional": true
162
+ },
163
+ "sass": {
164
+ "optional": true
165
+ },
166
+ "sass-loader": {
167
+ "optional": true
168
+ },
169
+ "swc-loader": {
170
+ "optional": true
171
+ },
172
+ "terser-webpack-plugin": {
173
+ "optional": true
174
+ },
175
+ "webpack": {
176
+ "optional": true
177
+ },
178
+ "webpack-assets-manifest": {
179
+ "optional": true
180
+ },
181
+ "webpack-cli": {
182
+ "optional": true
183
+ },
184
+ "webpack-dev-server": {
185
+ "optional": true
186
+ },
76
187
  "webpack-subresource-integrity": {
77
188
  "optional": true
78
189
  }
79
190
  },
80
191
  "packageManager": "yarn@1.22.22",
192
+ "lint-staged": {
193
+ "*.{js,jsx}": [
194
+ "eslint --fix",
195
+ "prettier --write"
196
+ ],
197
+ "*.{ts,tsx}": [
198
+ "eslint --fix",
199
+ "prettier --write",
200
+ "node scripts/type-check-no-emit.js"
201
+ ],
202
+ "*.{json,yml,yaml,md}": [
203
+ "prettier --write"
204
+ ]
205
+ },
81
206
  "engines": {
82
207
  "node": ">= 14",
83
208
  "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
+ }
@@ -71,6 +71,7 @@ describe("Config", () => {
71
71
  })
72
72
 
73
73
  test("should allow enabling integrity", () => {
74
+ process.env.RAILS_ENV = "production"
74
75
  process.env.SHAKAPACKER_CONFIG = "config/shakapacker_integrity.yml"
75
76
  const config = require("../../package/config")
76
77
 
@@ -78,6 +79,7 @@ describe("Config", () => {
78
79
  })
79
80
 
80
81
  test("should allow configuring hash functions", () => {
82
+ process.env.RAILS_ENV = "production"
81
83
  process.env.SHAKAPACKER_CONFIG = "config/shakapacker_integrity.yml"
82
84
  const config = require("../../package/config")
83
85
 
@@ -89,6 +91,7 @@ describe("Config", () => {
89
91
  })
90
92
 
91
93
  test("should allow configuring crossorigin", () => {
94
+ process.env.RAILS_ENV = "production"
92
95
  process.env.SHAKAPACKER_CONFIG = "config/shakapacker_integrity.yml"
93
96
  const config = require("../../package/config")
94
97
 
@@ -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
  })