shakapacker 9.0.0.beta.6 → 9.0.0.beta.7
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/workflows/generator.yml +6 -0
- data/.gitignore +0 -1
- data/.npmignore +55 -0
- data/Gemfile.lock +1 -1
- data/README.md +4 -0
- data/docs/transpiler-migration.md +191 -0
- data/docs/typescript-migration.md +378 -0
- data/lib/install/template.rb +54 -7
- data/lib/shakapacker/version.rb +1 -1
- data/package/.npmignore +4 -0
- data/package/config.ts +23 -10
- data/package/env.ts +15 -2
- data/package/environments/{development.js → development.ts} +30 -8
- data/package/environments/{production.js → production.ts} +18 -4
- data/package/environments/test.ts +53 -0
- data/package/environments/types.ts +90 -0
- data/package/types/README.md +87 -0
- data/package/types/index.ts +60 -0
- data/package/utils/errorCodes.ts +219 -0
- data/package/utils/errorHelpers.ts +68 -2
- data/package/utils/pathValidation.ts +139 -0
- data/package/utils/typeGuards.ts +161 -47
- data/package.json +5 -3
- data/scripts/remove-use-strict.js +45 -0
- data/test/package/transpiler-defaults.test.js +127 -0
- data/test/scripts/remove-use-strict.test.js +125 -0
- data/test/typescript/build.test.js +3 -2
- data/test/typescript/environments.test.js +107 -0
- data/test/typescript/pathValidation.test.js +142 -0
- data/test/typescript/securityValidation.test.js +182 -0
- metadata +25 -6
- data/package/environments/base.js +0 -103
- data/package/environments/test.js +0 -19
data/package/utils/typeGuards.ts
CHANGED
@@ -1,43 +1,111 @@
|
|
1
1
|
import { Config, DevServerConfig, YamlConfig } from "../types"
|
2
|
+
import { isPathTraversalSafe, validatePort } from "./pathValidation"
|
2
3
|
|
3
|
-
// Cache for validated configs
|
4
|
-
|
4
|
+
// Cache for validated configs with TTL
|
5
|
+
interface CacheEntry {
|
6
|
+
result: boolean
|
7
|
+
timestamp: number
|
8
|
+
configHash?: string
|
9
|
+
}
|
10
|
+
|
11
|
+
let validatedConfigs = new WeakMap<object, CacheEntry>()
|
12
|
+
|
13
|
+
// Cache computed values to avoid repeated checks
|
14
|
+
let cachedIsWatchMode: boolean | null = null
|
15
|
+
let cachedCacheTTL: number | null = null
|
16
|
+
|
17
|
+
/**
|
18
|
+
* Detect if running in watch mode (cached)
|
19
|
+
*/
|
20
|
+
function isWatchMode(): boolean {
|
21
|
+
if (cachedIsWatchMode === null) {
|
22
|
+
cachedIsWatchMode = process.argv.includes('--watch') || process.env.WEBPACK_WATCH === 'true'
|
23
|
+
}
|
24
|
+
return cachedIsWatchMode
|
25
|
+
}
|
26
|
+
|
27
|
+
/**
|
28
|
+
* Get cache TTL based on environment (cached)
|
29
|
+
*/
|
30
|
+
function getCacheTTL(): number {
|
31
|
+
if (cachedCacheTTL === null) {
|
32
|
+
if (process.env.SHAKAPACKER_CACHE_TTL) {
|
33
|
+
cachedCacheTTL = parseInt(process.env.SHAKAPACKER_CACHE_TTL, 10)
|
34
|
+
} else if (process.env.NODE_ENV === 'production' && !isWatchMode()) {
|
35
|
+
cachedCacheTTL = Infinity
|
36
|
+
} else if (isWatchMode()) {
|
37
|
+
cachedCacheTTL = 5000 // 5 seconds in watch mode
|
38
|
+
} else {
|
39
|
+
cachedCacheTTL = 60000 // 1 minute in dev
|
40
|
+
}
|
41
|
+
}
|
42
|
+
return cachedCacheTTL
|
43
|
+
}
|
5
44
|
|
6
45
|
// Only validate in development or when explicitly enabled
|
7
|
-
|
46
|
+
function shouldValidate(): boolean {
|
47
|
+
return process.env.NODE_ENV !== 'production' || process.env.SHAKAPACKER_STRICT_VALIDATION === 'true'
|
48
|
+
}
|
49
|
+
|
50
|
+
// Debug logging for cache operations
|
51
|
+
const debugCache = process.env.SHAKAPACKER_DEBUG_CACHE === 'true'
|
52
|
+
|
53
|
+
/**
|
54
|
+
* Clear the validation cache
|
55
|
+
* Useful for testing or when config files change
|
56
|
+
*/
|
57
|
+
export function clearValidationCache(): void {
|
58
|
+
// Reassign to a new WeakMap to clear all entries
|
59
|
+
validatedConfigs = new WeakMap<object, CacheEntry>()
|
60
|
+
if (debugCache) {
|
61
|
+
console.log('[SHAKAPACKER DEBUG] Validation cache cleared')
|
62
|
+
}
|
63
|
+
}
|
8
64
|
|
9
65
|
/**
|
10
66
|
* Type guard to validate Config object at runtime
|
11
67
|
* In production, caches results for performance unless SHAKAPACKER_STRICT_VALIDATION is set
|
68
|
+
*
|
69
|
+
* IMPORTANT: Path traversal security checks ALWAYS run regardless of environment or validation mode.
|
70
|
+
* This ensures application security is never compromised for performance.
|
12
71
|
*/
|
13
72
|
export function isValidConfig(obj: unknown): obj is Config {
|
14
73
|
if (typeof obj !== 'object' || obj === null) {
|
15
74
|
return false
|
16
75
|
}
|
17
76
|
|
18
|
-
//
|
19
|
-
|
20
|
-
|
77
|
+
// Check cache with TTL
|
78
|
+
const cached = validatedConfigs.get(obj as object)
|
79
|
+
if (cached && (Date.now() - cached.timestamp) < getCacheTTL()) {
|
80
|
+
if (debugCache) {
|
81
|
+
console.log(`[SHAKAPACKER DEBUG] Config validation cache hit (result: ${cached.result})`)
|
82
|
+
}
|
83
|
+
return cached.result
|
21
84
|
}
|
22
85
|
|
23
86
|
const config = obj as Record<string, unknown>
|
24
|
-
|
87
|
+
|
25
88
|
// Check required string fields
|
26
89
|
const requiredStringFields = [
|
27
90
|
'source_path',
|
28
|
-
'source_entry_path',
|
91
|
+
'source_entry_path',
|
29
92
|
'public_root_path',
|
30
93
|
'public_output_path',
|
31
94
|
'cache_path',
|
32
95
|
'javascript_transpiler'
|
33
96
|
]
|
34
|
-
|
97
|
+
|
35
98
|
for (const field of requiredStringFields) {
|
36
99
|
if (typeof config[field] !== 'string') {
|
37
|
-
// Cache negative result
|
38
|
-
|
39
|
-
|
40
|
-
|
100
|
+
// Cache negative result
|
101
|
+
validatedConfigs.set(obj as object, { result: false, timestamp: Date.now() })
|
102
|
+
return false
|
103
|
+
}
|
104
|
+
// SECURITY: Path traversal validation ALWAYS runs (not subject to shouldValidate)
|
105
|
+
// 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() })
|
41
109
|
return false
|
42
110
|
}
|
43
111
|
}
|
@@ -56,52 +124,58 @@ export function isValidConfig(obj: unknown): obj is Config {
|
|
56
124
|
|
57
125
|
for (const field of requiredBooleanFields) {
|
58
126
|
if (typeof config[field] !== 'boolean') {
|
59
|
-
// Cache negative result
|
60
|
-
|
61
|
-
validatedConfigs.set(obj as object, false)
|
62
|
-
}
|
127
|
+
// Cache negative result
|
128
|
+
validatedConfigs.set(obj as object, { result: false, timestamp: Date.now() })
|
63
129
|
return false
|
64
130
|
}
|
65
131
|
}
|
66
132
|
|
67
133
|
// Check arrays
|
68
134
|
if (!Array.isArray(config.additional_paths)) {
|
69
|
-
// Cache negative result
|
70
|
-
|
71
|
-
validatedConfigs.set(obj as object, false)
|
72
|
-
}
|
135
|
+
// Cache negative result
|
136
|
+
validatedConfigs.set(obj as object, { result: false, timestamp: Date.now() })
|
73
137
|
return false
|
74
138
|
}
|
75
|
-
|
76
|
-
//
|
77
|
-
|
78
|
-
|
79
|
-
if (!
|
80
|
-
|
139
|
+
|
140
|
+
// SECURITY: Path traversal validation for additional_paths ALWAYS runs (not subject to shouldValidate)
|
141
|
+
// This critical security check ensures user-provided paths cannot escape the project directory
|
142
|
+
for (const additionalPath of config.additional_paths as string[]) {
|
143
|
+
if (!isPathTraversalSafe(additionalPath)) {
|
144
|
+
console.warn(`[SHAKAPACKER SECURITY] Invalid additional_path: ${additionalPath}`)
|
145
|
+
validatedConfigs.set(obj as object, { result: false, timestamp: Date.now() })
|
146
|
+
return false
|
81
147
|
}
|
148
|
+
}
|
149
|
+
|
150
|
+
// In production, skip deep validation of optional fields unless explicitly enabled
|
151
|
+
// Security checks above still run regardless of this flag
|
152
|
+
if (!shouldValidate()) {
|
153
|
+
// Cache positive result - basic structure and security validated
|
154
|
+
validatedConfigs.set(obj as object, { result: true, timestamp: Date.now() })
|
155
|
+
return true
|
156
|
+
}
|
157
|
+
|
158
|
+
// Deep validation of optional fields (only in development or with SHAKAPACKER_STRICT_VALIDATION=true)
|
159
|
+
if (config.dev_server !== undefined && !isValidDevServerConfig(config.dev_server)) {
|
160
|
+
// Cache negative result
|
161
|
+
validatedConfigs.set(obj as object, { result: false, timestamp: Date.now() })
|
82
162
|
return false
|
83
163
|
}
|
84
|
-
|
164
|
+
|
85
165
|
if (config.integrity !== undefined) {
|
86
166
|
const integrity = config.integrity as Record<string, unknown>
|
87
|
-
if (typeof integrity.enabled !== 'boolean' ||
|
167
|
+
if (typeof integrity.enabled !== 'boolean' ||
|
88
168
|
typeof integrity.cross_origin !== 'string') {
|
89
|
-
// Cache negative result
|
90
|
-
|
91
|
-
validatedConfigs.set(obj as object, false)
|
92
|
-
}
|
169
|
+
// Cache negative result
|
170
|
+
validatedConfigs.set(obj as object, { result: false, timestamp: Date.now() })
|
93
171
|
return false
|
94
172
|
}
|
95
173
|
}
|
96
174
|
|
97
|
-
|
98
|
-
|
99
|
-
// Cache result in production
|
100
|
-
if (!shouldValidate) {
|
101
|
-
validatedConfigs.set(obj as object, result)
|
102
|
-
}
|
175
|
+
// Cache positive result
|
176
|
+
validatedConfigs.set(obj as object, { result: true, timestamp: Date.now() })
|
103
177
|
|
104
|
-
return
|
178
|
+
return true
|
105
179
|
}
|
106
180
|
|
107
181
|
/**
|
@@ -114,7 +188,7 @@ export function isValidDevServerConfig(obj: unknown): obj is DevServerConfig {
|
|
114
188
|
}
|
115
189
|
|
116
190
|
// In production, skip deep validation unless explicitly enabled
|
117
|
-
if (!shouldValidate) {
|
191
|
+
if (!shouldValidate()) {
|
118
192
|
return true
|
119
193
|
}
|
120
194
|
|
@@ -127,16 +201,56 @@ export function isValidDevServerConfig(obj: unknown): obj is DevServerConfig {
|
|
127
201
|
return false
|
128
202
|
}
|
129
203
|
|
130
|
-
if (config.port !== undefined &&
|
131
|
-
typeof config.port !== 'number' &&
|
132
|
-
typeof config.port !== 'string' &&
|
133
|
-
config.port !== 'auto') {
|
204
|
+
if (config.port !== undefined && !validatePort(config.port)) {
|
134
205
|
return false
|
135
206
|
}
|
136
207
|
|
137
208
|
return true
|
138
209
|
}
|
139
210
|
|
211
|
+
/**
|
212
|
+
* Type guard to validate Rspack plugin instance
|
213
|
+
* Checks if an object looks like a valid Rspack plugin
|
214
|
+
*/
|
215
|
+
export function isValidRspackPlugin(obj: unknown): boolean {
|
216
|
+
if (typeof obj !== 'object' || obj === null) {
|
217
|
+
return false
|
218
|
+
}
|
219
|
+
|
220
|
+
const plugin = obj as Record<string, unknown>
|
221
|
+
|
222
|
+
// Check for common plugin patterns
|
223
|
+
// Most rspack plugins should have an apply method
|
224
|
+
if (typeof plugin.apply === 'function') {
|
225
|
+
return true
|
226
|
+
}
|
227
|
+
|
228
|
+
// Check for constructor name pattern (e.g., HtmlRspackPlugin)
|
229
|
+
const constructorName = plugin.constructor?.name || ''
|
230
|
+
if (constructorName.includes('Plugin') || constructorName.includes('Rspack')) {
|
231
|
+
return true
|
232
|
+
}
|
233
|
+
|
234
|
+
// Check for common plugin properties
|
235
|
+
if ('name' in plugin && typeof plugin.name === 'string') {
|
236
|
+
return true
|
237
|
+
}
|
238
|
+
|
239
|
+
return false
|
240
|
+
}
|
241
|
+
|
242
|
+
/**
|
243
|
+
* Type guard to validate array of Rspack plugins
|
244
|
+
* Ensures all items in the array are valid plugin instances
|
245
|
+
*/
|
246
|
+
export function isValidRspackPluginArray(arr: unknown): boolean {
|
247
|
+
if (!Array.isArray(arr)) {
|
248
|
+
return false
|
249
|
+
}
|
250
|
+
|
251
|
+
return arr.every(item => isValidRspackPlugin(item))
|
252
|
+
}
|
253
|
+
|
140
254
|
/**
|
141
255
|
* Type guard to validate YamlConfig structure
|
142
256
|
* In production, performs minimal validation for performance
|
@@ -147,7 +261,7 @@ export function isValidYamlConfig(obj: unknown): obj is YamlConfig {
|
|
147
261
|
}
|
148
262
|
|
149
263
|
// In production, skip deep validation unless explicitly enabled
|
150
|
-
if (!shouldValidate) {
|
264
|
+
if (!shouldValidate()) {
|
151
265
|
return true
|
152
266
|
}
|
153
267
|
|
@@ -174,7 +288,7 @@ export function isPartialConfig(obj: unknown): obj is Partial<Config> {
|
|
174
288
|
}
|
175
289
|
|
176
290
|
// In production, skip deep validation unless explicitly enabled
|
177
|
-
if (!shouldValidate) {
|
291
|
+
if (!shouldValidate()) {
|
178
292
|
return true
|
179
293
|
}
|
180
294
|
|
data/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "shakapacker",
|
3
|
-
"version": "9.0.0-beta.
|
3
|
+
"version": "9.0.0-beta.7",
|
4
4
|
"description": "Use webpack to manage app-like JavaScript modules in Rails",
|
5
5
|
"homepage": "https://github.com/shakacode/shakapacker",
|
6
6
|
"bugs": {
|
@@ -16,6 +16,7 @@
|
|
16
16
|
"types": "package/index.d.ts",
|
17
17
|
"exports": {
|
18
18
|
".": "./package/index.js",
|
19
|
+
"./types": "./package/types/index.js",
|
19
20
|
"./webpack": "./package/webpack/index.js",
|
20
21
|
"./rspack": "./package/rspack/index.js",
|
21
22
|
"./swc": "./package/swc/index.js",
|
@@ -30,11 +31,12 @@
|
|
30
31
|
],
|
31
32
|
"scripts": {
|
32
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",
|
33
|
-
"build": "tsc",
|
34
|
+
"build": "tsc && node scripts/remove-use-strict.js && yarn prettier --write 'package/**/*.js'",
|
34
35
|
"build:types": "tsc",
|
35
36
|
"lint": "eslint .",
|
36
37
|
"test": "jest",
|
37
|
-
"type-check": "tsc --noEmit"
|
38
|
+
"type-check": "tsc --noEmit",
|
39
|
+
"prepublishOnly": "yarn build && yarn type-check"
|
38
40
|
},
|
39
41
|
"dependencies": {
|
40
42
|
"js-yaml": "^4.1.0",
|
@@ -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,127 @@
|
|
1
|
+
// Test transpiler defaults for backward compatibility
|
2
|
+
|
3
|
+
describe("JavaScript Transpiler Defaults", () => {
|
4
|
+
let originalEnv
|
5
|
+
|
6
|
+
beforeEach(() => {
|
7
|
+
// Save original environment
|
8
|
+
originalEnv = { ...process.env }
|
9
|
+
|
10
|
+
// Clear module cache to test different configurations
|
11
|
+
jest.resetModules()
|
12
|
+
})
|
13
|
+
|
14
|
+
afterEach(() => {
|
15
|
+
// Restore original environment
|
16
|
+
process.env = originalEnv
|
17
|
+
})
|
18
|
+
|
19
|
+
describe("webpack bundler", () => {
|
20
|
+
it("respects config file transpiler setting (swc in this project)", () => {
|
21
|
+
// Set up webpack environment
|
22
|
+
delete process.env.SHAKAPACKER_ASSETS_BUNDLER
|
23
|
+
delete process.env.SHAKAPACKER_JAVASCRIPT_TRANSPILER
|
24
|
+
|
25
|
+
// Load config fresh
|
26
|
+
const config = require("../../package/config")
|
27
|
+
|
28
|
+
// This project's shakapacker.yml has javascript_transpiler: 'swc'
|
29
|
+
// which overrides the default babel for webpack
|
30
|
+
expect(config.javascript_transpiler).toBe("swc")
|
31
|
+
expect(config.webpack_loader).toBe("swc") // Legacy property
|
32
|
+
})
|
33
|
+
|
34
|
+
it("respects explicit javascript_transpiler setting", () => {
|
35
|
+
delete process.env.SHAKAPACKER_ASSETS_BUNDLER
|
36
|
+
process.env.SHAKAPACKER_JAVASCRIPT_TRANSPILER = "swc"
|
37
|
+
|
38
|
+
jest.resetModules()
|
39
|
+
const config = require("../../package/config")
|
40
|
+
|
41
|
+
expect(config.javascript_transpiler).toBe("swc")
|
42
|
+
})
|
43
|
+
})
|
44
|
+
|
45
|
+
describe("rspack bundler", () => {
|
46
|
+
it("uses swc as default transpiler for modern performance", () => {
|
47
|
+
process.env.SHAKAPACKER_ASSETS_BUNDLER = "rspack"
|
48
|
+
delete process.env.SHAKAPACKER_JAVASCRIPT_TRANSPILER
|
49
|
+
|
50
|
+
jest.resetModules()
|
51
|
+
const config = require("../../package/config")
|
52
|
+
|
53
|
+
expect(config.javascript_transpiler).toBe("swc")
|
54
|
+
expect(config.webpack_loader).toBe("swc") // Legacy property
|
55
|
+
})
|
56
|
+
|
57
|
+
it("allows environment override to babel if needed", () => {
|
58
|
+
process.env.SHAKAPACKER_ASSETS_BUNDLER = "rspack"
|
59
|
+
process.env.SHAKAPACKER_JAVASCRIPT_TRANSPILER = "babel"
|
60
|
+
|
61
|
+
jest.resetModules()
|
62
|
+
const config = require("../../package/config")
|
63
|
+
|
64
|
+
expect(config.javascript_transpiler).toBe("babel")
|
65
|
+
})
|
66
|
+
})
|
67
|
+
|
68
|
+
describe("backward compatibility", () => {
|
69
|
+
it("supports deprecated webpack_loader property", () => {
|
70
|
+
delete process.env.SHAKAPACKER_ASSETS_BUNDLER
|
71
|
+
delete process.env.SHAKAPACKER_JAVASCRIPT_TRANSPILER
|
72
|
+
|
73
|
+
jest.resetModules()
|
74
|
+
const config = require("../../package/config")
|
75
|
+
|
76
|
+
// Both properties should exist and match
|
77
|
+
expect(config.webpack_loader).toBeDefined()
|
78
|
+
expect(config.javascript_transpiler).toBeDefined()
|
79
|
+
expect(config.webpack_loader).toBe(config.javascript_transpiler)
|
80
|
+
})
|
81
|
+
|
82
|
+
it("warns when using deprecated webpack_loader in config", () => {
|
83
|
+
const consoleSpy = jest.spyOn(console, "warn").mockImplementation()
|
84
|
+
|
85
|
+
// Simulate config with webpack_loader set
|
86
|
+
// This would normally come from YAML config
|
87
|
+
delete process.env.SHAKAPACKER_JAVASCRIPT_TRANSPILER
|
88
|
+
|
89
|
+
jest.resetModules()
|
90
|
+
require("../../package/config")
|
91
|
+
|
92
|
+
// The warning is shown during config loading if webpack_loader is detected
|
93
|
+
// Since we can't easily mock the YAML config here, we check the mechanism exists
|
94
|
+
expect(consoleSpy.mock.calls.length >= 0).toBe(true)
|
95
|
+
|
96
|
+
consoleSpy.mockRestore()
|
97
|
+
})
|
98
|
+
})
|
99
|
+
|
100
|
+
describe("environment variable precedence", () => {
|
101
|
+
it("environment variable overrides default", () => {
|
102
|
+
process.env.SHAKAPACKER_JAVASCRIPT_TRANSPILER = "esbuild"
|
103
|
+
|
104
|
+
jest.resetModules()
|
105
|
+
const config = require("../../package/config")
|
106
|
+
|
107
|
+
expect(config.javascript_transpiler).toBe("esbuild")
|
108
|
+
})
|
109
|
+
|
110
|
+
it("bundler change doesn't affect transpiler when explicitly set in config", () => {
|
111
|
+
// With this project's config, transpiler is always 'swc'
|
112
|
+
// regardless of bundler because it's explicitly set in shakapacker.yml
|
113
|
+
|
114
|
+
// Test webpack
|
115
|
+
delete process.env.SHAKAPACKER_ASSETS_BUNDLER
|
116
|
+
jest.resetModules()
|
117
|
+
let config = require("../../package/config")
|
118
|
+
expect(config.javascript_transpiler).toBe("swc") // Config file overrides default
|
119
|
+
|
120
|
+
// Test rspack - should still be swc
|
121
|
+
process.env.SHAKAPACKER_ASSETS_BUNDLER = "rspack"
|
122
|
+
jest.resetModules()
|
123
|
+
config = require("../../package/config")
|
124
|
+
expect(config.javascript_transpiler).toBe("swc")
|
125
|
+
})
|
126
|
+
})
|
127
|
+
})
|
@@ -0,0 +1,125 @@
|
|
1
|
+
const fs = require("fs")
|
2
|
+
const path = require("path")
|
3
|
+
const { execSync } = require("child_process")
|
4
|
+
const os = require("os")
|
5
|
+
|
6
|
+
describe("remove-use-strict script", () => {
|
7
|
+
let tempDir
|
8
|
+
|
9
|
+
beforeEach(() => {
|
10
|
+
// Create a temporary directory for test files
|
11
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "remove-use-strict-test-"))
|
12
|
+
})
|
13
|
+
|
14
|
+
afterEach(() => {
|
15
|
+
// Clean up the temporary directory
|
16
|
+
fs.rmSync(tempDir, { recursive: true, force: true })
|
17
|
+
})
|
18
|
+
|
19
|
+
function createTestFile(filename, content) {
|
20
|
+
const filePath = path.join(tempDir, filename)
|
21
|
+
fs.writeFileSync(filePath, content, "utf8")
|
22
|
+
return filePath
|
23
|
+
}
|
24
|
+
|
25
|
+
function runScript(directory) {
|
26
|
+
// Run the script with a custom directory
|
27
|
+
const scriptContent = fs.readFileSync(
|
28
|
+
"scripts/remove-use-strict.js",
|
29
|
+
"utf8"
|
30
|
+
)
|
31
|
+
// Replace 'package' with our test directory
|
32
|
+
const modifiedScript = scriptContent.replace(
|
33
|
+
'findJsFiles("package")',
|
34
|
+
`findJsFiles("${directory}")`
|
35
|
+
)
|
36
|
+
const tempScript = path.join(tempDir, "test-script.js")
|
37
|
+
fs.writeFileSync(tempScript, modifiedScript, "utf8")
|
38
|
+
execSync(`node "${tempScript}"`, { stdio: "pipe" })
|
39
|
+
}
|
40
|
+
|
41
|
+
it("removes standard double-quoted use strict with semicolon", () => {
|
42
|
+
const filePath = createTestFile("test1.js", '"use strict";\nconst x = 1;')
|
43
|
+
runScript(tempDir)
|
44
|
+
const result = fs.readFileSync(filePath, "utf8")
|
45
|
+
expect(result).toBe("const x = 1;\n")
|
46
|
+
})
|
47
|
+
|
48
|
+
it("removes single-quoted use strict without semicolon", () => {
|
49
|
+
const filePath = createTestFile("test2.js", "'use strict'\nconst x = 1;")
|
50
|
+
runScript(tempDir)
|
51
|
+
const result = fs.readFileSync(filePath, "utf8")
|
52
|
+
expect(result).toBe("const x = 1;\n")
|
53
|
+
})
|
54
|
+
|
55
|
+
it("removes use strict with leading whitespace", () => {
|
56
|
+
const filePath = createTestFile(
|
57
|
+
"test3.js",
|
58
|
+
' \t"use strict";\nconst x = 1;'
|
59
|
+
)
|
60
|
+
runScript(tempDir)
|
61
|
+
const result = fs.readFileSync(filePath, "utf8")
|
62
|
+
expect(result).toBe("const x = 1;\n")
|
63
|
+
})
|
64
|
+
|
65
|
+
it("removes use strict with trailing whitespace and multiple newlines", () => {
|
66
|
+
const filePath = createTestFile(
|
67
|
+
"test4.js",
|
68
|
+
'"use strict"; \n\n\nconst x = 1;'
|
69
|
+
)
|
70
|
+
runScript(tempDir)
|
71
|
+
const result = fs.readFileSync(filePath, "utf8")
|
72
|
+
expect(result).toBe("const x = 1;\n")
|
73
|
+
})
|
74
|
+
|
75
|
+
it("removes use strict with unicode quotes", () => {
|
76
|
+
const filePath = createTestFile(
|
77
|
+
"test5.js",
|
78
|
+
"\u201Cuse strict\u201D;\nconst x = 1;"
|
79
|
+
)
|
80
|
+
runScript(tempDir)
|
81
|
+
const result = fs.readFileSync(filePath, "utf8")
|
82
|
+
expect(result).toBe("const x = 1;\n")
|
83
|
+
})
|
84
|
+
|
85
|
+
it("ensures trailing newline when missing", () => {
|
86
|
+
const filePath = createTestFile("test6.js", '"use strict";\nconst x = 1')
|
87
|
+
runScript(tempDir)
|
88
|
+
const result = fs.readFileSync(filePath, "utf8")
|
89
|
+
expect(result).toBe("const x = 1\n")
|
90
|
+
expect(result.endsWith("\n")).toBe(true)
|
91
|
+
})
|
92
|
+
|
93
|
+
it("preserves content that doesn't start with use strict", () => {
|
94
|
+
const filePath = createTestFile(
|
95
|
+
"test7.js",
|
96
|
+
'const y = 2;\n"use strict";\nconst x = 1;'
|
97
|
+
)
|
98
|
+
runScript(tempDir)
|
99
|
+
const result = fs.readFileSync(filePath, "utf8")
|
100
|
+
expect(result).toBe('const y = 2;\n"use strict";\nconst x = 1;\n')
|
101
|
+
})
|
102
|
+
|
103
|
+
it("handles files already ending with newline", () => {
|
104
|
+
const filePath = createTestFile("test8.js", '"use strict";\nconst x = 1;\n')
|
105
|
+
runScript(tempDir)
|
106
|
+
const result = fs.readFileSync(filePath, "utf8")
|
107
|
+
expect(result).toBe("const x = 1;\n")
|
108
|
+
// Should have exactly one trailing newline, not double
|
109
|
+
expect(result.match(/\n$/g)).toHaveLength(1)
|
110
|
+
})
|
111
|
+
|
112
|
+
it("handles Windows-style line endings", () => {
|
113
|
+
const filePath = createTestFile("test9.js", '"use strict";\r\nconst x = 1;')
|
114
|
+
runScript(tempDir)
|
115
|
+
const result = fs.readFileSync(filePath, "utf8")
|
116
|
+
expect(result).toBe("const x = 1;\n")
|
117
|
+
})
|
118
|
+
|
119
|
+
it("handles use strict with extra spaces", () => {
|
120
|
+
const filePath = createTestFile("test10.js", '"use strict";\nconst x = 1;')
|
121
|
+
runScript(tempDir)
|
122
|
+
const result = fs.readFileSync(filePath, "utf8")
|
123
|
+
expect(result).toBe("const x = 1;\n")
|
124
|
+
})
|
125
|
+
})
|
@@ -24,9 +24,10 @@ describe("typescript build", () => {
|
|
24
24
|
expect(existsSync(tsPath)).toBe(true)
|
25
25
|
expect(existsSync(jsPath)).toBe(true)
|
26
26
|
|
27
|
-
// Verify JS file
|
27
|
+
// Verify JS file contains CommonJS exports (has been compiled)
|
28
28
|
const jsContent = readFileSync(jsPath, "utf8")
|
29
|
-
expect(jsContent).toContain("
|
29
|
+
expect(jsContent).toContain("require(")
|
30
|
+
expect(jsContent).toContain("module.exports")
|
30
31
|
})
|
31
32
|
})
|
32
33
|
|