zip-writer 1.0.0 → 1.0.1

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 (45) hide show
  1. package/dist/constants.d.ts +21 -0
  2. package/dist/constants.d.ts.map +1 -0
  3. package/{src/constants.ts → dist/constants.js} +1 -5
  4. package/dist/constants.js.map +1 -0
  5. package/dist/crc-lookup.d.ts +6 -0
  6. package/dist/crc-lookup.d.ts.map +1 -0
  7. package/dist/crc-lookup.js +22 -0
  8. package/dist/crc-lookup.js.map +1 -0
  9. package/{src/crc.ts → dist/crc.d.ts} +2 -39
  10. package/dist/crc.d.ts.map +1 -0
  11. package/dist/crc.js +62 -0
  12. package/dist/crc.js.map +1 -0
  13. package/dist/index.d.ts +104 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +245 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/utils.d.ts +4 -0
  18. package/dist/utils.d.ts.map +1 -0
  19. package/dist/utils.js +12 -0
  20. package/dist/utils.js.map +1 -0
  21. package/dist/write-central-directory.d.ts +14 -0
  22. package/dist/write-central-directory.d.ts.map +1 -0
  23. package/dist/write-central-directory.js +279 -0
  24. package/dist/write-central-directory.js.map +1 -0
  25. package/dist/write-entry.d.ts +24 -0
  26. package/dist/write-entry.d.ts.map +1 -0
  27. package/dist/write-entry.js +173 -0
  28. package/dist/write-entry.js.map +1 -0
  29. package/package.json +12 -3
  30. package/.claude/settings.local.json +0 -11
  31. package/.github/workflows/test.yml +0 -36
  32. package/.husky/pre-commit +0 -1
  33. package/eslint.config.mjs +0 -9
  34. package/src/crc-lookup.ts +0 -27
  35. package/src/index.ts +0 -342
  36. package/src/utils.ts +0 -17
  37. package/src/write-central-directory.ts +0 -405
  38. package/src/write-entry.ts +0 -246
  39. package/test/commands.ts +0 -47
  40. package/test/crc.test.ts +0 -45
  41. package/test/integration.test.ts +0 -483
  42. package/test/utils.ts +0 -40
  43. package/tsconfig.json +0 -36
  44. package/tsconfig.node.json +0 -10
  45. package/vitest.config.ts +0 -46
package/test/crc.test.ts DELETED
@@ -1,45 +0,0 @@
1
- import { describe, it, assert } from "vitest";
2
- import { crc32 } from "../src/crc.js";
3
-
4
- describe("CRC32", () => {
5
- const testString =
6
- "abcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefgh";
7
- const expectedCrc = 0x6b64f5d;
8
- const encoder = new TextEncoder();
9
- const testBuffer = encoder.encode(testString);
10
-
11
- it("should calculate correct CRC when writing entire buffer at once", () => {
12
- const crc = crc32(testBuffer);
13
- assert.strictEqual(crc, expectedCrc);
14
- });
15
-
16
- it("should calculate correct CRC when writing in chunks > 16 bytes", () => {
17
- let crc = 0;
18
- const chunkSize = 20; // > 16
19
- for (let i = 0; i < testBuffer.length; i += chunkSize) {
20
- const chunk = testBuffer.slice(i, i + chunkSize);
21
- crc = crc32(chunk, crc);
22
- }
23
- assert.strictEqual(crc, expectedCrc);
24
- });
25
-
26
- it("should calculate correct CRC when writing in chunks < 16 bytes", () => {
27
- let crc = 0;
28
- const chunkSize = 10; // < 16
29
- for (let i = 0; i < testBuffer.length; i += chunkSize) {
30
- const chunk = testBuffer.slice(i, i + chunkSize);
31
- crc = crc32(chunk, crc);
32
- }
33
- assert.strictEqual(crc, expectedCrc);
34
- });
35
-
36
- it("should calculate correct CRC when writing in chunks of exactly 16 bytes", () => {
37
- let crc = 0;
38
- const chunkSize = 16; // = 16
39
- for (let i = 0; i < testBuffer.length; i += chunkSize) {
40
- const chunk = testBuffer.slice(i, i + chunkSize);
41
- crc = crc32(chunk, crc);
42
- }
43
- assert.strictEqual(crc, expectedCrc);
44
- });
45
- });
@@ -1,483 +0,0 @@
1
- import { describe, it, assert } from "vitest";
2
- import { ZipWriter } from "../src/index.js";
3
- import { validateZip } from "./utils.js";
4
-
5
- /**
6
- * Helper to collect a ReadableStream into a Uint8Array
7
- */
8
- async function collectStream(
9
- stream: ReadableStream<ArrayBufferView>
10
- ): Promise<Uint8Array> {
11
- const chunks: Uint8Array[] = [];
12
- const reader = stream.getReader();
13
-
14
- try {
15
- while (true) {
16
- const { done, value } = await reader.read();
17
- if (done) break;
18
- chunks.push(
19
- new Uint8Array(value.buffer, value.byteOffset, value.byteLength)
20
- );
21
- }
22
- } finally {
23
- reader.releaseLock();
24
- }
25
-
26
- const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
27
- const result = new Uint8Array(totalLength);
28
- let offset = 0;
29
- for (const chunk of chunks) {
30
- result.set(chunk, offset);
31
- offset += chunk.length;
32
- }
33
- return result;
34
- }
35
-
36
- /**
37
- * Calculate SHA256 hash of data using Web Crypto API (browser) or crypto module (Node)
38
- */
39
- async function sha256(data: Uint8Array): Promise<string> {
40
- if (typeof window !== "undefined" && window.crypto?.subtle) {
41
- // Browser: use Web Crypto API
42
- const hashBuffer = await crypto.subtle.digest(
43
- "SHA-256",
44
- data as BufferSource
45
- );
46
- const hashArray = Array.from(new Uint8Array(hashBuffer));
47
- return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
48
- } else {
49
- // Node: use crypto module
50
- const { createHash } = await import("crypto");
51
- return createHash("sha256").update(data).digest("hex");
52
- }
53
- }
54
-
55
- describe("ZIP Integration Tests", () => {
56
- describe("Single file archives", () => {
57
- it("should create a valid ZIP with one stored (uncompressed) file", async () => {
58
- const zipWriter = new ZipWriter();
59
- const fileName = "test.txt";
60
- const fileContent = "Hello, World!";
61
- const fileBytes = new TextEncoder().encode(fileContent);
62
-
63
- // Write entry
64
- const entryWriter = zipWriter.entry({ name: fileName, store: true });
65
- const writer = entryWriter.writable.getWriter();
66
- await writer.write(fileBytes);
67
- await writer.close();
68
-
69
- // Finalize and collect ZIP
70
- await zipWriter.finalize();
71
- const zipBuffer = await collectStream(zipWriter.readable);
72
-
73
- // Validate with yauzl via validateZip helper
74
- const entries = await validateZip(zipBuffer);
75
-
76
- assert.strictEqual(entries.length, 1, "Should have one entry");
77
- const entry = entries[0];
78
- assert.strictEqual(entry.filename, fileName, "Filename should match");
79
- assert.strictEqual(
80
- entry.uncompressedSize,
81
- fileBytes.length,
82
- "Uncompressed size should match"
83
- );
84
- assert.strictEqual(
85
- entry.compressionMethod,
86
- 0,
87
- "Should use store (0) compression"
88
- );
89
-
90
- // Verify content via SHA256
91
- const expectedHash = await sha256(fileBytes);
92
- assert.strictEqual(
93
- entry.sha256,
94
- expectedHash,
95
- "Content SHA256 should match"
96
- );
97
- });
98
-
99
- it("should create a valid ZIP with one deflated (compressed) file", async () => {
100
- const zipWriter = new ZipWriter();
101
- const fileName = "compressed.txt";
102
- const fileContent = "A".repeat(1000); // Repeating content compresses well
103
- const fileBytes = new TextEncoder().encode(fileContent);
104
-
105
- // Write entry with deflate (default)
106
- const entryWriter = zipWriter.entry({ name: fileName });
107
- const writer = entryWriter.writable.getWriter();
108
- await writer.write(fileBytes);
109
- await writer.close();
110
-
111
- // Finalize and collect ZIP
112
- await zipWriter.finalize();
113
- const zipBuffer = await collectStream(zipWriter.readable);
114
-
115
- // Validate with yauzl via validateZip helper
116
- const entries = await validateZip(zipBuffer);
117
-
118
- assert.strictEqual(entries.length, 1, "Should have one entry");
119
- const entry = entries[0];
120
- assert.strictEqual(entry.filename, fileName, "Filename should match");
121
- assert.strictEqual(
122
- entry.uncompressedSize,
123
- fileBytes.length,
124
- "Uncompressed size should match"
125
- );
126
- assert.strictEqual(
127
- entry.compressionMethod,
128
- 8,
129
- "Should use deflate (8) compression"
130
- );
131
- assert.isBelow(
132
- entry.compressedSize,
133
- entry.uncompressedSize,
134
- "Compressed size should be smaller"
135
- );
136
-
137
- // Verify content via SHA256
138
- const expectedHash = await sha256(fileBytes);
139
- assert.strictEqual(
140
- entry.sha256,
141
- expectedHash,
142
- "Content SHA256 should match after decompression"
143
- );
144
- });
145
- });
146
-
147
- describe("Multiple file archives", () => {
148
- it("should create a valid ZIP with multiple files", async () => {
149
- const zipWriter = new ZipWriter();
150
- const files = [
151
- { name: "file1.txt", content: "First file content" },
152
- { name: "file2.txt", content: "Second file content" },
153
- { name: "file3.txt", content: "Third file content" },
154
- ];
155
-
156
- // Write all entries
157
- for (const file of files) {
158
- const entryWriter = zipWriter.entry({ name: file.name, store: true });
159
- const writer = entryWriter.writable.getWriter();
160
- await writer.write(new TextEncoder().encode(file.content));
161
- await writer.close();
162
- }
163
-
164
- // Finalize and collect ZIP
165
- await zipWriter.finalize();
166
- const zipBuffer = await collectStream(zipWriter.readable);
167
-
168
- // Validate with yauzl via validateZip helper
169
- const entries = await validateZip(zipBuffer);
170
-
171
- assert.strictEqual(
172
- entries.length,
173
- files.length,
174
- `Should have ${files.length} entries`
175
- );
176
-
177
- // Verify each file
178
- for (let i = 0; i < files.length; i++) {
179
- const expectedFile = files[i];
180
- const entry = entries[i];
181
- const expectedBytes = new TextEncoder().encode(expectedFile.content);
182
-
183
- assert.strictEqual(
184
- entry.filename,
185
- expectedFile.name,
186
- `Entry ${i} filename should match`
187
- );
188
- const expectedHash = await sha256(expectedBytes);
189
- assert.strictEqual(
190
- entry.sha256,
191
- expectedHash,
192
- `Entry ${i} content should match`
193
- );
194
- }
195
- });
196
-
197
- it("should handle files with subdirectories", async () => {
198
- const zipWriter = new ZipWriter();
199
- const files = [
200
- { name: "root.txt", content: "Root file" },
201
- { name: "subdir/file1.txt", content: "Subdirectory file 1" },
202
- { name: "subdir/nested/file2.txt", content: "Nested file 2" },
203
- ];
204
-
205
- // Write all entries
206
- for (const file of files) {
207
- const entryWriter = zipWriter.entry({ name: file.name, store: true });
208
- const writer = entryWriter.writable.getWriter();
209
- await writer.write(new TextEncoder().encode(file.content));
210
- await writer.close();
211
- }
212
-
213
- // Finalize and collect ZIP
214
- await zipWriter.finalize();
215
- const zipBuffer = await collectStream(zipWriter.readable);
216
-
217
- // Validate with yauzl via validateZip helper
218
- const entries = await validateZip(zipBuffer);
219
-
220
- assert.strictEqual(
221
- entries.length,
222
- files.length,
223
- "Should have all entries"
224
- );
225
-
226
- // Verify paths are preserved correctly
227
- for (const file of files) {
228
- const entry = entries.find((e) => e.filename === file.name);
229
- assert.isDefined(entry, `Should find entry for ${file.name}`);
230
-
231
- const expectedBytes = new TextEncoder().encode(file.content);
232
- const expectedHash = await sha256(expectedBytes);
233
- assert.strictEqual(
234
- entry!.sha256,
235
- expectedHash,
236
- `Content for ${file.name} should match`
237
- );
238
- }
239
- });
240
- });
241
-
242
- describe("File metadata", () => {
243
- it("should preserve file modification dates", async () => {
244
- const zipWriter = new ZipWriter();
245
- const testDate = new Date("2024-06-15T12:00:00Z");
246
- const fileName = "dated-file.txt";
247
-
248
- const entryWriter = zipWriter.entry({
249
- name: fileName,
250
- store: true,
251
- date: testDate,
252
- });
253
- const writer = entryWriter.writable.getWriter();
254
- await writer.write(new TextEncoder().encode("Content"));
255
- await writer.close();
256
-
257
- await zipWriter.finalize();
258
- const zipBuffer = await collectStream(zipWriter.readable);
259
-
260
- // Validate with yauzl via validateZip helper
261
- const entries = await validateZip(zipBuffer);
262
-
263
- const entry = entries[0];
264
- assert.strictEqual(entry.filename, fileName);
265
- // Just verify that the entry exists with proper metadata
266
- assert.isAbove(entry.uncompressedSize, 0, "Should have content");
267
- });
268
-
269
- it("should handle file comments", async () => {
270
- const zipWriter = new ZipWriter();
271
- const fileName = "commented.txt";
272
- const fileComment = "This is a test comment";
273
-
274
- const entryWriter = zipWriter.entry({
275
- name: fileName,
276
- store: true,
277
- comment: fileComment,
278
- });
279
- const writer = entryWriter.writable.getWriter();
280
- await writer.write(new TextEncoder().encode("Content with comment"));
281
- await writer.close();
282
-
283
- await zipWriter.finalize();
284
- const zipBuffer = await collectStream(zipWriter.readable);
285
-
286
- // Validate with yauzl via validateZip helper
287
- const entries = await validateZip(zipBuffer);
288
-
289
- const entry = entries[0];
290
- assert.strictEqual(entry.filename, fileName);
291
- // Entry should exist - comment validation depends on reader implementation
292
- assert.isDefined(entry, "Entry should exist");
293
- });
294
-
295
- it("should handle Unix file permissions", async () => {
296
- const zipWriter = new ZipWriter();
297
- const fileName = "executable.sh";
298
- const fileMode = 0o755; // rwxr-xr-x
299
-
300
- const entryWriter = zipWriter.entry({
301
- name: fileName,
302
- store: true,
303
- mode: fileMode,
304
- });
305
- const writer = entryWriter.writable.getWriter();
306
- await writer.write(new TextEncoder().encode("#!/bin/bash\necho 'test'"));
307
- await writer.close();
308
-
309
- await zipWriter.finalize();
310
- const zipBuffer = await collectStream(zipWriter.readable);
311
-
312
- // Validate with yauzl via validateZip helper
313
- const entries = await validateZip(zipBuffer);
314
-
315
- const entry = entries[0];
316
- assert.strictEqual(entry.filename, fileName);
317
-
318
- // Extract Unix mode from external file attributes (upper 16 bits)
319
- const mode = (entry.externalFileAttributes >> 16) & 0xffff;
320
- assert.strictEqual(mode, fileMode, "Unix file mode should be preserved");
321
- });
322
- });
323
-
324
- describe("UTF-8 filename handling", () => {
325
- it("should handle UTF-8 filenames correctly", async () => {
326
- const zipWriter = new ZipWriter();
327
- const files = [
328
- { name: "test-文件.txt", content: "Chinese filename" },
329
- { name: "тест.txt", content: "Cyrillic filename" },
330
- { name: "δοκιμή.txt", content: "Greek filename" },
331
- { name: "テスト.txt", content: "Japanese filename" },
332
- ];
333
-
334
- // Write all entries
335
- for (const file of files) {
336
- const entryWriter = zipWriter.entry({ name: file.name, store: true });
337
- const writer = entryWriter.writable.getWriter();
338
- await writer.write(new TextEncoder().encode(file.content));
339
- await writer.close();
340
- }
341
-
342
- await zipWriter.finalize();
343
- const zipBuffer = await collectStream(zipWriter.readable);
344
-
345
- // Validate with yauzl via validateZip helper
346
- const entries = await validateZip(zipBuffer);
347
-
348
- assert.strictEqual(
349
- entries.length,
350
- files.length,
351
- "Should have all UTF-8 named files"
352
- );
353
-
354
- // Verify each filename and content
355
- for (const file of files) {
356
- const entry = entries.find((e) => e.filename === file.name);
357
- assert.isDefined(entry, `Should find entry for ${file.name}`);
358
-
359
- const expectedBytes = new TextEncoder().encode(file.content);
360
- const expectedHash = await sha256(expectedBytes);
361
- assert.strictEqual(
362
- entry!.sha256,
363
- expectedHash,
364
- `Content for ${file.name} should match`
365
- );
366
- }
367
- });
368
- });
369
-
370
- describe("Empty files and edge cases", () => {
371
- it("should handle empty files", async () => {
372
- const zipWriter = new ZipWriter();
373
- const fileName = "empty.txt";
374
-
375
- const entryWriter = zipWriter.entry({ name: fileName, store: true });
376
- const writer = entryWriter.writable.getWriter();
377
- await writer.close(); // Close without writing anything
378
-
379
- await zipWriter.finalize();
380
- const zipBuffer = await collectStream(zipWriter.readable);
381
-
382
- // Validate with yauzl via validateZip helper
383
- const entries = await validateZip(zipBuffer);
384
-
385
- const entry = entries[0];
386
- assert.strictEqual(entry.filename, fileName);
387
- assert.strictEqual(
388
- entry.uncompressedSize,
389
- 0,
390
- "Empty file should have 0 size"
391
- );
392
- assert.strictEqual(
393
- entry.compressedSize,
394
- 0,
395
- "Empty file compressed size should be 0"
396
- );
397
-
398
- // SHA256 of empty buffer
399
- const expectedHash = await sha256(new Uint8Array(0));
400
- assert.strictEqual(entry.sha256, expectedHash, "Content should be empty");
401
- });
402
-
403
- it("should handle very long filenames", async () => {
404
- const zipWriter = new ZipWriter();
405
- // ZIP format supports filenames up to 65535 bytes
406
- const longName = "a".repeat(200) + "/b".repeat(200) + "/file.txt";
407
-
408
- const entryWriter = zipWriter.entry({ name: longName, store: true });
409
- const writer = entryWriter.writable.getWriter();
410
- await writer.write(new TextEncoder().encode("Content"));
411
- await writer.close();
412
-
413
- await zipWriter.finalize();
414
- const zipBuffer = await collectStream(zipWriter.readable);
415
-
416
- // Validate with yauzl via validateZip helper
417
- const entries = await validateZip(zipBuffer);
418
-
419
- const entry = entries[0];
420
- assert.strictEqual(
421
- entry.filename,
422
- longName,
423
- "Long filename should be preserved"
424
- );
425
- });
426
- });
427
-
428
- describe("Mixed compression methods", () => {
429
- it("should handle mix of stored and deflated files in same archive", async () => {
430
- const zipWriter = new ZipWriter();
431
- const files = [
432
- { name: "stored.txt", content: "Stored content", store: true },
433
- { name: "deflated.txt", content: "Deflated content", store: false },
434
- { name: "stored2.txt", content: "Another stored", store: true },
435
- ];
436
-
437
- // Write all entries
438
- for (const file of files) {
439
- const entryWriter = zipWriter.entry({
440
- name: file.name,
441
- store: file.store,
442
- });
443
- const writer = entryWriter.writable.getWriter();
444
- await writer.write(new TextEncoder().encode(file.content));
445
- await writer.close();
446
- }
447
-
448
- await zipWriter.finalize();
449
- const zipBuffer = await collectStream(zipWriter.readable);
450
-
451
- // Validate with yauzl via validateZip helper
452
- const entries = await validateZip(zipBuffer);
453
-
454
- assert.strictEqual(
455
- entries.length,
456
- files.length,
457
- "Should have all entries"
458
- );
459
-
460
- // Verify compression methods
461
- for (let i = 0; i < files.length; i++) {
462
- const expectedFile = files[i];
463
- const entry = entries[i];
464
-
465
- assert.strictEqual(entry.filename, expectedFile.name);
466
- const expectedMethod = expectedFile.store ? 0 : 8;
467
- assert.strictEqual(
468
- entry.compressionMethod,
469
- expectedMethod,
470
- `${expectedFile.name} should use correct compression method`
471
- );
472
-
473
- const expectedBytes = new TextEncoder().encode(expectedFile.content);
474
- const expectedHash = await sha256(expectedBytes);
475
- assert.strictEqual(
476
- entry.sha256,
477
- expectedHash,
478
- `Content for ${expectedFile.name} should match`
479
- );
480
- }
481
- });
482
- });
483
- });
package/test/utils.ts DELETED
@@ -1,40 +0,0 @@
1
- // Shim for validateZip that works in both Node and browser contexts
2
-
3
- export interface ZipEntryInfo {
4
- filename: string;
5
- uncompressedSize: number;
6
- compressedSize: number;
7
- compressionMethod: number;
8
- sha256: string;
9
- isDirectory: boolean;
10
- externalFileAttributes: number;
11
- }
12
-
13
- /**
14
- * Convert Uint8Array to hex string
15
- */
16
- function toHex(buffer: Uint8Array): string {
17
- return Array.from(buffer)
18
- .map((b) => b.toString(16).padStart(2, "0"))
19
- .join("");
20
- }
21
-
22
- // In Node context, import the command directly
23
- // In browser context, use vitest browser commands
24
- export async function validateZip(
25
- zipBuffer: Uint8Array
26
- ): Promise<ZipEntryInfo[]> {
27
- const hexString = toHex(zipBuffer);
28
-
29
- // Check if we're in a browser context
30
- if (typeof window !== "undefined") {
31
- // Browser: use vitest browser commands
32
- const { commands } = await import("vitest/browser");
33
- return commands.validateZip(hexString);
34
- } else {
35
- // Node: use the command directly
36
- const { validateZip: validateZipCommand } = await import("./commands.js");
37
- // Call the command with a mock context
38
- return validateZipCommand({} as any, hexString);
39
- }
40
- }
package/tsconfig.json DELETED
@@ -1,36 +0,0 @@
1
- {
2
- // Visit https://aka.ms/tsconfig to read more about this file
3
- "compilerOptions": {
4
- // File Layout
5
- "rootDir": "./src",
6
- "outDir": "./dist",
7
-
8
- // Environment Settings
9
- // See also https://aka.ms/tsconfig/module
10
- "module": "nodenext",
11
- "target": "ES2022",
12
- // Set to empty to avoid loading node types automatically
13
- "typeRoots": [],
14
- // For nodejs:
15
- // "types": ["node"],
16
- // and npm install -D @types/node
17
-
18
- // Other Outputs
19
- "sourceMap": true,
20
- "declaration": true,
21
- "declarationMap": true,
22
-
23
- // Stricter Typechecking Options
24
- // "noUncheckedIndexedAccess": true,
25
- "exactOptionalPropertyTypes": true,
26
-
27
- // Recommended Options
28
- "strict": true,
29
- "verbatimModuleSyntax": true,
30
- "isolatedModules": true,
31
- "noUncheckedSideEffectImports": true,
32
- "moduleDetection": "force",
33
- "skipLibCheck": true
34
- },
35
- "include": ["src/**/*.ts"]
36
- }
@@ -1,10 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "compilerOptions": {
4
- "types": ["node"],
5
- "typeRoots": ["./node_modules/@types"],
6
- "noEmit": true,
7
- "rootDir": "."
8
- },
9
- "include": ["vitest.config.ts", "test/**/*.ts"]
10
- }
package/vitest.config.ts DELETED
@@ -1,46 +0,0 @@
1
- import { defineConfig } from "vitest/config";
2
- import { playwright } from "@vitest/browser-playwright";
3
- import type { BrowserInstanceOption } from "vitest/node";
4
- import { validateZip } from "./test/commands.js";
5
-
6
- const browserInstances: BrowserInstanceOption[] = [
7
- { browser: "chromium" },
8
- { browser: "firefox" },
9
- ];
10
-
11
- if (process.platform === "darwin") {
12
- browserInstances.push({ browser: "webkit" });
13
- }
14
-
15
- export default defineConfig({
16
- test: {
17
- include: ["test/**/*.test.ts"],
18
- projects: [
19
- {
20
- test: {
21
- name: "node",
22
- environment: "node",
23
- },
24
- },
25
- {
26
- optimizeDeps: {
27
- exclude: ["yauzl-promise"],
28
- },
29
- test: {
30
- name: "browser",
31
- browser: {
32
- ui: false,
33
- screenshotFailures: false,
34
- enabled: true,
35
- headless: true,
36
- provider: playwright(),
37
- instances: browserInstances,
38
- commands: {
39
- validateZip,
40
- },
41
- },
42
- },
43
- },
44
- ],
45
- },
46
- });