shakapacker 9.2.0 → 9.3.0.beta.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 (115) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +6 -9
  3. data/.github/ISSUE_TEMPLATE/feature_request.md +6 -8
  4. data/.github/workflows/claude-code-review.yml +4 -5
  5. data/.github/workflows/claude.yml +1 -2
  6. data/.github/workflows/dummy.yml +4 -4
  7. data/.github/workflows/generator.yml +9 -9
  8. data/.github/workflows/node.yml +11 -2
  9. data/.github/workflows/ruby.yml +16 -16
  10. data/.github/workflows/test-bundlers.yml +9 -9
  11. data/.gitignore +4 -0
  12. data/CHANGELOG.md +19 -4
  13. data/CLAUDE.md +6 -1
  14. data/CONTRIBUTING.md +0 -1
  15. data/Gemfile.lock +1 -1
  16. data/README.md +14 -14
  17. data/TODO.md +10 -2
  18. data/TODO_v9.md +13 -3
  19. data/bin/export-bundler-config +1 -1
  20. data/conductor-setup.sh +1 -1
  21. data/conductor.json +1 -1
  22. data/docs/cdn_setup.md +13 -8
  23. data/docs/common-upgrades.md +2 -1
  24. data/docs/configuration.md +630 -0
  25. data/docs/css-modules-export-mode.md +120 -100
  26. data/docs/customizing_babel_config.md +16 -16
  27. data/docs/deployment.md +18 -0
  28. data/docs/developing_shakapacker.md +6 -0
  29. data/docs/optional-peer-dependencies.md +9 -4
  30. data/docs/peer-dependencies.md +17 -6
  31. data/docs/precompile_hook.md +342 -0
  32. data/docs/react.md +57 -47
  33. data/docs/releasing.md +0 -2
  34. data/docs/rspack.md +25 -21
  35. data/docs/rspack_migration_guide.md +335 -8
  36. data/docs/sprockets.md +1 -0
  37. data/docs/style_loader_vs_mini_css.md +12 -12
  38. data/docs/subresource_integrity.md +13 -7
  39. data/docs/transpiler-performance.md +40 -19
  40. data/docs/troubleshooting.md +0 -2
  41. data/docs/typescript-migration.md +48 -39
  42. data/docs/typescript.md +12 -8
  43. data/docs/using_esbuild_loader.md +10 -10
  44. data/docs/v6_upgrade.md +33 -20
  45. data/docs/v7_upgrade.md +8 -6
  46. data/docs/v8_upgrade.md +13 -12
  47. data/docs/v9_upgrade.md +2 -1
  48. data/eslint.config.fast.js +134 -0
  49. data/eslint.config.js +140 -0
  50. data/knip.ts +54 -0
  51. data/lib/install/bin/export-bundler-config +1 -1
  52. data/lib/install/config/shakapacker.yml +16 -5
  53. data/lib/shakapacker/compiler.rb +80 -0
  54. data/lib/shakapacker/configuration.rb +33 -5
  55. data/lib/shakapacker/dev_server_runner.rb +140 -1
  56. data/lib/shakapacker/doctor.rb +294 -65
  57. data/lib/shakapacker/instance.rb +8 -3
  58. data/lib/shakapacker/runner.rb +244 -8
  59. data/lib/shakapacker/version.rb +1 -1
  60. data/lib/tasks/shakapacker/doctor.rake +42 -2
  61. data/package/babel/preset.ts +7 -4
  62. data/package/config.ts +42 -30
  63. data/package/configExporter/cli.ts +799 -208
  64. data/package/configExporter/configFile.ts +520 -0
  65. data/package/configExporter/fileWriter.ts +12 -8
  66. data/package/configExporter/index.ts +9 -1
  67. data/package/configExporter/types.ts +36 -2
  68. data/package/configExporter/yamlSerializer.ts +22 -8
  69. data/package/dev_server.ts +1 -1
  70. data/package/environments/__type-tests__/rspack-plugin-compatibility.ts +11 -5
  71. data/package/environments/base.ts +18 -13
  72. data/package/environments/development.ts +1 -1
  73. data/package/environments/production.ts +4 -1
  74. data/package/index.d.ts +50 -3
  75. data/package/index.d.ts.template +50 -0
  76. data/package/index.ts +7 -7
  77. data/package/loaders.d.ts +2 -2
  78. data/package/optimization/rspack.ts +1 -1
  79. data/package/plugins/rspack.ts +15 -4
  80. data/package/plugins/webpack.ts +7 -3
  81. data/package/rspack/index.ts +10 -2
  82. data/package/rules/raw.ts +3 -2
  83. data/package/rules/sass.ts +1 -1
  84. data/package/types/README.md +15 -13
  85. data/package/types/index.ts +5 -5
  86. data/package/types.ts +0 -1
  87. data/package/utils/defaultConfigPath.ts +4 -1
  88. data/package/utils/errorCodes.ts +129 -100
  89. data/package/utils/errorHelpers.ts +34 -29
  90. data/package/utils/getStyleRule.ts +5 -2
  91. data/package/utils/helpers.ts +21 -11
  92. data/package/utils/pathValidation.ts +43 -35
  93. data/package/utils/requireOrError.ts +1 -1
  94. data/package/utils/snakeToCamelCase.ts +1 -1
  95. data/package/utils/typeGuards.ts +132 -83
  96. data/package/utils/validateDependencies.ts +1 -1
  97. data/package/webpack-types.d.ts +3 -3
  98. data/package/webpackDevServerConfig.ts +22 -10
  99. data/package-lock.json +2 -2
  100. data/package.json +36 -28
  101. data/scripts/type-check-no-emit.js +1 -1
  102. data/test/configExporter/configFile.test.js +392 -0
  103. data/test/configExporter/integration.test.js +275 -0
  104. data/test/helpers.js +1 -1
  105. data/test/package/configExporter.test.js +154 -0
  106. data/test/package/helpers.test.js +2 -2
  107. data/test/package/rules/sass-version-parsing.test.js +71 -0
  108. data/test/package/rules/sass.test.js +2 -4
  109. data/test/package/rules/sass1.test.js +1 -3
  110. data/test/package/rules/sass16.test.js +23 -0
  111. data/tools/README.md +15 -5
  112. data/tsconfig.eslint.json +2 -9
  113. data/yarn.lock +1894 -1492
  114. metadata +19 -3
  115. data/.eslintignore +0 -5
@@ -12,17 +12,17 @@ export function isPathTraversalSafe(inputPath: string): boolean {
12
12
  // Check for common traversal patterns
13
13
  // Null byte short-circuit (avoid regex with control chars)
14
14
  if (inputPath.includes("\0")) return false
15
-
15
+
16
16
  const dangerousPatterns = [
17
- /\.\.[\/\\]/, // ../ or ..\
18
- /^\//, // POSIX absolute
19
- /^[A-Za-z]:[\/\\]/, // Windows absolute (C:\ or C:/)
20
- /^\\\\/, // Windows UNC (\\server\share)
21
- /~[\/\\]/, // Home directory expansion
22
- /%2e%2e/i, // URL encoded traversal
17
+ /\.\.[\/\\]/, // ../ or ..\
18
+ /^\//, // POSIX absolute
19
+ /^[A-Za-z]:[\/\\]/, // Windows absolute (C:\ or C:/)
20
+ /^\\\\/, // Windows UNC (\\server\share)
21
+ /~[\/\\]/, // Home directory expansion
22
+ /%2e%2e/i // URL encoded traversal
23
23
  ]
24
24
 
25
- return !dangerousPatterns.some(pattern => pattern.test(inputPath))
25
+ return !dangerousPatterns.some((pattern) => pattern.test(inputPath))
26
26
  }
27
27
 
28
28
  /**
@@ -33,21 +33,24 @@ export function isPathTraversalSafe(inputPath: string): boolean {
33
33
  export function safeResolvePath(basePath: string, userPath: string): string {
34
34
  // Normalize the base path
35
35
  const normalizedBase = path.resolve(basePath)
36
-
36
+
37
37
  // Resolve the user path relative to base
38
38
  const resolved = path.resolve(normalizedBase, userPath)
39
-
39
+
40
40
  // Ensure the resolved path is within the base directory
41
- if (!resolved.startsWith(normalizedBase + path.sep) && resolved !== normalizedBase) {
41
+ if (
42
+ !resolved.startsWith(normalizedBase + path.sep) &&
43
+ resolved !== normalizedBase
44
+ ) {
42
45
  throw new Error(
43
46
  `[SHAKAPACKER SECURITY] Path traversal attempt detected.\n` +
44
- `Requested path would resolve outside of allowed directory.\n` +
45
- `Base: ${normalizedBase}\n` +
46
- `Attempted: ${userPath}\n` +
47
- `Resolved to: ${resolved}`
47
+ `Requested path would resolve outside of allowed directory.\n` +
48
+ `Base: ${normalizedBase}\n` +
49
+ `Attempted: ${userPath}\n` +
50
+ `Resolved to: ${resolved}`
48
51
  )
49
52
  }
50
-
53
+
51
54
  return resolved
52
55
  }
53
56
 
@@ -68,7 +71,7 @@ export function validatePathExists(filePath: string): boolean {
68
71
  */
69
72
  export function validatePaths(paths: string[], basePath: string): string[] {
70
73
  const validatedPaths: string[] = []
71
-
74
+
72
75
  for (const userPath of paths) {
73
76
  if (!isPathTraversalSafe(userPath)) {
74
77
  console.warn(
@@ -76,42 +79,47 @@ export function validatePaths(paths: string[], basePath: string): string[] {
76
79
  )
77
80
  continue
78
81
  }
79
-
82
+
80
83
  try {
81
84
  const safePath = safeResolvePath(basePath, userPath)
82
85
  validatedPaths.push(safePath)
83
86
  } catch (error) {
84
87
  console.warn(
85
88
  `[SHAKAPACKER WARNING] Invalid path configuration: ${userPath}\n` +
86
- `Error: ${error instanceof Error ? error.message : String(error)}`
89
+ `Error: ${error instanceof Error ? error.message : String(error)}`
87
90
  )
88
91
  }
89
92
  }
90
-
93
+
91
94
  return validatedPaths
92
95
  }
93
96
 
94
97
  /**
95
98
  * Sanitizes environment variable values to prevent injection
96
99
  */
97
- export function sanitizeEnvValue(value: string | undefined): string | undefined {
100
+ export function sanitizeEnvValue(
101
+ value: string | undefined
102
+ ): string | undefined {
98
103
  if (!value) return value
99
-
104
+
100
105
  // Remove control characters and null bytes
101
106
  // Filter by character code to avoid control character regex (Biome compliance)
102
- const sanitized = value.split('').filter(char => {
103
- const code = char.charCodeAt(0)
104
- // Keep chars with code > 31 (after control chars) and not 127 (DEL)
105
- return code > 31 && code !== 127
106
- }).join('')
107
-
107
+ const sanitized = value
108
+ .split("")
109
+ .filter((char) => {
110
+ const code = char.charCodeAt(0)
111
+ // Keep chars with code > 31 (after control chars) and not 127 (DEL)
112
+ return code > 31 && code !== 127
113
+ })
114
+ .join("")
115
+
108
116
  // Warn if sanitization changed the value
109
117
  if (sanitized !== value) {
110
118
  console.warn(
111
119
  `[SHAKAPACKER SECURITY] Environment variable value contained control characters that were removed`
112
120
  )
113
121
  }
114
-
122
+
115
123
  return sanitized
116
124
  }
117
125
 
@@ -119,13 +127,13 @@ export function sanitizeEnvValue(value: string | undefined): string | undefined
119
127
  * Validates a port number or string
120
128
  */
121
129
  export function validatePort(port: unknown): boolean {
122
- if (port === 'auto') return true
123
-
124
- if (typeof port === 'number') {
130
+ if (port === "auto") return true
131
+
132
+ if (typeof port === "number") {
125
133
  return port > 0 && port <= 65535 && Number.isInteger(port)
126
134
  }
127
-
128
- if (typeof port === 'string') {
135
+
136
+ if (typeof port === "string") {
129
137
  // First check if the string contains only digits
130
138
  if (!/^\d+$/.test(port)) {
131
139
  return false
@@ -134,6 +142,6 @@ export function validatePort(port: unknown): boolean {
134
142
  const num = parseInt(port, 10)
135
143
  return num > 0 && num <= 65535
136
144
  }
137
-
145
+
138
146
  return false
139
147
  }
@@ -12,4 +12,4 @@ const requireOrError = (moduleName: string): any => {
12
12
  }
13
13
  }
14
14
 
15
- export = { requireOrError }
15
+ export = { requireOrError }
@@ -2,4 +2,4 @@ function snakeToCamelCase(s: string): string {
2
2
  return s.replace(/(_\w)/g, (match) => match[1].toUpperCase())
3
3
  }
4
4
 
5
- export = snakeToCamelCase
5
+ export = snakeToCamelCase
@@ -19,7 +19,8 @@ let cachedCacheTTL: number | null = null
19
19
  */
20
20
  function isWatchMode(): boolean {
21
21
  if (cachedIsWatchMode === null) {
22
- cachedIsWatchMode = process.argv.includes('--watch') || process.env.WEBPACK_WATCH === 'true'
22
+ cachedIsWatchMode =
23
+ process.argv.includes("--watch") || process.env.WEBPACK_WATCH === "true"
23
24
  }
24
25
  return cachedIsWatchMode
25
26
  }
@@ -31,10 +32,10 @@ function getCacheTTL(): number {
31
32
  if (cachedCacheTTL === null) {
32
33
  if (process.env.SHAKAPACKER_CACHE_TTL) {
33
34
  cachedCacheTTL = parseInt(process.env.SHAKAPACKER_CACHE_TTL, 10)
34
- } else if (process.env.NODE_ENV === 'production' && !isWatchMode()) {
35
+ } else if (process.env.NODE_ENV === "production" && !isWatchMode()) {
35
36
  cachedCacheTTL = Infinity
36
37
  } else if (isWatchMode()) {
37
- cachedCacheTTL = 5000 // 5 seconds in watch mode
38
+ cachedCacheTTL = 5000 // 5 seconds in watch mode
38
39
  } else {
39
40
  cachedCacheTTL = 60000 // 1 minute in dev
40
41
  }
@@ -44,11 +45,14 @@ function getCacheTTL(): number {
44
45
 
45
46
  // Only validate in development or when explicitly enabled
46
47
  function shouldValidate(): boolean {
47
- return process.env.NODE_ENV !== 'production' || process.env.SHAKAPACKER_STRICT_VALIDATION === 'true'
48
+ return (
49
+ process.env.NODE_ENV !== "production" ||
50
+ process.env.SHAKAPACKER_STRICT_VALIDATION === "true"
51
+ )
48
52
  }
49
53
 
50
54
  // Debug logging for cache operations
51
- const debugCache = process.env.SHAKAPACKER_DEBUG_CACHE === 'true'
55
+ const debugCache = process.env.SHAKAPACKER_DEBUG_CACHE === "true"
52
56
 
53
57
  /**
54
58
  * Clear the validation cache
@@ -58,7 +62,7 @@ export function clearValidationCache(): void {
58
62
  // Reassign to a new WeakMap to clear all entries
59
63
  validatedConfigs = new WeakMap<object, CacheEntry>()
60
64
  if (debugCache) {
61
- console.log('[SHAKAPACKER DEBUG] Validation cache cleared')
65
+ console.log("[SHAKAPACKER DEBUG] Validation cache cleared")
62
66
  }
63
67
  }
64
68
 
@@ -70,15 +74,17 @@ export function clearValidationCache(): void {
70
74
  * This ensures application security is never compromised for performance.
71
75
  */
72
76
  export function isValidConfig(obj: unknown): obj is Config {
73
- if (typeof obj !== 'object' || obj === null) {
77
+ if (typeof obj !== "object" || obj === null) {
74
78
  return false
75
79
  }
76
80
 
77
81
  // Check cache with TTL
78
82
  const cached = validatedConfigs.get(obj as object)
79
- if (cached && (Date.now() - cached.timestamp) < getCacheTTL()) {
83
+ if (cached && Date.now() - cached.timestamp < getCacheTTL()) {
80
84
  if (debugCache) {
81
- console.log(`[SHAKAPACKER DEBUG] Config validation cache hit (result: ${cached.result})`)
85
+ console.log(
86
+ `[SHAKAPACKER DEBUG] Config validation cache hit (result: ${cached.result})`
87
+ )
82
88
  }
83
89
  return cached.result
84
90
  }
@@ -87,53 +93,70 @@ export function isValidConfig(obj: unknown): obj is Config {
87
93
 
88
94
  // Check required string fields
89
95
  const requiredStringFields = [
90
- 'source_path',
91
- 'source_entry_path',
92
- 'public_root_path',
93
- 'public_output_path',
94
- 'cache_path',
95
- 'javascript_transpiler'
96
+ "source_path",
97
+ "source_entry_path",
98
+ "public_root_path",
99
+ "public_output_path",
100
+ "cache_path",
101
+ "javascript_transpiler"
96
102
  ]
97
103
 
98
104
  for (const field of requiredStringFields) {
99
- if (typeof config[field] !== 'string') {
105
+ if (typeof config[field] !== "string") {
100
106
  // Cache negative result
101
- validatedConfigs.set(obj as object, { result: false, timestamp: Date.now() })
107
+ validatedConfigs.set(obj as object, {
108
+ result: false,
109
+ timestamp: Date.now()
110
+ })
102
111
  return false
103
112
  }
104
113
  // SECURITY: Path traversal validation ALWAYS runs (not subject to shouldValidate)
105
114
  // This ensures paths are safe regardless of environment or validation mode
106
- if (field.includes('path') && !isPathTraversalSafe(config[field] as string)) {
107
- console.warn(`[SHAKAPACKER SECURITY] Invalid path in ${field}: ${config[field]}`)
108
- validatedConfigs.set(obj as object, { result: false, timestamp: Date.now() })
115
+ if (
116
+ field.includes("path") &&
117
+ !isPathTraversalSafe(config[field] as string)
118
+ ) {
119
+ console.warn(
120
+ `[SHAKAPACKER SECURITY] Invalid path in ${field}: ${config[field]}`
121
+ )
122
+ validatedConfigs.set(obj as object, {
123
+ result: false,
124
+ timestamp: Date.now()
125
+ })
109
126
  return false
110
127
  }
111
128
  }
112
-
129
+
113
130
  // Check required boolean fields
114
131
  const requiredBooleanFields = [
115
- 'nested_entries',
116
- 'css_extract_ignore_order_warnings',
117
- 'webpack_compile_output',
118
- 'shakapacker_precompile',
119
- 'cache_manifest',
120
- 'ensure_consistent_versioning',
121
- 'useContentHash',
122
- 'compile'
132
+ "nested_entries",
133
+ "css_extract_ignore_order_warnings",
134
+ "webpack_compile_output",
135
+ "shakapacker_precompile",
136
+ "cache_manifest",
137
+ "ensure_consistent_versioning",
138
+ "useContentHash",
139
+ "compile"
123
140
  ]
124
-
141
+
125
142
  for (const field of requiredBooleanFields) {
126
- if (typeof config[field] !== 'boolean') {
143
+ if (typeof config[field] !== "boolean") {
127
144
  // Cache negative result
128
- validatedConfigs.set(obj as object, { result: false, timestamp: Date.now() })
145
+ validatedConfigs.set(obj as object, {
146
+ result: false,
147
+ timestamp: Date.now()
148
+ })
129
149
  return false
130
150
  }
131
151
  }
132
-
152
+
133
153
  // Check arrays
134
154
  if (!Array.isArray(config.additional_paths)) {
135
155
  // Cache negative result
136
- validatedConfigs.set(obj as object, { result: false, timestamp: Date.now() })
156
+ validatedConfigs.set(obj as object, {
157
+ result: false,
158
+ timestamp: Date.now()
159
+ })
137
160
  return false
138
161
  }
139
162
 
@@ -141,8 +164,13 @@ export function isValidConfig(obj: unknown): obj is Config {
141
164
  // This critical security check ensures user-provided paths cannot escape the project directory
142
165
  for (const additionalPath of config.additional_paths as string[]) {
143
166
  if (!isPathTraversalSafe(additionalPath)) {
144
- console.warn(`[SHAKAPACKER SECURITY] Invalid additional_path: ${additionalPath}`)
145
- validatedConfigs.set(obj as object, { result: false, timestamp: Date.now() })
167
+ console.warn(
168
+ `[SHAKAPACKER SECURITY] Invalid additional_path: ${additionalPath}`
169
+ )
170
+ validatedConfigs.set(obj as object, {
171
+ result: false,
172
+ timestamp: Date.now()
173
+ })
146
174
  return false
147
175
  }
148
176
  }
@@ -156,25 +184,36 @@ export function isValidConfig(obj: unknown): obj is Config {
156
184
  }
157
185
 
158
186
  // Deep validation of optional fields (only in development or with SHAKAPACKER_STRICT_VALIDATION=true)
159
- if (config.dev_server !== undefined && !isValidDevServerConfig(config.dev_server)) {
187
+ if (
188
+ config.dev_server !== undefined &&
189
+ !isValidDevServerConfig(config.dev_server)
190
+ ) {
160
191
  // Cache negative result
161
- validatedConfigs.set(obj as object, { result: false, timestamp: Date.now() })
192
+ validatedConfigs.set(obj as object, {
193
+ result: false,
194
+ timestamp: Date.now()
195
+ })
162
196
  return false
163
197
  }
164
198
 
165
199
  if (config.integrity !== undefined) {
166
200
  const integrity = config.integrity as Record<string, unknown>
167
- if (typeof integrity.enabled !== 'boolean' ||
168
- typeof integrity.cross_origin !== 'string') {
201
+ if (
202
+ typeof integrity.enabled !== "boolean" ||
203
+ typeof integrity.cross_origin !== "string"
204
+ ) {
169
205
  // Cache negative result
170
- validatedConfigs.set(obj as object, { result: false, timestamp: Date.now() })
206
+ validatedConfigs.set(obj as object, {
207
+ result: false,
208
+ timestamp: Date.now()
209
+ })
171
210
  return false
172
211
  }
173
212
  }
174
-
213
+
175
214
  // Cache positive result
176
215
  validatedConfigs.set(obj as object, { result: true, timestamp: Date.now() })
177
-
216
+
178
217
  return true
179
218
  }
180
219
 
@@ -183,28 +222,30 @@ export function isValidConfig(obj: unknown): obj is Config {
183
222
  * In production, performs minimal validation for performance
184
223
  */
185
224
  export function isValidDevServerConfig(obj: unknown): obj is DevServerConfig {
186
- if (typeof obj !== 'object' || obj === null) {
225
+ if (typeof obj !== "object" || obj === null) {
187
226
  return false
188
227
  }
189
-
228
+
190
229
  // In production, skip deep validation unless explicitly enabled
191
230
  if (!shouldValidate()) {
192
231
  return true
193
232
  }
194
-
233
+
195
234
  const config = obj as Record<string, unknown>
196
-
235
+
197
236
  // All fields are optional, just check types if present
198
- if (config.hmr !== undefined &&
199
- typeof config.hmr !== 'boolean' &&
200
- config.hmr !== 'only') {
237
+ if (
238
+ config.hmr !== undefined &&
239
+ typeof config.hmr !== "boolean" &&
240
+ config.hmr !== "only"
241
+ ) {
201
242
  return false
202
243
  }
203
-
244
+
204
245
  if (config.port !== undefined && !validatePort(config.port)) {
205
246
  return false
206
247
  }
207
-
248
+
208
249
  return true
209
250
  }
210
251
 
@@ -213,7 +254,7 @@ export function isValidDevServerConfig(obj: unknown): obj is DevServerConfig {
213
254
  * Checks if an object looks like a valid Rspack plugin
214
255
  */
215
256
  export function isValidRspackPlugin(obj: unknown): boolean {
216
- if (typeof obj !== 'object' || obj === null) {
257
+ if (typeof obj !== "object" || obj === null) {
217
258
  return false
218
259
  }
219
260
 
@@ -221,18 +262,21 @@ export function isValidRspackPlugin(obj: unknown): boolean {
221
262
 
222
263
  // Check for common plugin patterns
223
264
  // Most rspack plugins should have an apply method
224
- if (typeof plugin.apply === 'function') {
265
+ if (typeof plugin.apply === "function") {
225
266
  return true
226
267
  }
227
268
 
228
269
  // Check for constructor name pattern (e.g., HtmlRspackPlugin)
229
- const constructorName = plugin.constructor?.name || ''
230
- if (constructorName.includes('Plugin') || constructorName.includes('Rspack')) {
270
+ const constructorName = plugin.constructor?.name || ""
271
+ if (
272
+ constructorName.includes("Plugin") ||
273
+ constructorName.includes("Rspack")
274
+ ) {
231
275
  return true
232
276
  }
233
277
 
234
278
  // Check for common plugin properties
235
- if ('name' in plugin && typeof plugin.name === 'string') {
279
+ if ("name" in plugin && typeof plugin.name === "string") {
236
280
  return true
237
281
  }
238
282
 
@@ -248,7 +292,7 @@ export function isValidRspackPluginArray(arr: unknown): boolean {
248
292
  return false
249
293
  }
250
294
 
251
- return arr.every(item => isValidRspackPlugin(item))
295
+ return arr.every((item) => isValidRspackPlugin(item))
252
296
  }
253
297
 
254
298
  /**
@@ -256,24 +300,24 @@ export function isValidRspackPluginArray(arr: unknown): boolean {
256
300
  * In production, performs minimal validation for performance
257
301
  */
258
302
  export function isValidYamlConfig(obj: unknown): obj is YamlConfig {
259
- if (typeof obj !== 'object' || obj === null) {
303
+ if (typeof obj !== "object" || obj === null) {
260
304
  return false
261
305
  }
262
-
306
+
263
307
  // In production, skip deep validation unless explicitly enabled
264
308
  if (!shouldValidate()) {
265
309
  return true
266
310
  }
267
-
311
+
268
312
  const config = obj as Record<string, unknown>
269
-
313
+
270
314
  // Each key should map to an object
271
315
  for (const env of Object.keys(config)) {
272
- if (typeof config[env] !== 'object' || config[env] === null) {
316
+ if (typeof config[env] !== "object" || config[env] === null) {
273
317
  return false
274
318
  }
275
319
  }
276
-
320
+
277
321
  return true
278
322
  }
279
323
 
@@ -283,47 +327,54 @@ export function isValidYamlConfig(obj: unknown): obj is YamlConfig {
283
327
  * In production, performs minimal validation for performance
284
328
  */
285
329
  export function isPartialConfig(obj: unknown): obj is Partial<Config> {
286
- if (typeof obj !== 'object' || obj === null) {
330
+ if (typeof obj !== "object" || obj === null) {
287
331
  return false
288
332
  }
289
-
333
+
290
334
  // In production, skip deep validation unless explicitly enabled
291
335
  if (!shouldValidate()) {
292
336
  return true
293
337
  }
294
-
338
+
295
339
  const config = obj as Record<string, unknown>
296
-
340
+
297
341
  // Check string fields if present
298
342
  const stringFields = [
299
- 'source_path', 'source_entry_path', 'public_root_path',
300
- 'public_output_path', 'cache_path', 'javascript_transpiler'
343
+ "source_path",
344
+ "source_entry_path",
345
+ "public_root_path",
346
+ "public_output_path",
347
+ "cache_path",
348
+ "javascript_transpiler"
301
349
  ]
302
-
350
+
303
351
  for (const field of stringFields) {
304
- if (field in config && typeof config[field] !== 'string') {
352
+ if (field in config && typeof config[field] !== "string") {
305
353
  return false
306
354
  }
307
355
  }
308
-
356
+
309
357
  // Check boolean fields if present
310
358
  const booleanFields = [
311
- 'nested_entries', 'css_extract_ignore_order_warnings',
312
- 'webpack_compile_output', 'shakapacker_precompile',
313
- 'cache_manifest', 'ensure_consistent_versioning'
359
+ "nested_entries",
360
+ "css_extract_ignore_order_warnings",
361
+ "webpack_compile_output",
362
+ "shakapacker_precompile",
363
+ "cache_manifest",
364
+ "ensure_consistent_versioning"
314
365
  ]
315
-
366
+
316
367
  for (const field of booleanFields) {
317
- if (field in config && typeof config[field] !== 'boolean') {
368
+ if (field in config && typeof config[field] !== "boolean") {
318
369
  return false
319
370
  }
320
371
  }
321
-
372
+
322
373
  // Check arrays if present
323
- if ('additional_paths' in config && !Array.isArray(config.additional_paths)) {
374
+ if ("additional_paths" in config && !Array.isArray(config.additional_paths)) {
324
375
  return false
325
376
  }
326
-
377
+
327
378
  return true
328
379
  }
329
380
 
@@ -338,5 +389,3 @@ export function createConfigValidationError(
338
389
  const message = `Invalid configuration in ${configPath} for environment '${environment}'`
339
390
  return new Error(details ? `${message}: ${details}` : message)
340
391
  }
341
-
342
-
@@ -58,4 +58,4 @@ const validateWebpackDependencies = (): void => {
58
58
  export = {
59
59
  validateRspackDependencies,
60
60
  validateWebpackDependencies
61
- }
61
+ }
@@ -1,8 +1,8 @@
1
1
  // @ts-ignore: webpack is an optional peer dependency (using type-only import)
2
- import type { Configuration, RuleSetRule, RuleSetUseItem } from 'webpack'
2
+ import type { Configuration, RuleSetRule, RuleSetUseItem } from "webpack"
3
3
 
4
4
  export interface ShakapackerWebpackConfig extends Configuration {
5
- module?: Configuration['module'] & {
5
+ module?: Configuration["module"] & {
6
6
  rules?: RuleSetRule[]
7
7
  }
8
8
  }
@@ -30,4 +30,4 @@ export interface LoaderUtils {
30
30
  getCssLoader(modules?: boolean): LoaderType
31
31
  getPostCssLoader(): LoaderType
32
32
  getSassLoader(): LoaderType
33
- }
33
+ }
@@ -1,7 +1,8 @@
1
1
  import { DevServerConfig } from "./types"
2
2
  const snakeToCamelCase = require("./utils/snakeToCamelCase")
3
3
 
4
- const shakapackerDevServerYamlConfig = require("./dev_server") as DevServerConfig
4
+ const shakapackerDevServerYamlConfig =
5
+ require("./dev_server") as DevServerConfig
5
6
  const { outputPath: contentBase, publicPath } = require("./config") as {
6
7
  outputPath: string
7
8
  publicPath: string
@@ -13,9 +14,11 @@ interface WebpackDevServerConfig {
13
14
  }
14
15
  hot?: boolean | string
15
16
  liveReload?: boolean
16
- historyApiFallback?: boolean | {
17
- disableDotRule?: boolean
18
- }
17
+ historyApiFallback?:
18
+ | boolean
19
+ | {
20
+ disableDotRule?: boolean
21
+ }
19
22
  static?: {
20
23
  publicPath?: string
21
24
  [key: string]: unknown
@@ -32,7 +35,12 @@ interface WebpackDevServerConfig {
32
35
  magicHtml?: boolean
33
36
  onAfterSetupMiddleware?: (devServer: unknown) => void
34
37
  onBeforeSetupMiddleware?: (devServer: unknown) => void
35
- open?: boolean | string | string[] | Record<string, unknown> | Record<string, unknown>[]
38
+ open?:
39
+ | boolean
40
+ | string
41
+ | string[]
42
+ | Record<string, unknown>
43
+ | Record<string, unknown>[]
36
44
  port?: "auto" | string | number
37
45
  proxy?: unknown
38
46
  server?: string | boolean | Record<string, unknown>
@@ -69,7 +77,9 @@ const webpackDevServerMappedKeys = new Set([
69
77
  ])
70
78
 
71
79
  function createDevServerConfig(): WebpackDevServerConfig {
72
- const devServerYamlConfig = { ...shakapackerDevServerYamlConfig } as DevServerConfig & Record<string, unknown>
80
+ const devServerYamlConfig = {
81
+ ...shakapackerDevServerYamlConfig
82
+ } as DevServerConfig & Record<string, unknown>
73
83
  const liveReload =
74
84
  devServerYamlConfig.live_reload !== undefined
75
85
  ? devServerYamlConfig.live_reload
@@ -92,9 +102,11 @@ function createDevServerConfig(): WebpackDevServerConfig {
92
102
  delete devServerYamlConfig.hmr
93
103
 
94
104
  if (devServerYamlConfig.static) {
95
- config.static = {
96
- ...config.static,
97
- ...(typeof devServerYamlConfig.static === 'object' ? devServerYamlConfig.static as Record<string, unknown> : {})
105
+ config.static = {
106
+ ...config.static,
107
+ ...(typeof devServerYamlConfig.static === "object"
108
+ ? (devServerYamlConfig.static as Record<string, unknown>)
109
+ : {})
98
110
  }
99
111
  delete devServerYamlConfig.static
100
112
  }
@@ -114,4 +126,4 @@ function createDevServerConfig(): WebpackDevServerConfig {
114
126
  return config
115
127
  }
116
128
 
117
- export = createDevServerConfig
129
+ export = createDevServerConfig