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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -109
  3. data/Gemfile.lock +1 -1
  4. data/README.md +53 -2
  5. data/docs/configuration.md +28 -0
  6. data/docs/rspack_migration_guide.md +238 -2
  7. data/docs/troubleshooting.md +21 -21
  8. data/eslint.config.fast.js +8 -0
  9. data/eslint.config.js +47 -10
  10. data/knip.ts +8 -1
  11. data/lib/install/config/shakapacker.yml +6 -6
  12. data/lib/shakapacker/configuration.rb +227 -4
  13. data/lib/shakapacker/dev_server.rb +88 -1
  14. data/lib/shakapacker/doctor.rb +4 -4
  15. data/lib/shakapacker/instance.rb +85 -1
  16. data/lib/shakapacker/manifest.rb +85 -11
  17. data/lib/shakapacker/version.rb +1 -1
  18. data/lib/shakapacker.rb +143 -3
  19. data/lib/tasks/shakapacker/doctor.rake +1 -1
  20. data/lib/tasks/shakapacker/export_bundler_config.rake +4 -4
  21. data/package/config.ts +0 -1
  22. data/package/configExporter/buildValidator.ts +53 -29
  23. data/package/configExporter/cli.ts +81 -56
  24. data/package/configExporter/configFile.ts +33 -26
  25. data/package/configExporter/types.ts +64 -0
  26. data/package/configExporter/yamlSerializer.ts +118 -43
  27. data/package/dev_server.ts +2 -1
  28. data/package/env.ts +1 -1
  29. data/package/environments/base.ts +4 -4
  30. data/package/environments/development.ts +7 -6
  31. data/package/environments/production.ts +6 -7
  32. data/package/environments/test.ts +2 -1
  33. data/package/index.ts +28 -4
  34. data/package/loaders.d.ts +2 -2
  35. data/package/optimization/webpack.ts +29 -31
  36. data/package/rspack/index.ts +2 -1
  37. data/package/rules/file.ts +1 -0
  38. data/package/rules/jscommon.ts +1 -0
  39. data/package/utils/helpers.ts +0 -1
  40. data/package/utils/pathValidation.ts +68 -7
  41. data/package/utils/requireOrError.ts +10 -2
  42. data/package/utils/typeGuards.ts +43 -46
  43. data/package/webpack-types.d.ts +2 -2
  44. data/package/webpackDevServerConfig.ts +1 -0
  45. data/package.json +2 -3
  46. data/test/package/configExporter/cli.test.js +440 -0
  47. data/test/package/configExporter/types.test.js +163 -0
  48. data/test/package/configExporter.test.js +264 -0
  49. data/test/package/yamlSerializer.test.js +204 -0
  50. data/test/typescript/pathValidation.test.js +44 -0
  51. data/test/typescript/requireOrError.test.js +49 -0
  52. data/yarn.lock +0 -32
  53. metadata +11 -5
  54. data/.eslintrc.fast.js +0 -40
  55. data/.eslintrc.js +0 -84
@@ -0,0 +1,440 @@
1
+ const { resetEnv } = require("../../helpers")
2
+
3
+ describe("configExporter/cli", () => {
4
+ let mockExit
5
+
6
+ beforeEach(() => {
7
+ jest.resetModules()
8
+ resetEnv()
9
+ // Mock process.exit to prevent yargs from killing the test process
10
+ mockExit = jest.spyOn(process, "exit").mockImplementation(() => {
11
+ throw new Error("process.exit called")
12
+ })
13
+ })
14
+
15
+ afterEach(() => {
16
+ mockExit.mockRestore()
17
+ })
18
+
19
+ describe("parseArguments", () => {
20
+ test("parses basic CLI options", () => {
21
+ const { parseArguments } = require("../../../package/configExporter/cli")
22
+ const options = parseArguments(["--env=production", "--bundler=webpack"])
23
+
24
+ expect(options.env).toBe("production")
25
+ expect(options.bundler).toBe("webpack")
26
+ })
27
+
28
+ test("parses --init flag", () => {
29
+ const { parseArguments } = require("../../../package/configExporter/cli")
30
+ const options = parseArguments(["--init"])
31
+
32
+ expect(options.init).toBe(true)
33
+ })
34
+
35
+ test("parses --ssr flag with --init", () => {
36
+ const { parseArguments } = require("../../../package/configExporter/cli")
37
+ const options = parseArguments(["--init", "--ssr"])
38
+
39
+ expect(options.init).toBe(true)
40
+ expect(options.ssr).toBe(true)
41
+ })
42
+
43
+ test("parses --doctor flag", () => {
44
+ const { parseArguments } = require("../../../package/configExporter/cli")
45
+ const options = parseArguments(["--doctor"])
46
+
47
+ expect(options.doctor).toBe(true)
48
+ })
49
+
50
+ test("parses --stdout flag", () => {
51
+ const { parseArguments } = require("../../../package/configExporter/cli")
52
+ const options = parseArguments(["--stdout"])
53
+
54
+ expect(options.stdout).toBe(true)
55
+ })
56
+
57
+ test("parses --output flag", () => {
58
+ const { parseArguments } = require("../../../package/configExporter/cli")
59
+ const options = parseArguments(["--output=config.yml"])
60
+
61
+ expect(options.output).toBe("config.yml")
62
+ })
63
+
64
+ test("parses --save-dir flag", () => {
65
+ const { parseArguments } = require("../../../package/configExporter/cli")
66
+ const options = parseArguments(["--save-dir=./configs"])
67
+
68
+ expect(options.saveDir).toBe("./configs")
69
+ })
70
+
71
+ test("parses --format=yaml", () => {
72
+ const { parseArguments } = require("../../../package/configExporter/cli")
73
+ const options = parseArguments(["--format=yaml"])
74
+
75
+ expect(options.format).toBe("yaml")
76
+ })
77
+
78
+ test("parses --format=json", () => {
79
+ const { parseArguments } = require("../../../package/configExporter/cli")
80
+ const options = parseArguments(["--format=json"])
81
+
82
+ expect(options.format).toBe("json")
83
+ })
84
+
85
+ test("parses --format=inspect", () => {
86
+ const { parseArguments } = require("../../../package/configExporter/cli")
87
+ const options = parseArguments(["--format=inspect"])
88
+
89
+ expect(options.format).toBe("inspect")
90
+ })
91
+
92
+ test("parses --annotate flag", () => {
93
+ const { parseArguments } = require("../../../package/configExporter/cli")
94
+ const options = parseArguments(["--annotate"])
95
+
96
+ expect(options.annotate).toBe(true)
97
+ })
98
+
99
+ test("parses --no-annotate flag", () => {
100
+ const { parseArguments } = require("../../../package/configExporter/cli")
101
+ const options = parseArguments(["--no-annotate"])
102
+
103
+ expect(options.annotate).toBe(false)
104
+ })
105
+
106
+ test("parses --depth with number", () => {
107
+ const { parseArguments } = require("../../../package/configExporter/cli")
108
+ const options = parseArguments(["--depth=10"])
109
+
110
+ expect(options.depth).toBe(10)
111
+ })
112
+
113
+ test("parses --depth=null to return null", () => {
114
+ const { parseArguments } = require("../../../package/configExporter/cli")
115
+ const options = parseArguments(["--depth=null"])
116
+
117
+ expect(options.depth).toBeNull()
118
+ })
119
+
120
+ test("parses --verbose flag", () => {
121
+ const { parseArguments } = require("../../../package/configExporter/cli")
122
+ const options = parseArguments(["--verbose"])
123
+
124
+ expect(options.verbose).toBe(true)
125
+ })
126
+
127
+ test("parses --client-only flag", () => {
128
+ const { parseArguments } = require("../../../package/configExporter/cli")
129
+ const options = parseArguments(["--client-only"])
130
+
131
+ expect(options.clientOnly).toBe(true)
132
+ })
133
+
134
+ test("parses --server-only flag", () => {
135
+ const { parseArguments } = require("../../../package/configExporter/cli")
136
+ const options = parseArguments(["--server-only"])
137
+
138
+ expect(options.serverOnly).toBe(true)
139
+ })
140
+
141
+ test("parses --webpack flag", () => {
142
+ const { parseArguments } = require("../../../package/configExporter/cli")
143
+ const options = parseArguments(["--webpack"])
144
+
145
+ expect(options.bundler).toBe("webpack")
146
+ })
147
+
148
+ test("parses --rspack flag", () => {
149
+ const { parseArguments } = require("../../../package/configExporter/cli")
150
+ const options = parseArguments(["--rspack"])
151
+
152
+ expect(options.bundler).toBe("rspack")
153
+ })
154
+
155
+ test("parses --build flag", () => {
156
+ const { parseArguments } = require("../../../package/configExporter/cli")
157
+ const options = parseArguments(["--build=dev"])
158
+
159
+ expect(options.build).toBe("dev")
160
+ })
161
+
162
+ test("parses --all-builds flag", () => {
163
+ const { parseArguments } = require("../../../package/configExporter/cli")
164
+ const options = parseArguments(["--all-builds"])
165
+
166
+ expect(options.allBuilds).toBe(true)
167
+ })
168
+
169
+ test("parses --list-builds flag", () => {
170
+ const { parseArguments } = require("../../../package/configExporter/cli")
171
+ const options = parseArguments(["--list-builds"])
172
+
173
+ expect(options.listBuilds).toBe(true)
174
+ })
175
+
176
+ test("parses --validate flag", () => {
177
+ const { parseArguments } = require("../../../package/configExporter/cli")
178
+ const options = parseArguments(["--validate"])
179
+
180
+ expect(options.validate).toBe(true)
181
+ })
182
+
183
+ test("parses --validate-build flag", () => {
184
+ const { parseArguments } = require("../../../package/configExporter/cli")
185
+ const options = parseArguments(["--validate-build=dev"])
186
+
187
+ expect(options.validateBuild).toBe("dev")
188
+ })
189
+
190
+ test("parses --config-file flag", () => {
191
+ const { parseArguments } = require("../../../package/configExporter/cli")
192
+ const options = parseArguments(["--config-file=custom-config.yml"])
193
+
194
+ expect(options.configFile).toBe("custom-config.yml")
195
+ })
196
+
197
+ test("parses combination of flags", () => {
198
+ const { parseArguments } = require("../../../package/configExporter/cli")
199
+ const options = parseArguments([
200
+ "--env=production",
201
+ "--bundler=rspack",
202
+ "--format=yaml",
203
+ "--verbose"
204
+ ])
205
+
206
+ expect(options.env).toBe("production")
207
+ expect(options.bundler).toBe("rspack")
208
+ expect(options.format).toBe("yaml")
209
+ expect(options.verbose).toBe(true)
210
+ })
211
+ })
212
+
213
+ describe("parseArguments - validation", () => {
214
+ test("throws error when both --webpack and --rspack are provided", () => {
215
+ const { parseArguments } = require("../../../package/configExporter/cli")
216
+
217
+ expect(() => {
218
+ parseArguments(["--webpack", "--rspack"])
219
+ }).toThrow("process.exit called") // yargs calls process.exit on validation failure
220
+ })
221
+
222
+ test("throws error when both --client-only and --server-only are provided", () => {
223
+ const { parseArguments } = require("../../../package/configExporter/cli")
224
+
225
+ expect(() => {
226
+ parseArguments(["--client-only", "--server-only"])
227
+ }).toThrow("process.exit called")
228
+ })
229
+
230
+ test("throws error when both --output and --save-dir are provided", () => {
231
+ const { parseArguments } = require("../../../package/configExporter/cli")
232
+
233
+ expect(() => {
234
+ parseArguments(["--output=file.yml", "--save-dir=./configs"])
235
+ }).toThrow("process.exit called")
236
+ })
237
+
238
+ test("throws error when both --stdout and --save-dir are provided", () => {
239
+ const { parseArguments } = require("../../../package/configExporter/cli")
240
+
241
+ expect(() => {
242
+ parseArguments(["--stdout", "--save-dir=./configs"])
243
+ }).toThrow("process.exit called")
244
+ })
245
+
246
+ test("throws error when both --build and --all-builds are provided", () => {
247
+ const { parseArguments } = require("../../../package/configExporter/cli")
248
+
249
+ expect(() => {
250
+ parseArguments(["--build=dev", "--all-builds"])
251
+ }).toThrow("process.exit called")
252
+ })
253
+
254
+ test("throws error when both --validate and --validate-build are provided", () => {
255
+ const { parseArguments } = require("../../../package/configExporter/cli")
256
+
257
+ expect(() => {
258
+ parseArguments(["--validate", "--validate-build=dev"])
259
+ }).toThrow("process.exit called")
260
+ })
261
+
262
+ test("throws error when --validate is used with --build", () => {
263
+ const { parseArguments } = require("../../../package/configExporter/cli")
264
+
265
+ expect(() => {
266
+ parseArguments(["--validate", "--build=dev"])
267
+ }).toThrow("process.exit called")
268
+ })
269
+
270
+ test("throws error when --validate is used with --all-builds", () => {
271
+ const { parseArguments } = require("../../../package/configExporter/cli")
272
+
273
+ expect(() => {
274
+ parseArguments(["--validate", "--all-builds"])
275
+ }).toThrow("process.exit called")
276
+ })
277
+
278
+ test("throws error when --ssr is used without --init", () => {
279
+ const { parseArguments } = require("../../../package/configExporter/cli")
280
+
281
+ expect(() => {
282
+ parseArguments(["--ssr"])
283
+ }).toThrow("process.exit called")
284
+ })
285
+ })
286
+
287
+ describe("parseArguments - type coercion", () => {
288
+ test("coerces string depth to number", () => {
289
+ const { parseArguments } = require("../../../package/configExporter/cli")
290
+ const options = parseArguments(["--depth=15"])
291
+
292
+ expect(options.depth).toBe(15)
293
+ expect(typeof options.depth).toBe("number")
294
+ })
295
+
296
+ test("handles null string for depth", () => {
297
+ const { parseArguments } = require("../../../package/configExporter/cli")
298
+ const options = parseArguments(["--depth=null"])
299
+
300
+ expect(options.depth).toBeNull()
301
+ })
302
+
303
+ test("uses default depth when not provided", () => {
304
+ const { parseArguments } = require("../../../package/configExporter/cli")
305
+ const options = parseArguments([])
306
+
307
+ expect(options.depth).toBe(20)
308
+ })
309
+ })
310
+
311
+ describe("parseArguments - edge cases", () => {
312
+ test("handles empty arguments array", () => {
313
+ const { parseArguments } = require("../../../package/configExporter/cli")
314
+ const options = parseArguments([])
315
+
316
+ expect(options).toBeDefined()
317
+ expect(options.doctor).toBe(false)
318
+ expect(options.init).toBe(false)
319
+ })
320
+
321
+ test("handles unknown environment value", () => {
322
+ const { parseArguments } = require("../../../package/configExporter/cli")
323
+
324
+ // yargs validates choices, so invalid env should throw
325
+ expect(() => {
326
+ parseArguments(["--env=invalid"])
327
+ }).toThrow("process.exit called")
328
+ })
329
+
330
+ test("handles unknown format value", () => {
331
+ const { parseArguments } = require("../../../package/configExporter/cli")
332
+
333
+ // yargs validates choices, so invalid format should throw
334
+ expect(() => {
335
+ parseArguments(["--format=invalid"])
336
+ }).toThrow("process.exit called")
337
+ })
338
+
339
+ test("handles unknown bundler value", () => {
340
+ const { parseArguments } = require("../../../package/configExporter/cli")
341
+
342
+ // yargs validates choices, so invalid bundler should throw
343
+ expect(() => {
344
+ parseArguments(["--bundler=invalid"])
345
+ }).toThrow("process.exit called")
346
+ })
347
+
348
+ test("throws error for invalid depth value (NaN)", () => {
349
+ const { parseArguments } = require("../../../package/configExporter/cli")
350
+
351
+ expect(() => {
352
+ parseArguments(["--depth=abc"])
353
+ }).toThrow("process.exit called") // yargs calls process.exit on coercion failure
354
+ })
355
+
356
+ test("throws error for depth with invalid string", () => {
357
+ const { parseArguments } = require("../../../package/configExporter/cli")
358
+
359
+ expect(() => {
360
+ parseArguments(["--depth=invalid"])
361
+ }).toThrow("process.exit called")
362
+ })
363
+ })
364
+
365
+ describe("parseArguments - path validation", () => {
366
+ // Note: Path validation tests are separate from other validation tests because
367
+ // they use a different validation mechanism. Other validations (mutual exclusivity,
368
+ // required combinations) happen in yargs .check() hook during parsing.
369
+ // Path validation happens later in run() after applyDefaults() to ensure
370
+ // default paths are also validated. These tests verify parseArguments() accepts
371
+ // all paths (validation is deferred to run()).
372
+
373
+ test("accepts output path within cwd", () => {
374
+ const { parseArguments } = require("../../../package/configExporter/cli")
375
+ const relativePath = "./output/config.yml"
376
+
377
+ expect(() => {
378
+ parseArguments([`--output=${relativePath}`])
379
+ }).not.toThrow()
380
+ })
381
+
382
+ test("accepts save-dir path within cwd", () => {
383
+ const { parseArguments } = require("../../../package/configExporter/cli")
384
+ const relativePath = "./configs"
385
+
386
+ expect(() => {
387
+ parseArguments([`--save-dir=${relativePath}`])
388
+ }).not.toThrow()
389
+ })
390
+
391
+ test("accepts output path outside cwd during parsing", () => {
392
+ const { parseArguments } = require("../../../package/configExporter/cli")
393
+ // Path validation happens later in run(), not during parsing
394
+ const maliciousPath = "../../etc/passwd"
395
+
396
+ expect(() => {
397
+ parseArguments([`--output=${maliciousPath}`])
398
+ }).not.toThrow()
399
+ })
400
+
401
+ test("accepts save-dir path outside cwd during parsing", () => {
402
+ const { parseArguments } = require("../../../package/configExporter/cli")
403
+ // Path validation happens later in run(), not during parsing
404
+ const maliciousPath = "../../etc"
405
+
406
+ expect(() => {
407
+ parseArguments([`--save-dir=${maliciousPath}`])
408
+ }).not.toThrow()
409
+ })
410
+
411
+ test("accepts absolute paths during parsing", () => {
412
+ const { parseArguments } = require("../../../package/configExporter/cli")
413
+ // Path validation happens later in run(), not during parsing
414
+ const outsidePath = "/tmp/config.yml"
415
+
416
+ expect(() => {
417
+ parseArguments([`--output=${outsidePath}`])
418
+ }).not.toThrow()
419
+ })
420
+
421
+ test("accepts various paths including those that would fail validation in run()", () => {
422
+ const { parseArguments } = require("../../../package/configExporter/cli")
423
+ // These paths would be rejected during run() but parseArguments() accepts them
424
+ // Testing multiple scenarios to ensure parsing is permissive
425
+ const testPaths = [
426
+ "./output.yml", // Valid relative path
427
+ "./subdir/output.yml", // Valid nested path
428
+ "../../etc/passwd", // Path traversal (would fail in run())
429
+ "/etc/passwd", // Absolute path outside cwd (would fail in run() on Unix)
430
+ "/tmp/output.yml" // Another absolute path (would fail in run())
431
+ ]
432
+
433
+ testPaths.forEach((path) => {
434
+ expect(() => {
435
+ parseArguments([`--output=${path}`])
436
+ }).not.toThrow()
437
+ })
438
+ })
439
+ })
440
+ })
@@ -0,0 +1,163 @@
1
+ const {
2
+ isBuildEnvVar,
3
+ isDangerousEnvVar,
4
+ BUILD_ENV_VARS,
5
+ DANGEROUS_ENV_VARS
6
+ } = require("../../../package/configExporter/types")
7
+
8
+ describe("configExporter/types", () => {
9
+ describe("isBuildEnvVar", () => {
10
+ test("returns true for whitelisted NODE_ENV", () => {
11
+ expect(isBuildEnvVar("NODE_ENV")).toBe(true)
12
+ })
13
+
14
+ test("returns true for whitelisted RAILS_ENV", () => {
15
+ expect(isBuildEnvVar("RAILS_ENV")).toBe(true)
16
+ })
17
+
18
+ test("returns true for whitelisted WEBPACK_SERVE", () => {
19
+ expect(isBuildEnvVar("WEBPACK_SERVE")).toBe(true)
20
+ })
21
+
22
+ test("returns true for whitelisted CLIENT_BUNDLE_ONLY", () => {
23
+ expect(isBuildEnvVar("CLIENT_BUNDLE_ONLY")).toBe(true)
24
+ })
25
+
26
+ test("returns true for whitelisted SERVER_BUNDLE_ONLY", () => {
27
+ expect(isBuildEnvVar("SERVER_BUNDLE_ONLY")).toBe(true)
28
+ })
29
+
30
+ test("returns true for whitelisted NODE_OPTIONS", () => {
31
+ expect(isBuildEnvVar("NODE_OPTIONS")).toBe(true)
32
+ })
33
+
34
+ test("returns true for whitelisted BABEL_ENV", () => {
35
+ expect(isBuildEnvVar("BABEL_ENV")).toBe(true)
36
+ })
37
+
38
+ test("returns false for dangerous PATH", () => {
39
+ expect(isBuildEnvVar("PATH")).toBe(false)
40
+ })
41
+
42
+ test("returns false for dangerous LD_PRELOAD", () => {
43
+ expect(isBuildEnvVar("LD_PRELOAD")).toBe(false)
44
+ })
45
+
46
+ test("returns false for arbitrary custom variable", () => {
47
+ expect(isBuildEnvVar("CUSTOM_VAR")).toBe(false)
48
+ expect(isBuildEnvVar("MY_SECRET_KEY")).toBe(false)
49
+ })
50
+
51
+ test("returns false for empty string", () => {
52
+ expect(isBuildEnvVar("")).toBe(false)
53
+ })
54
+
55
+ test("is case sensitive", () => {
56
+ expect(isBuildEnvVar("node_env")).toBe(false)
57
+ expect(isBuildEnvVar("Node_Env")).toBe(false)
58
+ })
59
+ })
60
+
61
+ describe("isDangerousEnvVar", () => {
62
+ test("returns true for dangerous PATH", () => {
63
+ expect(isDangerousEnvVar("PATH")).toBe(true)
64
+ })
65
+
66
+ test("returns true for dangerous HOME", () => {
67
+ expect(isDangerousEnvVar("HOME")).toBe(true)
68
+ })
69
+
70
+ test("returns true for dangerous LD_PRELOAD", () => {
71
+ expect(isDangerousEnvVar("LD_PRELOAD")).toBe(true)
72
+ })
73
+
74
+ test("returns true for dangerous LD_LIBRARY_PATH", () => {
75
+ expect(isDangerousEnvVar("LD_LIBRARY_PATH")).toBe(true)
76
+ })
77
+
78
+ test("returns true for dangerous DYLD_LIBRARY_PATH", () => {
79
+ expect(isDangerousEnvVar("DYLD_LIBRARY_PATH")).toBe(true)
80
+ })
81
+
82
+ test("returns true for dangerous DYLD_INSERT_LIBRARIES", () => {
83
+ expect(isDangerousEnvVar("DYLD_INSERT_LIBRARIES")).toBe(true)
84
+ })
85
+
86
+ test("returns false for safe NODE_ENV", () => {
87
+ expect(isDangerousEnvVar("NODE_ENV")).toBe(false)
88
+ })
89
+
90
+ test("returns false for safe RAILS_ENV", () => {
91
+ expect(isDangerousEnvVar("RAILS_ENV")).toBe(false)
92
+ })
93
+
94
+ test("returns false for arbitrary custom variable", () => {
95
+ expect(isDangerousEnvVar("CUSTOM_VAR")).toBe(false)
96
+ })
97
+
98
+ test("returns false for empty string", () => {
99
+ expect(isDangerousEnvVar("")).toBe(false)
100
+ })
101
+
102
+ test("is case sensitive", () => {
103
+ expect(isDangerousEnvVar("path")).toBe(false)
104
+ expect(isDangerousEnvVar("Path")).toBe(false)
105
+ })
106
+ })
107
+
108
+ describe("constant values", () => {
109
+ test("contains expected BUILD_ENV_VARS variables", () => {
110
+ expect(BUILD_ENV_VARS).toContain("NODE_ENV")
111
+ expect(BUILD_ENV_VARS).toContain("RAILS_ENV")
112
+ expect(BUILD_ENV_VARS).toContain("NODE_OPTIONS")
113
+ expect(BUILD_ENV_VARS).toContain("BABEL_ENV")
114
+ expect(BUILD_ENV_VARS).toContain("WEBPACK_SERVE")
115
+ expect(BUILD_ENV_VARS).toContain("CLIENT_BUNDLE_ONLY")
116
+ expect(BUILD_ENV_VARS).toContain("SERVER_BUNDLE_ONLY")
117
+ })
118
+
119
+ test("has expected length for BUILD_ENV_VARS", () => {
120
+ expect(BUILD_ENV_VARS).toHaveLength(7)
121
+ })
122
+
123
+ test("contains expected DANGEROUS_ENV_VARS variables", () => {
124
+ expect(DANGEROUS_ENV_VARS).toContain("PATH")
125
+ expect(DANGEROUS_ENV_VARS).toContain("HOME")
126
+ expect(DANGEROUS_ENV_VARS).toContain("LD_PRELOAD")
127
+ expect(DANGEROUS_ENV_VARS).toContain("LD_LIBRARY_PATH")
128
+ expect(DANGEROUS_ENV_VARS).toContain("DYLD_LIBRARY_PATH")
129
+ expect(DANGEROUS_ENV_VARS).toContain("DYLD_INSERT_LIBRARIES")
130
+ })
131
+
132
+ test("has expected length for DANGEROUS_ENV_VARS", () => {
133
+ expect(DANGEROUS_ENV_VARS).toHaveLength(6)
134
+ })
135
+
136
+ test("ensures no overlap between BUILD_ENV_VARS and DANGEROUS_ENV_VARS", () => {
137
+ const buildSet = new Set(BUILD_ENV_VARS)
138
+ const dangerousSet = new Set(DANGEROUS_ENV_VARS)
139
+
140
+ BUILD_ENV_VARS.forEach((v) => {
141
+ expect(dangerousSet.has(v)).toBe(false)
142
+ })
143
+
144
+ DANGEROUS_ENV_VARS.forEach((v) => {
145
+ expect(buildSet.has(v)).toBe(false)
146
+ })
147
+ })
148
+ })
149
+
150
+ describe("type predicate behavior", () => {
151
+ test("isBuildEnvVar returns true for valid key", () => {
152
+ const key = "NODE_ENV"
153
+ // Test that the predicate returns true for a valid key
154
+ expect(isBuildEnvVar(key)).toBe(true)
155
+ })
156
+
157
+ test("isDangerousEnvVar returns true for dangerous key", () => {
158
+ const key = "PATH"
159
+ // Test that the predicate returns true for a dangerous key
160
+ expect(isDangerousEnvVar(key)).toBe(true)
161
+ })
162
+ })
163
+ })