shakapacker 9.3.0.beta.7 → 9.3.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/CHANGELOG.md +46 -109
- data/Gemfile.lock +1 -1
- data/README.md +53 -2
- data/docs/configuration.md +28 -0
- data/docs/rspack_migration_guide.md +238 -2
- data/docs/troubleshooting.md +21 -21
- data/eslint.config.fast.js +8 -0
- data/eslint.config.js +47 -10
- data/knip.ts +8 -1
- data/lib/install/config/shakapacker.yml +6 -6
- data/lib/shakapacker/configuration.rb +227 -4
- data/lib/shakapacker/dev_server.rb +88 -1
- data/lib/shakapacker/doctor.rb +4 -4
- data/lib/shakapacker/instance.rb +85 -1
- data/lib/shakapacker/manifest.rb +85 -11
- data/lib/shakapacker/version.rb +1 -1
- data/lib/shakapacker.rb +143 -3
- data/lib/tasks/shakapacker/doctor.rake +1 -1
- data/lib/tasks/shakapacker/export_bundler_config.rake +4 -4
- data/package/config.ts +0 -1
- data/package/configExporter/buildValidator.ts +53 -29
- data/package/configExporter/cli.ts +81 -56
- data/package/configExporter/configFile.ts +33 -26
- data/package/configExporter/types.ts +64 -0
- data/package/configExporter/yamlSerializer.ts +118 -43
- data/package/dev_server.ts +2 -1
- data/package/env.ts +1 -1
- data/package/environments/base.ts +4 -4
- data/package/environments/development.ts +7 -6
- data/package/environments/production.ts +6 -7
- data/package/environments/test.ts +2 -1
- data/package/index.ts +28 -4
- data/package/loaders.d.ts +2 -2
- data/package/optimization/webpack.ts +29 -31
- data/package/rspack/index.ts +2 -1
- data/package/rules/file.ts +1 -0
- data/package/rules/jscommon.ts +1 -0
- data/package/utils/helpers.ts +0 -1
- data/package/utils/pathValidation.ts +68 -7
- data/package/utils/requireOrError.ts +10 -2
- data/package/utils/typeGuards.ts +43 -46
- data/package/webpack-types.d.ts +2 -2
- data/package/webpackDevServerConfig.ts +1 -0
- data/package.json +2 -3
- data/test/package/configExporter/cli.test.js +440 -0
- data/test/package/configExporter/types.test.js +163 -0
- data/test/package/configExporter.test.js +264 -0
- data/test/package/yamlSerializer.test.js +204 -0
- data/test/typescript/pathValidation.test.js +44 -0
- data/test/typescript/requireOrError.test.js +49 -0
- data/yarn.lock +0 -32
- metadata +11 -5
- data/.eslintrc.fast.js +0 -40
- data/.eslintrc.js +0 -84
|
@@ -105,6 +105,223 @@ describe("configExporter", () => {
|
|
|
105
105
|
})
|
|
106
106
|
})
|
|
107
107
|
|
|
108
|
+
describe("yamlSerializer", () => {
|
|
109
|
+
test("serializes object keys in alphabetical order", () => {
|
|
110
|
+
const {
|
|
111
|
+
YamlSerializer
|
|
112
|
+
} = require("../../package/configExporter/yamlSerializer")
|
|
113
|
+
const serializer = new YamlSerializer({
|
|
114
|
+
annotate: false,
|
|
115
|
+
appRoot: "/test/app"
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
// Create an object with keys intentionally out of alphabetical order
|
|
119
|
+
const config = {
|
|
120
|
+
mode: "production",
|
|
121
|
+
entry: "./src/index.js",
|
|
122
|
+
optimization: {
|
|
123
|
+
minimize: true
|
|
124
|
+
},
|
|
125
|
+
output: {
|
|
126
|
+
path: "/dist",
|
|
127
|
+
filename: "bundle.js"
|
|
128
|
+
},
|
|
129
|
+
devtool: "source-map"
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const metadata = {
|
|
133
|
+
exportedAt: "2025-10-28",
|
|
134
|
+
environment: "production",
|
|
135
|
+
bundler: "webpack",
|
|
136
|
+
configType: "client",
|
|
137
|
+
configCount: 1
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const result = serializer.serialize(config, metadata)
|
|
141
|
+
|
|
142
|
+
// Extract just the config part (skip the header)
|
|
143
|
+
const lines = result.split("\n")
|
|
144
|
+
const keyMatches = lines
|
|
145
|
+
.map((line) => line.match(/^(\w+):/))
|
|
146
|
+
.filter(Boolean)
|
|
147
|
+
.map((match) => match[1])
|
|
148
|
+
|
|
149
|
+
// Expected order: devtool, entry, mode, optimization, output
|
|
150
|
+
expect(keyMatches).toStrictEqual([
|
|
151
|
+
"devtool",
|
|
152
|
+
"entry",
|
|
153
|
+
"mode",
|
|
154
|
+
"optimization",
|
|
155
|
+
"output"
|
|
156
|
+
])
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
test("serializes nested object keys in alphabetical order", () => {
|
|
160
|
+
const {
|
|
161
|
+
YamlSerializer
|
|
162
|
+
} = require("../../package/configExporter/yamlSerializer")
|
|
163
|
+
const serializer = new YamlSerializer({
|
|
164
|
+
annotate: false,
|
|
165
|
+
appRoot: "/test/app"
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
const config = {
|
|
169
|
+
output: {
|
|
170
|
+
path: "/dist",
|
|
171
|
+
filename: "bundle.js",
|
|
172
|
+
clean: true
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const metadata = {
|
|
177
|
+
exportedAt: "2025-10-28",
|
|
178
|
+
environment: "production",
|
|
179
|
+
bundler: "webpack",
|
|
180
|
+
configType: "client",
|
|
181
|
+
configCount: 1
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const result = serializer.serialize(config, metadata)
|
|
185
|
+
|
|
186
|
+
// Extract nested keys from the output section
|
|
187
|
+
const lines = result.split("\n")
|
|
188
|
+
const outputKeys = lines
|
|
189
|
+
.map((line) => line.match(/^ {2}(\w+):/))
|
|
190
|
+
.filter(Boolean)
|
|
191
|
+
.map((match) => match[1])
|
|
192
|
+
|
|
193
|
+
// Expected order: clean, filename, path
|
|
194
|
+
expect(outputKeys).toStrictEqual(["clean", "filename", "path"])
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
test("quotes strings containing square brackets", () => {
|
|
198
|
+
const {
|
|
199
|
+
YamlSerializer
|
|
200
|
+
} = require("../../package/configExporter/yamlSerializer")
|
|
201
|
+
const yaml = require("js-yaml")
|
|
202
|
+
|
|
203
|
+
const serializer = new YamlSerializer({
|
|
204
|
+
annotate: false,
|
|
205
|
+
appRoot: process.cwd()
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
const testConfig = {
|
|
209
|
+
options: {
|
|
210
|
+
modules: {
|
|
211
|
+
localIdentName: "[name]-[local]__[contenthash]"
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const metadata = {
|
|
217
|
+
exportedAt: new Date().toISOString(),
|
|
218
|
+
environment: "test",
|
|
219
|
+
bundler: "webpack",
|
|
220
|
+
configType: "test",
|
|
221
|
+
configCount: 1
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const yamlOutput = serializer.serialize(testConfig, metadata)
|
|
225
|
+
|
|
226
|
+
// Verify YAML can be parsed without errors
|
|
227
|
+
expect(() => yaml.load(yamlOutput)).not.toThrow()
|
|
228
|
+
|
|
229
|
+
// Verify the parsed value matches the original
|
|
230
|
+
const parsed = yaml.load(yamlOutput)
|
|
231
|
+
expect(parsed.options.modules.localIdentName).toBe(
|
|
232
|
+
"[name]-[local]__[contenthash]"
|
|
233
|
+
)
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
test("quotes RegExp strings containing special characters", () => {
|
|
237
|
+
const {
|
|
238
|
+
YamlSerializer
|
|
239
|
+
} = require("../../package/configExporter/yamlSerializer")
|
|
240
|
+
const yaml = require("js-yaml")
|
|
241
|
+
|
|
242
|
+
const serializer = new YamlSerializer({
|
|
243
|
+
annotate: false,
|
|
244
|
+
appRoot: process.cwd()
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
const testConfig = {
|
|
248
|
+
options: {
|
|
249
|
+
modules: {
|
|
250
|
+
localIdentRegExp: /([^/-]+|[^/]+)(?:-styles)?.module.scss$/,
|
|
251
|
+
localIdentName: "[name]-[local]__[contenthash]"
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const metadata = {
|
|
257
|
+
exportedAt: new Date().toISOString(),
|
|
258
|
+
environment: "test",
|
|
259
|
+
bundler: "webpack",
|
|
260
|
+
configType: "test",
|
|
261
|
+
configCount: 1
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const yamlOutput = serializer.serialize(testConfig, metadata)
|
|
265
|
+
|
|
266
|
+
// Verify YAML can be parsed without errors
|
|
267
|
+
expect(() => yaml.load(yamlOutput)).not.toThrow()
|
|
268
|
+
|
|
269
|
+
// Verify the parsed values match the originals
|
|
270
|
+
const parsed = yaml.load(yamlOutput)
|
|
271
|
+
expect(parsed.options.modules.localIdentName).toBe(
|
|
272
|
+
"[name]-[local]__[contenthash]"
|
|
273
|
+
)
|
|
274
|
+
// RegExp becomes a string in YAML
|
|
275
|
+
expect(parsed.options.modules.localIdentRegExp).toBe(
|
|
276
|
+
"([^/-]+|[^/]+)(?:-styles)?.module.scss$"
|
|
277
|
+
)
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
test("quotes strings with YAML special characters", () => {
|
|
281
|
+
const {
|
|
282
|
+
YamlSerializer
|
|
283
|
+
} = require("../../package/configExporter/yamlSerializer")
|
|
284
|
+
const yaml = require("js-yaml")
|
|
285
|
+
|
|
286
|
+
const serializer = new YamlSerializer({
|
|
287
|
+
annotate: false,
|
|
288
|
+
appRoot: process.cwd()
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
// Test various YAML special characters
|
|
292
|
+
const testConfig = {
|
|
293
|
+
curlyBraces: "{value}",
|
|
294
|
+
asterisk: "*value*",
|
|
295
|
+
ampersand: "&value",
|
|
296
|
+
exclamation: "!important",
|
|
297
|
+
atSign: "@import",
|
|
298
|
+
backtick: "`value`"
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const metadata = {
|
|
302
|
+
exportedAt: new Date().toISOString(),
|
|
303
|
+
environment: "test",
|
|
304
|
+
bundler: "webpack",
|
|
305
|
+
configType: "test",
|
|
306
|
+
configCount: 1
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const yamlOutput = serializer.serialize(testConfig, metadata)
|
|
310
|
+
|
|
311
|
+
// Verify YAML can be parsed without errors
|
|
312
|
+
expect(() => yaml.load(yamlOutput)).not.toThrow()
|
|
313
|
+
|
|
314
|
+
// Verify all special characters are preserved
|
|
315
|
+
const parsed = yaml.load(yamlOutput)
|
|
316
|
+
expect(parsed.curlyBraces).toBe("{value}")
|
|
317
|
+
expect(parsed.asterisk).toBe("*value*")
|
|
318
|
+
expect(parsed.ampersand).toBe("&value")
|
|
319
|
+
expect(parsed.exclamation).toBe("!important")
|
|
320
|
+
expect(parsed.atSign).toBe("@import")
|
|
321
|
+
expect(parsed.backtick).toBe("`value`")
|
|
322
|
+
})
|
|
323
|
+
})
|
|
324
|
+
|
|
108
325
|
describe("environment variable preservation in runDoctorMode", () => {
|
|
109
326
|
let originalEnv
|
|
110
327
|
|
|
@@ -192,4 +409,51 @@ describe("configExporter", () => {
|
|
|
192
409
|
expect(process.env.SERVER_BUNDLE_ONLY).toBeUndefined()
|
|
193
410
|
})
|
|
194
411
|
})
|
|
412
|
+
|
|
413
|
+
describe("argument validation", () => {
|
|
414
|
+
let mockExit
|
|
415
|
+
|
|
416
|
+
beforeEach(() => {
|
|
417
|
+
// Mock process.exit to prevent yargs from killing the test process
|
|
418
|
+
mockExit = jest.spyOn(process, "exit").mockImplementation(() => {
|
|
419
|
+
throw new Error("process.exit called")
|
|
420
|
+
})
|
|
421
|
+
})
|
|
422
|
+
|
|
423
|
+
afterEach(() => {
|
|
424
|
+
mockExit.mockRestore()
|
|
425
|
+
})
|
|
426
|
+
|
|
427
|
+
test("rejects --all-builds with --output", () => {
|
|
428
|
+
const { parseArguments } = require("../../package/configExporter/cli")
|
|
429
|
+
|
|
430
|
+
expect(() => {
|
|
431
|
+
parseArguments(["--all-builds", "--output=config.yml"])
|
|
432
|
+
}).toThrow("process.exit called")
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
test("rejects --all-builds with --stdout", () => {
|
|
436
|
+
const { parseArguments } = require("../../package/configExporter/cli")
|
|
437
|
+
|
|
438
|
+
expect(() => {
|
|
439
|
+
parseArguments(["--all-builds", "--stdout"])
|
|
440
|
+
}).toThrow("process.exit called")
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
test("rejects --stdout with --output", () => {
|
|
444
|
+
const { parseArguments } = require("../../package/configExporter/cli")
|
|
445
|
+
|
|
446
|
+
expect(() => {
|
|
447
|
+
parseArguments(["--stdout", "--output=config.yml"])
|
|
448
|
+
}).toThrow("process.exit called")
|
|
449
|
+
})
|
|
450
|
+
|
|
451
|
+
test("allows --all-builds with --save-dir", () => {
|
|
452
|
+
const { parseArguments } = require("../../package/configExporter/cli")
|
|
453
|
+
|
|
454
|
+
expect(() => {
|
|
455
|
+
parseArguments(["--all-builds", "--save-dir=./configs"])
|
|
456
|
+
}).not.toThrow()
|
|
457
|
+
})
|
|
458
|
+
})
|
|
195
459
|
})
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
const {
|
|
2
|
+
YamlSerializer
|
|
3
|
+
} = require("../../package/configExporter/yamlSerializer")
|
|
4
|
+
|
|
5
|
+
describe("YamlSerializer", () => {
|
|
6
|
+
let serializer
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
serializer = new YamlSerializer({
|
|
10
|
+
annotate: false,
|
|
11
|
+
appRoot: "/test/app"
|
|
12
|
+
})
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
describe("serialize", () => {
|
|
16
|
+
test("includes metadata header in serialized output", () => {
|
|
17
|
+
const config = { mode: "development" }
|
|
18
|
+
const metadata = {
|
|
19
|
+
exportedAt: "2025-01-15T12:00:00Z",
|
|
20
|
+
environment: "development",
|
|
21
|
+
bundler: "webpack",
|
|
22
|
+
configType: "client",
|
|
23
|
+
configCount: 1
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const result = serializer.serialize(config, metadata)
|
|
27
|
+
|
|
28
|
+
expect(result).toContain("# Webpack/Rspack Configuration Export")
|
|
29
|
+
expect(result).toContain("# Generated: 2025-01-15T12:00:00Z")
|
|
30
|
+
expect(result).toContain("# Environment: development")
|
|
31
|
+
expect(result).toContain("# Bundler: webpack")
|
|
32
|
+
expect(result).toContain("# Config Type: client")
|
|
33
|
+
expect(result).toContain("mode: development")
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test("includes config count when multiple configs present", () => {
|
|
37
|
+
const config = { mode: "production" }
|
|
38
|
+
const metadata = {
|
|
39
|
+
exportedAt: "2025-01-15T12:00:00Z",
|
|
40
|
+
environment: "production",
|
|
41
|
+
bundler: "rspack",
|
|
42
|
+
configType: "server",
|
|
43
|
+
configCount: 3
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const result = serializer.serialize(config, metadata)
|
|
47
|
+
|
|
48
|
+
expect(result).toContain("# Total Configs: 3")
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test("omits config count when only one config present", () => {
|
|
52
|
+
const config = { mode: "production" }
|
|
53
|
+
const metadata = {
|
|
54
|
+
exportedAt: "2025-01-15T12:00:00Z",
|
|
55
|
+
environment: "production",
|
|
56
|
+
bundler: "rspack",
|
|
57
|
+
configType: "server",
|
|
58
|
+
configCount: 1
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const result = serializer.serialize(config, metadata)
|
|
62
|
+
|
|
63
|
+
expect(result).not.toContain("# Total Configs:")
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test("serializes simple objects correctly", () => {
|
|
67
|
+
const config = {
|
|
68
|
+
mode: "development",
|
|
69
|
+
devtool: "source-map"
|
|
70
|
+
}
|
|
71
|
+
const metadata = {
|
|
72
|
+
exportedAt: "2025-01-15T12:00:00Z",
|
|
73
|
+
environment: "development",
|
|
74
|
+
bundler: "webpack",
|
|
75
|
+
configType: "client",
|
|
76
|
+
configCount: 1
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const result = serializer.serialize(config, metadata)
|
|
80
|
+
|
|
81
|
+
expect(result).toContain("mode: development")
|
|
82
|
+
expect(result).toContain("devtool: source-map")
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test("serializes nested objects", () => {
|
|
86
|
+
const config = {
|
|
87
|
+
output: {
|
|
88
|
+
path: "/dist",
|
|
89
|
+
filename: "bundle.js"
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const metadata = {
|
|
93
|
+
exportedAt: "2025-01-15T12:00:00Z",
|
|
94
|
+
environment: "development",
|
|
95
|
+
bundler: "webpack",
|
|
96
|
+
configType: "client",
|
|
97
|
+
configCount: 1
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const result = serializer.serialize(config, metadata)
|
|
101
|
+
|
|
102
|
+
expect(result).toContain("output:")
|
|
103
|
+
expect(result).toContain("path: /dist")
|
|
104
|
+
expect(result).toContain("filename: bundle.js")
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
test("handles empty objects with constructor names", () => {
|
|
108
|
+
class CustomPlugin {}
|
|
109
|
+
const config = {
|
|
110
|
+
plugins: [new CustomPlugin()]
|
|
111
|
+
}
|
|
112
|
+
const metadata = {
|
|
113
|
+
exportedAt: "2025-01-15T12:00:00Z",
|
|
114
|
+
environment: "development",
|
|
115
|
+
bundler: "webpack",
|
|
116
|
+
configType: "client",
|
|
117
|
+
configCount: 1
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const result = serializer.serialize(config, metadata)
|
|
121
|
+
|
|
122
|
+
expect(result).toContain("plugins:")
|
|
123
|
+
expect(result).toContain("CustomPlugin")
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
test("serializes arrays", () => {
|
|
127
|
+
const config = {
|
|
128
|
+
entry: ["./src/index.js", "./src/app.js"]
|
|
129
|
+
}
|
|
130
|
+
const metadata = {
|
|
131
|
+
exportedAt: "2025-01-15T12:00:00Z",
|
|
132
|
+
environment: "development",
|
|
133
|
+
bundler: "webpack",
|
|
134
|
+
configType: "client",
|
|
135
|
+
configCount: 1
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const result = serializer.serialize(config, metadata)
|
|
139
|
+
|
|
140
|
+
expect(result).toContain("entry:")
|
|
141
|
+
expect(result).toContain("- ./src/index.js")
|
|
142
|
+
expect(result).toContain("- ./src/app.js")
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
test("handles functions in config", () => {
|
|
146
|
+
const config = {
|
|
147
|
+
output: {
|
|
148
|
+
filename() {
|
|
149
|
+
return "bundle.js"
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const metadata = {
|
|
154
|
+
exportedAt: "2025-01-15T12:00:00Z",
|
|
155
|
+
environment: "development",
|
|
156
|
+
bundler: "webpack",
|
|
157
|
+
configType: "client",
|
|
158
|
+
configCount: 1
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const result = serializer.serialize(config, metadata)
|
|
162
|
+
|
|
163
|
+
expect(result).toContain("output:")
|
|
164
|
+
expect(result).toContain("filename: |")
|
|
165
|
+
expect(result).toContain("filename()")
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
test("handles RegExp in config without flags", () => {
|
|
169
|
+
const config = {
|
|
170
|
+
test: /\.js$/
|
|
171
|
+
}
|
|
172
|
+
const metadata = {
|
|
173
|
+
exportedAt: "2025-01-15T12:00:00Z",
|
|
174
|
+
environment: "development",
|
|
175
|
+
bundler: "webpack",
|
|
176
|
+
configType: "client",
|
|
177
|
+
configCount: 1
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const result = serializer.serialize(config, metadata)
|
|
181
|
+
|
|
182
|
+
// RegExp objects serialize as their pattern without slashes
|
|
183
|
+
expect(result).toContain("test: \\.js$")
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
test("handles RegExp with flags in config", () => {
|
|
187
|
+
const config = {
|
|
188
|
+
test: /\.js$/i
|
|
189
|
+
}
|
|
190
|
+
const metadata = {
|
|
191
|
+
exportedAt: "2025-01-15T12:00:00Z",
|
|
192
|
+
environment: "development",
|
|
193
|
+
bundler: "webpack",
|
|
194
|
+
configType: "client",
|
|
195
|
+
configCount: 1
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const result = serializer.serialize(config, metadata)
|
|
199
|
+
|
|
200
|
+
// RegExp with flags includes flags as inline comment
|
|
201
|
+
expect(result).toContain("test: \\.js$ # flags: i")
|
|
202
|
+
})
|
|
203
|
+
})
|
|
204
|
+
})
|
|
@@ -63,6 +63,50 @@ describe("Path Validation Security", () => {
|
|
|
63
63
|
safeResolvePath(basePath, maliciousPath)
|
|
64
64
|
}).toThrow("Path traversal attempt detected")
|
|
65
65
|
})
|
|
66
|
+
|
|
67
|
+
it("rethrows non-ENOENT errors for better security", () => {
|
|
68
|
+
const fs = require("fs")
|
|
69
|
+
|
|
70
|
+
// Mock fs.realpathSync to throw EACCES (permission denied)
|
|
71
|
+
const realpathSyncSpy = jest
|
|
72
|
+
.spyOn(fs, "realpathSync")
|
|
73
|
+
.mockImplementation(() => {
|
|
74
|
+
const error = new Error("Permission denied")
|
|
75
|
+
error.code = "EACCES"
|
|
76
|
+
throw error
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
const basePath = path.join(path.sep, "app")
|
|
80
|
+
const userPath = path.join("src", "index.js")
|
|
81
|
+
|
|
82
|
+
expect(() => {
|
|
83
|
+
safeResolvePath(basePath, userPath)
|
|
84
|
+
}).toThrow("Permission denied")
|
|
85
|
+
|
|
86
|
+
// Restore original function
|
|
87
|
+
realpathSyncSpy.mockRestore()
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it("handles errors without code property gracefully", () => {
|
|
91
|
+
const fs = require("fs")
|
|
92
|
+
|
|
93
|
+
// Mock fs.realpathSync to throw error without code property
|
|
94
|
+
const realpathSyncSpy = jest
|
|
95
|
+
.spyOn(fs, "realpathSync")
|
|
96
|
+
.mockImplementation(() => {
|
|
97
|
+
throw new Error("Unknown error")
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
const basePath = path.join(path.sep, "app")
|
|
101
|
+
const userPath = path.join("src", "index.js")
|
|
102
|
+
|
|
103
|
+
expect(() => {
|
|
104
|
+
safeResolvePath(basePath, userPath)
|
|
105
|
+
}).toThrow("Unknown error")
|
|
106
|
+
|
|
107
|
+
// Restore original function
|
|
108
|
+
realpathSyncSpy.mockRestore()
|
|
109
|
+
})
|
|
66
110
|
})
|
|
67
111
|
|
|
68
112
|
describe("validatePaths", () => {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Tests for requireOrError utility
|
|
2
|
+
const { requireOrError } = require("../../package/utils/requireOrError")
|
|
3
|
+
|
|
4
|
+
describe("requireOrError", () => {
|
|
5
|
+
describe("successful require", () => {
|
|
6
|
+
it("returns the required module", () => {
|
|
7
|
+
const result = requireOrError("path")
|
|
8
|
+
expect(result).toBeDefined()
|
|
9
|
+
expect(typeof result.join).toBe("function")
|
|
10
|
+
})
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
describe("failed require", () => {
|
|
14
|
+
it("throws error with helpful message when module not found", () => {
|
|
15
|
+
expect(() => {
|
|
16
|
+
requireOrError("nonexistent-module-that-does-not-exist")
|
|
17
|
+
}).toThrow("[SHAKAPACKER]")
|
|
18
|
+
expect(() => {
|
|
19
|
+
requireOrError("nonexistent-module-that-does-not-exist")
|
|
20
|
+
}).toThrow("is required for")
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it("includes original error as cause for debugging", () => {
|
|
24
|
+
let caughtError
|
|
25
|
+
try {
|
|
26
|
+
requireOrError("nonexistent-module-that-does-not-exist")
|
|
27
|
+
} catch (error) {
|
|
28
|
+
caughtError = error
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
expect(caughtError).toBeDefined()
|
|
32
|
+
expect(caughtError.cause).toBeDefined()
|
|
33
|
+
expect(caughtError.cause.code).toBe("MODULE_NOT_FOUND")
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it("preserves original error stack in cause", () => {
|
|
37
|
+
let caughtError
|
|
38
|
+
try {
|
|
39
|
+
requireOrError("another-nonexistent-module")
|
|
40
|
+
} catch (error) {
|
|
41
|
+
caughtError = error
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
expect(caughtError.cause).toBeDefined()
|
|
45
|
+
expect(caughtError.cause.stack).toBeDefined()
|
|
46
|
+
expect(typeof caughtError.cause.stack).toBe("string")
|
|
47
|
+
})
|
|
48
|
+
})
|
|
49
|
+
})
|
data/yarn.lock
CHANGED
|
@@ -972,11 +972,6 @@
|
|
|
972
972
|
resolved "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.9.0.tgz#3e8127b5c225cb2d99b991f251768188efbda25f"
|
|
973
973
|
integrity sha512-gE3QJvhh0Yj9cSAkkHjRLKPmC7BTJeiaB5YyhVKVUwbnWQgTszV92lZ9pvZtNPEghP7jPbhEs4c6983A0ojQwA==
|
|
974
974
|
|
|
975
|
-
"@pkgr/core@^0.2.9":
|
|
976
|
-
version "0.2.9"
|
|
977
|
-
resolved "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz#d229a7b7f9dac167a156992ef23c7f023653f53b"
|
|
978
|
-
integrity sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==
|
|
979
|
-
|
|
980
975
|
"@polka/url@^1.0.0-next.24":
|
|
981
976
|
version "1.0.0-next.29"
|
|
982
977
|
resolved "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz#5a40109a1ab5f84d6fd8fc928b19f367cbe7e7b1"
|
|
@@ -3027,14 +3022,6 @@ eslint-plugin-jsx-a11y@^6.10.2:
|
|
|
3027
3022
|
safe-regex-test "^1.0.3"
|
|
3028
3023
|
string.prototype.includes "^2.0.1"
|
|
3029
3024
|
|
|
3030
|
-
eslint-plugin-prettier@^5.5.4:
|
|
3031
|
-
version "5.5.4"
|
|
3032
|
-
resolved "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz#9d61c4ea11de5af704d4edf108c82ccfa7f2e61c"
|
|
3033
|
-
integrity sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==
|
|
3034
|
-
dependencies:
|
|
3035
|
-
prettier-linter-helpers "^1.0.0"
|
|
3036
|
-
synckit "^0.11.7"
|
|
3037
|
-
|
|
3038
3025
|
eslint-plugin-react-hooks@^7.0.0:
|
|
3039
3026
|
version "7.0.0"
|
|
3040
3027
|
resolved "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.0.tgz#a255a1db826ea42b0e37f160430e4bd0b4b659f9"
|
|
@@ -3293,11 +3280,6 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
|
|
|
3293
3280
|
resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
|
3294
3281
|
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
|
|
3295
3282
|
|
|
3296
|
-
fast-diff@^1.1.2:
|
|
3297
|
-
version "1.3.0"
|
|
3298
|
-
resolved "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0"
|
|
3299
|
-
integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==
|
|
3300
|
-
|
|
3301
3283
|
fast-glob@^3.3.2, fast-glob@^3.3.3:
|
|
3302
3284
|
version "3.3.3"
|
|
3303
3285
|
resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818"
|
|
@@ -5458,13 +5440,6 @@ prelude-ls@^1.2.1:
|
|
|
5458
5440
|
resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
|
5459
5441
|
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
|
|
5460
5442
|
|
|
5461
|
-
prettier-linter-helpers@^1.0.0:
|
|
5462
|
-
version "1.0.0"
|
|
5463
|
-
resolved "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b"
|
|
5464
|
-
integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==
|
|
5465
|
-
dependencies:
|
|
5466
|
-
fast-diff "^1.1.2"
|
|
5467
|
-
|
|
5468
5443
|
prettier@^3.2.5:
|
|
5469
5444
|
version "3.6.2"
|
|
5470
5445
|
resolved "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz#ccda02a1003ebbb2bfda6f83a074978f608b9393"
|
|
@@ -6306,13 +6281,6 @@ swc-loader@^0.1.15:
|
|
|
6306
6281
|
resolved "https://registry.npmjs.org/swc-loader/-/swc-loader-0.1.16.tgz#4c718d698e518f3e6ceb9f7872c1855cdb187066"
|
|
6307
6282
|
integrity sha512-NKIm8aJjK/z/yfzk+v7YGwJMjBKaLaUs9ZKI2zoaIGKAjtkwjO92ZLI0fiTZuwzRqVLQl/29fBdSgFCBzquR0w==
|
|
6308
6283
|
|
|
6309
|
-
synckit@^0.11.7:
|
|
6310
|
-
version "0.11.11"
|
|
6311
|
-
resolved "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz#c0b619cf258a97faa209155d9cd1699b5c998cb0"
|
|
6312
|
-
integrity sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==
|
|
6313
|
-
dependencies:
|
|
6314
|
-
"@pkgr/core" "^0.2.9"
|
|
6315
|
-
|
|
6316
6284
|
tapable@^2.1.1, tapable@^2.2.0, tapable@^2.2.1, tapable@^2.3.0:
|
|
6317
6285
|
version "2.3.0"
|
|
6318
6286
|
resolved "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz#7e3ea6d5ca31ba8e078b560f0d83ce9a14aa8be6"
|