@cj-tech-master/excelts 1.4.3 → 1.4.5

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 (54) hide show
  1. package/README.md +3 -3
  2. package/README_zh.md +3 -3
  3. package/dist/browser/excelts.iife.js +12841 -7484
  4. package/dist/browser/excelts.iife.js.map +1 -1
  5. package/dist/browser/excelts.iife.min.js +86 -23
  6. package/dist/cjs/doc/column.js +1 -1
  7. package/dist/cjs/doc/row.js +9 -4
  8. package/dist/cjs/doc/worksheet.js +9 -4
  9. package/dist/cjs/stream/xlsx/workbook-writer.js +3 -2
  10. package/dist/cjs/utils/unzip/extract.js +166 -0
  11. package/dist/cjs/utils/unzip/index.js +7 -1
  12. package/dist/cjs/utils/xml-stream.js +25 -3
  13. package/dist/cjs/utils/zip/compress.js +261 -0
  14. package/dist/cjs/utils/zip/crc32.js +154 -0
  15. package/dist/cjs/utils/zip/index.js +70 -0
  16. package/dist/cjs/utils/zip/zip-builder.js +378 -0
  17. package/dist/cjs/utils/zip-stream.js +30 -34
  18. package/dist/cjs/xlsx/xform/book/defined-name-xform.js +36 -2
  19. package/dist/cjs/xlsx/xform/list-xform.js +6 -0
  20. package/dist/cjs/xlsx/xform/sheet/cell-xform.js +6 -1
  21. package/dist/cjs/xlsx/xform/sheet/row-xform.js +24 -2
  22. package/dist/cjs/xlsx/xform/table/filter-column-xform.js +4 -0
  23. package/dist/esm/doc/column.js +1 -1
  24. package/dist/esm/doc/row.js +9 -4
  25. package/dist/esm/doc/worksheet.js +9 -4
  26. package/dist/esm/stream/xlsx/workbook-writer.js +3 -2
  27. package/dist/esm/utils/unzip/extract.js +160 -0
  28. package/dist/esm/utils/unzip/index.js +2 -0
  29. package/dist/esm/utils/xml-stream.js +25 -3
  30. package/dist/esm/utils/zip/compress.js +220 -0
  31. package/dist/esm/utils/zip/crc32.js +116 -0
  32. package/dist/esm/utils/zip/index.js +55 -0
  33. package/dist/esm/utils/zip/zip-builder.js +372 -0
  34. package/dist/esm/utils/zip-stream.js +30 -34
  35. package/dist/esm/xlsx/xform/book/defined-name-xform.js +36 -2
  36. package/dist/esm/xlsx/xform/list-xform.js +6 -0
  37. package/dist/esm/xlsx/xform/sheet/cell-xform.js +6 -1
  38. package/dist/esm/xlsx/xform/sheet/row-xform.js +24 -2
  39. package/dist/esm/xlsx/xform/table/filter-column-xform.js +4 -0
  40. package/dist/types/doc/cell.d.ts +10 -6
  41. package/dist/types/doc/column.d.ts +8 -4
  42. package/dist/types/doc/row.d.ts +9 -8
  43. package/dist/types/doc/worksheet.d.ts +2 -2
  44. package/dist/types/utils/unzip/extract.d.ts +92 -0
  45. package/dist/types/utils/unzip/index.d.ts +1 -0
  46. package/dist/types/utils/xml-stream.d.ts +2 -0
  47. package/dist/types/utils/zip/compress.d.ts +83 -0
  48. package/dist/types/utils/zip/crc32.d.ts +55 -0
  49. package/dist/types/utils/zip/index.d.ts +52 -0
  50. package/dist/types/utils/zip/zip-builder.d.ts +110 -0
  51. package/dist/types/utils/zip-stream.d.ts +6 -12
  52. package/dist/types/xlsx/xform/list-xform.d.ts +1 -0
  53. package/dist/types/xlsx/xform/sheet/row-xform.d.ts +2 -0
  54. package/package.json +8 -8
@@ -0,0 +1,154 @@
1
+ "use strict";
2
+ /**
3
+ * CRC32 calculation utility for ZIP files
4
+ *
5
+ * - Node.js: Uses native zlib.crc32 (C++ implementation, ~100x faster)
6
+ * - Browser: Uses lookup table optimization
7
+ *
8
+ * The polynomial used is the standard CRC-32 IEEE 802.3:
9
+ * x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 + x^10 + x^8 + x^7 + x^5 + x^4 + x^2 + x + 1
10
+ * Represented as 0xEDB88320 in reversed (LSB-first) form
11
+ */
12
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ var desc = Object.getOwnPropertyDescriptor(m, k);
15
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
16
+ desc = { enumerable: true, get: function() { return m[k]; } };
17
+ }
18
+ Object.defineProperty(o, k2, desc);
19
+ }) : (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ o[k2] = m[k];
22
+ }));
23
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
24
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
25
+ }) : function(o, v) {
26
+ o["default"] = v;
27
+ });
28
+ var __importStar = (this && this.__importStar) || (function () {
29
+ var ownKeys = function(o) {
30
+ ownKeys = Object.getOwnPropertyNames || function (o) {
31
+ var ar = [];
32
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
33
+ return ar;
34
+ };
35
+ return ownKeys(o);
36
+ };
37
+ return function (mod) {
38
+ if (mod && mod.__esModule) return mod;
39
+ var result = {};
40
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
41
+ __setModuleDefault(result, mod);
42
+ return result;
43
+ };
44
+ })();
45
+ Object.defineProperty(exports, "__esModule", { value: true });
46
+ exports.crc32 = crc32;
47
+ exports.ensureCrc32 = ensureCrc32;
48
+ exports.crc32Update = crc32Update;
49
+ exports.crc32Finalize = crc32Finalize;
50
+ // Detect Node.js environment
51
+ const isNode = typeof process !== "undefined" && process.versions?.node;
52
+ // Lazy-loaded zlib module for Node.js
53
+ let _zlib = null;
54
+ let _zlibLoading = null;
55
+ // Auto-initialize zlib in Node.js environment
56
+ if (isNode) {
57
+ _zlibLoading = Promise.resolve().then(() => __importStar(require("zlib"))).then(module => {
58
+ _zlib = module.default ?? module;
59
+ return _zlib;
60
+ })
61
+ .catch(() => {
62
+ _zlib = null;
63
+ return null;
64
+ });
65
+ }
66
+ /**
67
+ * Pre-computed CRC32 lookup table (256 entries)
68
+ * Generated using the standard polynomial 0xEDB88320
69
+ * Used as fallback when native zlib is not available
70
+ */
71
+ const CRC32_TABLE = /* @__PURE__ */ (() => {
72
+ const table = new Uint32Array(256);
73
+ for (let i = 0; i < 256; i++) {
74
+ let crc = i;
75
+ for (let j = 0; j < 8; j++) {
76
+ crc = crc & 1 ? 0xedb88320 ^ (crc >>> 1) : crc >>> 1;
77
+ }
78
+ table[i] = crc;
79
+ }
80
+ return table;
81
+ })();
82
+ /**
83
+ * JavaScript fallback CRC32 implementation using lookup table
84
+ */
85
+ function crc32JS(data) {
86
+ let crc = 0xffffffff;
87
+ for (let i = 0; i < data.length; i++) {
88
+ crc = CRC32_TABLE[(crc ^ data[i]) & 0xff] ^ (crc >>> 8);
89
+ }
90
+ return (crc ^ 0xffffffff) >>> 0;
91
+ }
92
+ /**
93
+ * Calculate CRC32 checksum for the given data
94
+ * Uses native zlib.crc32 in Node.js for ~100x better performance
95
+ *
96
+ * @param data - Input data as Uint8Array or Buffer
97
+ * @returns CRC32 checksum as unsigned 32-bit integer
98
+ *
99
+ * @example
100
+ * ```ts
101
+ * const data = new TextEncoder().encode("Hello, World!");
102
+ * const checksum = crc32(data);
103
+ * console.log(checksum.toString(16)); // "ec4ac3d0"
104
+ * ```
105
+ */
106
+ function crc32(data) {
107
+ // Use native zlib.crc32 if available (Node.js)
108
+ if (_zlib && typeof _zlib.crc32 === "function") {
109
+ return _zlib.crc32(data) >>> 0;
110
+ }
111
+ // Fallback to JS implementation
112
+ return crc32JS(data);
113
+ }
114
+ /**
115
+ * Ensure zlib is loaded (for use before calling crc32)
116
+ */
117
+ async function ensureCrc32() {
118
+ if (_zlibLoading) {
119
+ await _zlibLoading;
120
+ }
121
+ }
122
+ /**
123
+ * Calculate CRC32 incrementally (useful for streaming)
124
+ * Call with initial crc of 0xffffffff, then finalize with crc32Finalize
125
+ * Note: This always uses JS implementation for consistency in streaming
126
+ *
127
+ * @param crc - Current CRC value (start with 0xffffffff)
128
+ * @param data - Input data chunk
129
+ * @returns Updated CRC value (not finalized)
130
+ *
131
+ * @example
132
+ * ```ts
133
+ * let crc = 0xffffffff;
134
+ * crc = crc32Update(crc, chunk1);
135
+ * crc = crc32Update(crc, chunk2);
136
+ * const checksum = crc32Finalize(crc);
137
+ * ```
138
+ */
139
+ function crc32Update(crc, data) {
140
+ for (let i = 0; i < data.length; i++) {
141
+ crc = CRC32_TABLE[(crc ^ data[i]) & 0xff] ^ (crc >>> 8);
142
+ }
143
+ return crc;
144
+ }
145
+ /**
146
+ * Finalize CRC32 calculation
147
+ * XOR with 0xffffffff and convert to unsigned 32-bit
148
+ *
149
+ * @param crc - CRC value from crc32Update
150
+ * @returns Final CRC32 checksum
151
+ */
152
+ function crc32Finalize(crc) {
153
+ return (crc ^ 0xffffffff) >>> 0;
154
+ }
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ /**
3
+ * Native ZIP utilities - Pure native implementation without third-party dependencies
4
+ *
5
+ * This module provides ZIP file creation using only native platform APIs:
6
+ * - Node.js: Uses native zlib module (C++ implementation, fastest)
7
+ * - Browser: Uses CompressionStream API (Chrome 80+, Firefox 113+, Safari 16.4+)
8
+ *
9
+ * Features:
10
+ * - Full ZIP format support (Local File Headers, Central Directory, EOCD)
11
+ * - DEFLATE compression (level 0-9 on Node.js, fixed level on browser)
12
+ * - STORE mode (no compression)
13
+ * - UTF-8 filename support
14
+ * - File comments and ZIP comments
15
+ * - Streaming API for large files
16
+ * - Both sync (Node.js) and async APIs
17
+ *
18
+ * @example Basic usage
19
+ * ```ts
20
+ * import { createZip } from "./utils/zip/index.js";
21
+ *
22
+ * const zipData = await createZip([
23
+ * { name: "hello.txt", data: new TextEncoder().encode("Hello!") },
24
+ * { name: "folder/nested.txt", data: new TextEncoder().encode("Nested file") }
25
+ * ], { level: 6 });
26
+ *
27
+ * // Write to file (Node.js)
28
+ * fs.writeFileSync("output.zip", zipData);
29
+ * ```
30
+ *
31
+ * @example Streaming usage
32
+ * ```ts
33
+ * import { ZipBuilder } from "./utils/zip/index.js";
34
+ *
35
+ * const builder = new ZipBuilder({ level: 1 });
36
+ *
37
+ * // Add files one by one
38
+ * const [header1, data1] = await builder.addFile({
39
+ * name: "file1.txt",
40
+ * data: new TextEncoder().encode("File 1 content")
41
+ * });
42
+ * stream.write(header1);
43
+ * stream.write(data1);
44
+ *
45
+ * // Finalize and write central directory
46
+ * for (const chunk of builder.finalize()) {
47
+ * stream.write(chunk);
48
+ * }
49
+ * ```
50
+ */
51
+ Object.defineProperty(exports, "__esModule", { value: true });
52
+ exports.ZipBuilder = exports.createZipSync = exports.createZip = exports.hasCompressionStream = exports.hasNativeZlib = exports.decompressSync = exports.decompress = exports.compressSync = exports.compress = exports.crc32Finalize = exports.crc32Update = exports.crc32 = void 0;
53
+ // CRC32 utilities
54
+ var crc32_js_1 = require("./crc32");
55
+ Object.defineProperty(exports, "crc32", { enumerable: true, get: function () { return crc32_js_1.crc32; } });
56
+ Object.defineProperty(exports, "crc32Update", { enumerable: true, get: function () { return crc32_js_1.crc32Update; } });
57
+ Object.defineProperty(exports, "crc32Finalize", { enumerable: true, get: function () { return crc32_js_1.crc32Finalize; } });
58
+ // Compression utilities
59
+ var compress_js_1 = require("./compress");
60
+ Object.defineProperty(exports, "compress", { enumerable: true, get: function () { return compress_js_1.compress; } });
61
+ Object.defineProperty(exports, "compressSync", { enumerable: true, get: function () { return compress_js_1.compressSync; } });
62
+ Object.defineProperty(exports, "decompress", { enumerable: true, get: function () { return compress_js_1.decompress; } });
63
+ Object.defineProperty(exports, "decompressSync", { enumerable: true, get: function () { return compress_js_1.decompressSync; } });
64
+ Object.defineProperty(exports, "hasNativeZlib", { enumerable: true, get: function () { return compress_js_1.hasNativeZlib; } });
65
+ Object.defineProperty(exports, "hasCompressionStream", { enumerable: true, get: function () { return compress_js_1.hasCompressionStream; } });
66
+ // ZIP builder
67
+ var zip_builder_js_1 = require("./zip-builder");
68
+ Object.defineProperty(exports, "createZip", { enumerable: true, get: function () { return zip_builder_js_1.createZip; } });
69
+ Object.defineProperty(exports, "createZipSync", { enumerable: true, get: function () { return zip_builder_js_1.createZipSync; } });
70
+ Object.defineProperty(exports, "ZipBuilder", { enumerable: true, get: function () { return zip_builder_js_1.ZipBuilder; } });
@@ -0,0 +1,378 @@
1
+ "use strict";
2
+ /**
3
+ * ZIP file format builder
4
+ *
5
+ * Implements ZIP file structure according to PKWARE's APPNOTE.TXT specification
6
+ * https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
7
+ *
8
+ * ZIP file structure:
9
+ * ┌──────────────────────────┐
10
+ * │ Local File Header 1 │
11
+ * │ File Data 1 │
12
+ * ├──────────────────────────┤
13
+ * │ Local File Header 2 │
14
+ * │ File Data 2 │
15
+ * ├──────────────────────────┤
16
+ * │ ... │
17
+ * ├──────────────────────────┤
18
+ * │ Central Directory 1 │
19
+ * │ Central Directory 2 │
20
+ * │ ... │
21
+ * ├──────────────────────────┤
22
+ * │ End of Central Directory │
23
+ * └──────────────────────────┘
24
+ */
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.ZipBuilder = void 0;
27
+ exports.createZip = createZip;
28
+ exports.createZipSync = createZipSync;
29
+ const crc32_js_1 = require("./crc32");
30
+ const compress_js_1 = require("./compress");
31
+ // ZIP signature constants
32
+ const LOCAL_FILE_HEADER_SIG = 0x04034b50;
33
+ const CENTRAL_DIR_HEADER_SIG = 0x02014b50;
34
+ const END_OF_CENTRAL_DIR_SIG = 0x06054b50;
35
+ // ZIP version constants
36
+ const VERSION_NEEDED = 20; // 2.0 - supports DEFLATE
37
+ const VERSION_MADE_BY = 20; // 2.0
38
+ // Compression methods
39
+ const COMPRESSION_STORE = 0;
40
+ const COMPRESSION_DEFLATE = 8;
41
+ /**
42
+ * Convert Date to DOS time format
43
+ * @param date - Date to convert
44
+ * @returns [dosTime, dosDate]
45
+ */
46
+ function dateToDos(date) {
47
+ const dosTime = ((date.getHours() & 0x1f) << 11) |
48
+ ((date.getMinutes() & 0x3f) << 5) |
49
+ ((date.getSeconds() >> 1) & 0x1f);
50
+ const dosDate = (((date.getFullYear() - 1980) & 0x7f) << 9) |
51
+ (((date.getMonth() + 1) & 0x0f) << 5) |
52
+ (date.getDate() & 0x1f);
53
+ return [dosTime, dosDate];
54
+ }
55
+ /**
56
+ * Encode string to UTF-8 bytes
57
+ */
58
+ const encoder = new TextEncoder();
59
+ function encodeString(str) {
60
+ return encoder.encode(str);
61
+ }
62
+ /**
63
+ * Build Local File Header (30 bytes + filename + extra)
64
+ */
65
+ function buildLocalFileHeader(entry) {
66
+ const header = new Uint8Array(30 + entry.name.length);
67
+ const view = new DataView(header.buffer);
68
+ view.setUint32(0, LOCAL_FILE_HEADER_SIG, true); // Signature
69
+ view.setUint16(4, VERSION_NEEDED, true); // Version needed to extract
70
+ view.setUint16(6, 0x0800, true); // General purpose bit flag (UTF-8 names)
71
+ view.setUint16(8, entry.compressionMethod, true); // Compression method
72
+ view.setUint16(10, entry.modTime, true); // Last mod time
73
+ view.setUint16(12, entry.modDate, true); // Last mod date
74
+ view.setUint32(14, entry.crc, true); // CRC-32
75
+ view.setUint32(18, entry.compressedData.length, true); // Compressed size
76
+ view.setUint32(22, entry.data.length, true); // Uncompressed size
77
+ view.setUint16(26, entry.name.length, true); // Filename length
78
+ view.setUint16(28, 0, true); // Extra field length
79
+ header.set(entry.name, 30);
80
+ return header;
81
+ }
82
+ /**
83
+ * Build Central Directory Header (46 bytes + filename + extra + comment)
84
+ */
85
+ function buildCentralDirHeader(entry) {
86
+ const header = new Uint8Array(46 + entry.name.length + entry.comment.length);
87
+ const view = new DataView(header.buffer);
88
+ view.setUint32(0, CENTRAL_DIR_HEADER_SIG, true); // Signature
89
+ view.setUint16(4, VERSION_MADE_BY, true); // Version made by
90
+ view.setUint16(6, VERSION_NEEDED, true); // Version needed to extract
91
+ view.setUint16(8, 0x0800, true); // General purpose bit flag (UTF-8 names)
92
+ view.setUint16(10, entry.compressionMethod, true); // Compression method
93
+ view.setUint16(12, entry.modTime, true); // Last mod time
94
+ view.setUint16(14, entry.modDate, true); // Last mod date
95
+ view.setUint32(16, entry.crc, true); // CRC-32
96
+ view.setUint32(20, entry.compressedData.length, true); // Compressed size
97
+ view.setUint32(24, entry.data.length, true); // Uncompressed size
98
+ view.setUint16(28, entry.name.length, true); // Filename length
99
+ view.setUint16(30, 0, true); // Extra field length
100
+ view.setUint16(32, entry.comment.length, true); // Comment length
101
+ view.setUint16(34, 0, true); // Disk number start
102
+ view.setUint16(36, 0, true); // Internal file attributes
103
+ view.setUint32(38, 0, true); // External file attributes
104
+ view.setUint32(42, entry.offset, true); // Relative offset of local header
105
+ header.set(entry.name, 46);
106
+ if (entry.comment.length > 0) {
107
+ header.set(entry.comment, 46 + entry.name.length);
108
+ }
109
+ return header;
110
+ }
111
+ /**
112
+ * Build End of Central Directory Record (22 bytes + comment)
113
+ */
114
+ function buildEndOfCentralDir(entryCount, centralDirSize, centralDirOffset, comment) {
115
+ const record = new Uint8Array(22 + comment.length);
116
+ const view = new DataView(record.buffer);
117
+ view.setUint32(0, END_OF_CENTRAL_DIR_SIG, true); // Signature
118
+ view.setUint16(4, 0, true); // Number of this disk
119
+ view.setUint16(6, 0, true); // Disk where central dir starts
120
+ view.setUint16(8, entryCount, true); // Number of entries on this disk
121
+ view.setUint16(10, entryCount, true); // Total number of entries
122
+ view.setUint32(12, centralDirSize, true); // Size of central directory
123
+ view.setUint32(16, centralDirOffset, true); // Offset of central directory
124
+ view.setUint16(20, comment.length, true); // Comment length
125
+ if (comment.length > 0) {
126
+ record.set(comment, 22);
127
+ }
128
+ return record;
129
+ }
130
+ /**
131
+ * Create a ZIP file from entries (async)
132
+ *
133
+ * @param entries - Files to include in ZIP
134
+ * @param options - ZIP options
135
+ * @returns ZIP file as Uint8Array
136
+ *
137
+ * @example
138
+ * ```ts
139
+ * const zip = await createZip([
140
+ * { name: "hello.txt", data: new TextEncoder().encode("Hello!") },
141
+ * { name: "folder/file.txt", data: new TextEncoder().encode("Nested!") }
142
+ * ], { level: 6 });
143
+ * ```
144
+ */
145
+ async function createZip(entries, options = {}) {
146
+ const level = options.level ?? 6;
147
+ const zipComment = encodeString(options.comment ?? "");
148
+ const now = new Date();
149
+ // Process entries
150
+ const processedEntries = [];
151
+ let currentOffset = 0;
152
+ for (const entry of entries) {
153
+ const nameBytes = encodeString(entry.name);
154
+ const commentBytes = encodeString(entry.comment ?? "");
155
+ const modDate = entry.modTime ?? now;
156
+ const [dosTime, dosDate] = dateToDos(modDate);
157
+ // Compress data
158
+ const isCompressed = level > 0 && entry.data.length > 0;
159
+ const compressedData = isCompressed ? await (0, compress_js_1.compress)(entry.data, { level }) : entry.data;
160
+ const processedEntry = {
161
+ name: nameBytes,
162
+ data: entry.data,
163
+ compressedData,
164
+ crc: (0, crc32_js_1.crc32)(entry.data),
165
+ compressionMethod: isCompressed ? COMPRESSION_DEFLATE : COMPRESSION_STORE,
166
+ modTime: dosTime,
167
+ modDate: dosDate,
168
+ comment: commentBytes,
169
+ offset: currentOffset
170
+ };
171
+ // Calculate offset for next entry
172
+ currentOffset += 30 + nameBytes.length + compressedData.length;
173
+ processedEntries.push(processedEntry);
174
+ }
175
+ // Build ZIP structure
176
+ const chunks = [];
177
+ // Local file headers and data
178
+ for (const entry of processedEntries) {
179
+ chunks.push(buildLocalFileHeader(entry));
180
+ chunks.push(entry.compressedData);
181
+ }
182
+ const centralDirOffset = currentOffset;
183
+ // Central directory
184
+ const centralDirChunks = [];
185
+ for (const entry of processedEntries) {
186
+ centralDirChunks.push(buildCentralDirHeader(entry));
187
+ }
188
+ chunks.push(...centralDirChunks);
189
+ const centralDirSize = centralDirChunks.reduce((sum, c) => sum + c.length, 0);
190
+ // End of central directory
191
+ chunks.push(buildEndOfCentralDir(processedEntries.length, centralDirSize, centralDirOffset, zipComment));
192
+ // Combine all chunks
193
+ const totalSize = chunks.reduce((sum, c) => sum + c.length, 0);
194
+ const result = new Uint8Array(totalSize);
195
+ let offset = 0;
196
+ for (const chunk of chunks) {
197
+ result.set(chunk, offset);
198
+ offset += chunk.length;
199
+ }
200
+ return result;
201
+ }
202
+ /**
203
+ * Create a ZIP file from entries (sync, Node.js only)
204
+ *
205
+ * @param entries - Files to include in ZIP
206
+ * @param options - ZIP options
207
+ * @returns ZIP file as Uint8Array
208
+ * @throws Error if not in Node.js environment
209
+ */
210
+ function createZipSync(entries, options = {}) {
211
+ const level = options.level ?? 6;
212
+ const zipComment = encodeString(options.comment ?? "");
213
+ const now = new Date();
214
+ // Process entries
215
+ const processedEntries = [];
216
+ let currentOffset = 0;
217
+ for (const entry of entries) {
218
+ const nameBytes = encodeString(entry.name);
219
+ const commentBytes = encodeString(entry.comment ?? "");
220
+ const modDate = entry.modTime ?? now;
221
+ const [dosTime, dosDate] = dateToDos(modDate);
222
+ // Compress data
223
+ const isCompressed = level > 0 && entry.data.length > 0;
224
+ const compressedData = isCompressed ? (0, compress_js_1.compressSync)(entry.data, { level }) : entry.data;
225
+ const processedEntry = {
226
+ name: nameBytes,
227
+ data: entry.data,
228
+ compressedData,
229
+ crc: (0, crc32_js_1.crc32)(entry.data),
230
+ compressionMethod: isCompressed ? COMPRESSION_DEFLATE : COMPRESSION_STORE,
231
+ modTime: dosTime,
232
+ modDate: dosDate,
233
+ comment: commentBytes,
234
+ offset: currentOffset
235
+ };
236
+ currentOffset += 30 + nameBytes.length + compressedData.length;
237
+ processedEntries.push(processedEntry);
238
+ }
239
+ // Build ZIP structure
240
+ const chunks = [];
241
+ // Local file headers and data
242
+ for (const entry of processedEntries) {
243
+ chunks.push(buildLocalFileHeader(entry));
244
+ chunks.push(entry.compressedData);
245
+ }
246
+ const centralDirOffset = currentOffset;
247
+ // Central directory
248
+ const centralDirChunks = [];
249
+ for (const entry of processedEntries) {
250
+ centralDirChunks.push(buildCentralDirHeader(entry));
251
+ }
252
+ chunks.push(...centralDirChunks);
253
+ const centralDirSize = centralDirChunks.reduce((sum, c) => sum + c.length, 0);
254
+ // End of central directory
255
+ chunks.push(buildEndOfCentralDir(processedEntries.length, centralDirSize, centralDirOffset, zipComment));
256
+ // Combine all chunks
257
+ const totalSize = chunks.reduce((sum, c) => sum + c.length, 0);
258
+ const result = new Uint8Array(totalSize);
259
+ let offset = 0;
260
+ for (const chunk of chunks) {
261
+ result.set(chunk, offset);
262
+ offset += chunk.length;
263
+ }
264
+ return result;
265
+ }
266
+ /**
267
+ * Streaming ZIP builder for large files
268
+ * Writes chunks to a callback as they are generated
269
+ */
270
+ class ZipBuilder {
271
+ /**
272
+ * Create a new ZIP builder
273
+ * @param options - ZIP options
274
+ */
275
+ constructor(options = {}) {
276
+ this.entries = [];
277
+ this.currentOffset = 0;
278
+ this.finalized = false;
279
+ this.level = options.level ?? 6;
280
+ this.zipComment = encodeString(options.comment ?? "");
281
+ }
282
+ /**
283
+ * Add a file to the ZIP (async)
284
+ * @param entry - File entry
285
+ * @returns Local file header and compressed data chunks
286
+ */
287
+ async addFile(entry) {
288
+ if (this.finalized) {
289
+ throw new Error("Cannot add files after finalizing");
290
+ }
291
+ const nameBytes = encodeString(entry.name);
292
+ const commentBytes = encodeString(entry.comment ?? "");
293
+ const [dosTime, dosDate] = dateToDos(entry.modTime ?? new Date());
294
+ // Compress data
295
+ const isCompressed = this.level > 0 && entry.data.length > 0;
296
+ const compressedData = isCompressed
297
+ ? await (0, compress_js_1.compress)(entry.data, { level: this.level })
298
+ : entry.data;
299
+ const processedEntry = {
300
+ name: nameBytes,
301
+ data: entry.data,
302
+ compressedData,
303
+ crc: (0, crc32_js_1.crc32)(entry.data),
304
+ compressionMethod: isCompressed ? COMPRESSION_DEFLATE : COMPRESSION_STORE,
305
+ modTime: dosTime,
306
+ modDate: dosDate,
307
+ comment: commentBytes,
308
+ offset: this.currentOffset
309
+ };
310
+ this.entries.push(processedEntry);
311
+ this.currentOffset += 30 + nameBytes.length + compressedData.length;
312
+ return [buildLocalFileHeader(processedEntry), compressedData];
313
+ }
314
+ /**
315
+ * Add a file to the ZIP (sync, Node.js only)
316
+ * @param entry - File entry
317
+ * @returns Local file header and compressed data chunks
318
+ */
319
+ addFileSync(entry) {
320
+ if (this.finalized) {
321
+ throw new Error("Cannot add files after finalizing");
322
+ }
323
+ const nameBytes = encodeString(entry.name);
324
+ const commentBytes = encodeString(entry.comment ?? "");
325
+ const [dosTime, dosDate] = dateToDos(entry.modTime ?? new Date());
326
+ // Compress data
327
+ const isCompressed = this.level > 0 && entry.data.length > 0;
328
+ const compressedData = isCompressed
329
+ ? (0, compress_js_1.compressSync)(entry.data, { level: this.level })
330
+ : entry.data;
331
+ const processedEntry = {
332
+ name: nameBytes,
333
+ data: entry.data,
334
+ compressedData,
335
+ crc: (0, crc32_js_1.crc32)(entry.data),
336
+ compressionMethod: isCompressed ? COMPRESSION_DEFLATE : COMPRESSION_STORE,
337
+ modTime: dosTime,
338
+ modDate: dosDate,
339
+ comment: commentBytes,
340
+ offset: this.currentOffset
341
+ };
342
+ this.entries.push(processedEntry);
343
+ this.currentOffset += 30 + nameBytes.length + compressedData.length;
344
+ return [buildLocalFileHeader(processedEntry), compressedData];
345
+ }
346
+ /**
347
+ * Finalize the ZIP and return central directory + end record
348
+ * @returns Central directory and end of central directory chunks
349
+ */
350
+ finalize() {
351
+ if (this.finalized) {
352
+ throw new Error("ZIP already finalized");
353
+ }
354
+ this.finalized = true;
355
+ const chunks = [];
356
+ // Central directory
357
+ for (const entry of this.entries) {
358
+ chunks.push(buildCentralDirHeader(entry));
359
+ }
360
+ const centralDirSize = chunks.reduce((sum, c) => sum + c.length, 0);
361
+ // End of central directory
362
+ chunks.push(buildEndOfCentralDir(this.entries.length, centralDirSize, this.currentOffset, this.zipComment));
363
+ return chunks;
364
+ }
365
+ /**
366
+ * Get current number of entries
367
+ */
368
+ get entryCount() {
369
+ return this.entries.length;
370
+ }
371
+ /**
372
+ * Get current ZIP data size (without central directory)
373
+ */
374
+ get dataSize() {
375
+ return this.currentOffset;
376
+ }
377
+ }
378
+ exports.ZipBuilder = ZipBuilder;