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.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.md +6 -9
- data/.github/ISSUE_TEMPLATE/feature_request.md +6 -8
- data/.github/workflows/claude-code-review.yml +4 -5
- data/.github/workflows/claude.yml +1 -2
- data/.github/workflows/dummy.yml +4 -4
- data/.github/workflows/generator.yml +9 -9
- data/.github/workflows/node.yml +11 -2
- data/.github/workflows/ruby.yml +16 -16
- data/.github/workflows/test-bundlers.yml +9 -9
- data/.gitignore +4 -0
- data/CHANGELOG.md +19 -4
- data/CLAUDE.md +6 -1
- data/CONTRIBUTING.md +0 -1
- data/Gemfile.lock +1 -1
- data/README.md +14 -14
- data/TODO.md +10 -2
- data/TODO_v9.md +13 -3
- data/bin/export-bundler-config +1 -1
- data/conductor-setup.sh +1 -1
- data/conductor.json +1 -1
- data/docs/cdn_setup.md +13 -8
- data/docs/common-upgrades.md +2 -1
- data/docs/configuration.md +630 -0
- data/docs/css-modules-export-mode.md +120 -100
- data/docs/customizing_babel_config.md +16 -16
- data/docs/deployment.md +18 -0
- data/docs/developing_shakapacker.md +6 -0
- data/docs/optional-peer-dependencies.md +9 -4
- data/docs/peer-dependencies.md +17 -6
- data/docs/precompile_hook.md +342 -0
- data/docs/react.md +57 -47
- data/docs/releasing.md +0 -2
- data/docs/rspack.md +25 -21
- data/docs/rspack_migration_guide.md +335 -8
- data/docs/sprockets.md +1 -0
- data/docs/style_loader_vs_mini_css.md +12 -12
- data/docs/subresource_integrity.md +13 -7
- data/docs/transpiler-performance.md +40 -19
- data/docs/troubleshooting.md +0 -2
- data/docs/typescript-migration.md +48 -39
- data/docs/typescript.md +12 -8
- data/docs/using_esbuild_loader.md +10 -10
- data/docs/v6_upgrade.md +33 -20
- data/docs/v7_upgrade.md +8 -6
- data/docs/v8_upgrade.md +13 -12
- data/docs/v9_upgrade.md +2 -1
- data/eslint.config.fast.js +134 -0
- data/eslint.config.js +140 -0
- data/knip.ts +54 -0
- data/lib/install/bin/export-bundler-config +1 -1
- data/lib/install/config/shakapacker.yml +16 -5
- data/lib/shakapacker/compiler.rb +80 -0
- data/lib/shakapacker/configuration.rb +33 -5
- data/lib/shakapacker/dev_server_runner.rb +140 -1
- data/lib/shakapacker/doctor.rb +294 -65
- data/lib/shakapacker/instance.rb +8 -3
- data/lib/shakapacker/runner.rb +244 -8
- data/lib/shakapacker/version.rb +1 -1
- data/lib/tasks/shakapacker/doctor.rake +42 -2
- data/package/babel/preset.ts +7 -4
- data/package/config.ts +42 -30
- data/package/configExporter/cli.ts +799 -208
- data/package/configExporter/configFile.ts +520 -0
- data/package/configExporter/fileWriter.ts +12 -8
- data/package/configExporter/index.ts +9 -1
- data/package/configExporter/types.ts +36 -2
- data/package/configExporter/yamlSerializer.ts +22 -8
- data/package/dev_server.ts +1 -1
- data/package/environments/__type-tests__/rspack-plugin-compatibility.ts +11 -5
- data/package/environments/base.ts +18 -13
- data/package/environments/development.ts +1 -1
- data/package/environments/production.ts +4 -1
- data/package/index.d.ts +50 -3
- data/package/index.d.ts.template +50 -0
- data/package/index.ts +7 -7
- data/package/loaders.d.ts +2 -2
- data/package/optimization/rspack.ts +1 -1
- data/package/plugins/rspack.ts +15 -4
- data/package/plugins/webpack.ts +7 -3
- data/package/rspack/index.ts +10 -2
- data/package/rules/raw.ts +3 -2
- data/package/rules/sass.ts +1 -1
- data/package/types/README.md +15 -13
- data/package/types/index.ts +5 -5
- data/package/types.ts +0 -1
- data/package/utils/defaultConfigPath.ts +4 -1
- data/package/utils/errorCodes.ts +129 -100
- data/package/utils/errorHelpers.ts +34 -29
- data/package/utils/getStyleRule.ts +5 -2
- data/package/utils/helpers.ts +21 -11
- data/package/utils/pathValidation.ts +43 -35
- data/package/utils/requireOrError.ts +1 -1
- data/package/utils/snakeToCamelCase.ts +1 -1
- data/package/utils/typeGuards.ts +132 -83
- data/package/utils/validateDependencies.ts +1 -1
- data/package/webpack-types.d.ts +3 -3
- data/package/webpackDevServerConfig.ts +22 -10
- data/package-lock.json +2 -2
- data/package.json +36 -28
- data/scripts/type-check-no-emit.js +1 -1
- data/test/configExporter/configFile.test.js +392 -0
- data/test/configExporter/integration.test.js +275 -0
- data/test/helpers.js +1 -1
- data/test/package/configExporter.test.js +154 -0
- data/test/package/helpers.test.js +2 -2
- data/test/package/rules/sass-version-parsing.test.js +71 -0
- data/test/package/rules/sass.test.js +2 -4
- data/test/package/rules/sass1.test.js +1 -3
- data/test/package/rules/sass16.test.js +23 -0
- data/tools/README.md +15 -5
- data/tsconfig.eslint.json +2 -9
- data/yarn.lock +1894 -1492
- metadata +19 -3
- 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
|
-
/\.\.[\/\\]/,
|
18
|
-
/^\//,
|
19
|
-
/^[A-Za-z]:[\/\\]/,
|
20
|
-
/^\\\\/,
|
21
|
-
/~[\/\\]/,
|
22
|
-
/%2e%2e/i
|
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 (
|
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
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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
|
-
|
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(
|
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
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
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 ===
|
123
|
-
|
124
|
-
if (typeof port ===
|
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 ===
|
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
|
}
|
data/package/utils/typeGuards.ts
CHANGED
@@ -19,7 +19,8 @@ let cachedCacheTTL: number | null = null
|
|
19
19
|
*/
|
20
20
|
function isWatchMode(): boolean {
|
21
21
|
if (cachedIsWatchMode === null) {
|
22
|
-
cachedIsWatchMode =
|
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 ===
|
35
|
+
} else if (process.env.NODE_ENV === "production" && !isWatchMode()) {
|
35
36
|
cachedCacheTTL = Infinity
|
36
37
|
} else if (isWatchMode()) {
|
37
|
-
cachedCacheTTL = 5000
|
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
|
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 ===
|
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(
|
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 !==
|
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 &&
|
83
|
+
if (cached && Date.now() - cached.timestamp < getCacheTTL()) {
|
80
84
|
if (debugCache) {
|
81
|
-
console.log(
|
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
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
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] !==
|
105
|
+
if (typeof config[field] !== "string") {
|
100
106
|
// Cache negative result
|
101
|
-
validatedConfigs.set(obj as object, {
|
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 (
|
107
|
-
|
108
|
-
|
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
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
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] !==
|
143
|
+
if (typeof config[field] !== "boolean") {
|
127
144
|
// Cache negative result
|
128
|
-
validatedConfigs.set(obj as object, {
|
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, {
|
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(
|
145
|
-
|
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 (
|
187
|
+
if (
|
188
|
+
config.dev_server !== undefined &&
|
189
|
+
!isValidDevServerConfig(config.dev_server)
|
190
|
+
) {
|
160
191
|
// Cache negative result
|
161
|
-
validatedConfigs.set(obj as object, {
|
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 (
|
168
|
-
|
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, {
|
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 !==
|
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 (
|
199
|
-
|
200
|
-
|
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 !==
|
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 ===
|
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 (
|
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 (
|
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 !==
|
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] !==
|
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 !==
|
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
|
-
|
300
|
-
|
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] !==
|
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
|
-
|
312
|
-
|
313
|
-
|
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] !==
|
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 (
|
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
|
-
|
data/package/webpack-types.d.ts
CHANGED
@@ -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
|
2
|
+
import type { Configuration, RuleSetRule, RuleSetUseItem } from "webpack"
|
3
3
|
|
4
4
|
export interface ShakapackerWebpackConfig extends Configuration {
|
5
|
-
module?: Configuration[
|
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 =
|
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?:
|
17
|
-
|
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?:
|
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 = {
|
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 ===
|
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
|