neozipkit 1.0.5 → 1.5.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.
- package/NEOZIP_APPNOTE.md +116 -18
- package/README.md +38 -8
- package/WHATS_NEW.md +111 -0
- package/dist/browser/zstd-browser-shim.d.ts +17 -9
- package/dist/browser/zstd-browser-shim.d.ts.map +1 -1
- package/dist/browser/zstd-browser-shim.js +99 -21
- package/dist/browser/zstd-browser-shim.js.map +1 -1
- package/dist/browser-esm/index.js +6178 -1261
- package/dist/browser-esm/index.js.map +4 -4
- package/dist/browser-umd/index.js +6178 -1261
- package/dist/browser-umd/index.js.map +4 -4
- package/dist/browser-umd/index.min.js +6 -3
- package/dist/core/ZipCompress.d.ts +6 -0
- package/dist/core/ZipCompress.d.ts.map +1 -1
- package/dist/core/ZipCompress.js.map +1 -1
- package/dist/core/ZipCopy.d.ts.map +1 -1
- package/dist/core/ZipCopy.js +33 -25
- package/dist/core/ZipCopy.js.map +1 -1
- package/dist/core/ZipEntry.d.ts +18 -0
- package/dist/core/ZipEntry.d.ts.map +1 -1
- package/dist/core/ZipEntry.js +139 -13
- package/dist/core/ZipEntry.js.map +1 -1
- package/dist/core/Zipkit.d.ts.map +1 -1
- package/dist/core/Zipkit.js +26 -19
- package/dist/core/Zipkit.js.map +1 -1
- package/dist/core/constants/Headers.d.ts +1 -0
- package/dist/core/constants/Headers.d.ts.map +1 -1
- package/dist/core/constants/Headers.js +1 -0
- package/dist/core/constants/Headers.js.map +1 -1
- package/dist/core/index.d.ts +3 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +5 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/inflateZipPayload.d.ts +6 -0
- package/dist/core/inflateZipPayload.d.ts.map +1 -0
- package/dist/core/inflateZipPayload.js +28 -0
- package/dist/core/inflateZipPayload.js.map +1 -0
- package/dist/core/origin/OriginExtra.d.ts +43 -0
- package/dist/core/origin/OriginExtra.d.ts.map +1 -0
- package/dist/core/origin/OriginExtra.js +187 -0
- package/dist/core/origin/OriginExtra.js.map +1 -0
- package/dist/core/version.js +1 -1
- package/dist/core/zip64/Zip64.d.ts +138 -0
- package/dist/core/zip64/Zip64.d.ts.map +1 -0
- package/dist/core/zip64/Zip64.js +353 -0
- package/dist/core/zip64/Zip64.js.map +1 -0
- package/dist/node/ZipCopyNode.d.ts.map +1 -1
- package/dist/node/ZipCopyNode.js +26 -26
- package/dist/node/ZipCopyNode.js.map +1 -1
- package/dist/node/ZipDecompressNode.d.ts.map +1 -1
- package/dist/node/ZipDecompressNode.js +8 -6
- package/dist/node/ZipDecompressNode.js.map +1 -1
- package/dist/node/ZipkitNode.d.ts +14 -1
- package/dist/node/ZipkitNode.d.ts.map +1 -1
- package/dist/node/ZipkitNode.js +91 -53
- package/dist/node/ZipkitNode.js.map +1 -1
- package/dist/node/ZstdNode.d.ts +14 -4
- package/dist/node/ZstdNode.d.ts.map +1 -1
- package/dist/node/ZstdNode.js +28 -4
- package/dist/node/ZstdNode.js.map +1 -1
- package/dist/node/buildZipBuffer.d.ts +49 -0
- package/dist/node/buildZipBuffer.d.ts.map +1 -0
- package/dist/node/buildZipBuffer.js +146 -0
- package/dist/node/buildZipBuffer.js.map +1 -0
- package/dist/node/index.d.ts +3 -0
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +6 -1
- package/dist/node/index.js.map +1 -1
- package/dist/node/inflateZipPayload.d.ts +5 -0
- package/dist/node/inflateZipPayload.d.ts.map +1 -0
- package/dist/node/inflateZipPayload.js +21 -0
- package/dist/node/inflateZipPayload.js.map +1 -0
- package/dist/types/index.js +2 -2
- package/node-esm.mjs +32 -0
- package/package.json +11 -5
- package/src/browser/zstd-browser-shim.ts +110 -28
- package/src/core/ZipCompress.ts +6 -0
- package/src/core/ZipCopy.ts +56 -27
- package/src/core/ZipEntry.ts +192 -13
- package/src/core/Zipkit.ts +40 -22
- package/src/core/constants/Headers.ts +1 -0
- package/src/core/index.ts +3 -0
- package/src/core/inflateZipPayload.ts +27 -0
- package/src/core/origin/OriginExtra.ts +209 -0
- package/src/core/version.ts +1 -1
- package/src/core/zip64/Zip64.ts +473 -0
- package/src/node/ZipCopyNode.ts +31 -29
- package/src/node/ZipDecompressNode.ts +6 -5
- package/src/node/ZipkitNode.ts +121 -68
- package/src/node/ZstdNode.ts +33 -8
- package/src/node/buildZipBuffer.ts +221 -0
- package/src/node/index.ts +7 -0
- package/src/node/inflateZipPayload.ts +19 -0
- package/src/types/index.ts +2 -2
package/src/node/ZipkitNode.ts
CHANGED
|
@@ -11,19 +11,31 @@ import { ZipDecompressNode } from './ZipDecompressNode';
|
|
|
11
11
|
import {
|
|
12
12
|
CENTRAL_END,
|
|
13
13
|
CENTRAL_DIR,
|
|
14
|
-
ZIP64_CENTRAL_END,
|
|
15
|
-
ZIP64_CENTRAL_DIR,
|
|
16
14
|
LOCAL_HDR,
|
|
17
15
|
GP_FLAG,
|
|
18
16
|
CMP_METHOD,
|
|
19
|
-
ENCRYPT_HDR_SIZE
|
|
17
|
+
ENCRYPT_HDR_SIZE,
|
|
18
|
+
HDR_ID,
|
|
20
19
|
} from '../core/constants/Headers';
|
|
20
|
+
import {
|
|
21
|
+
buildEndRecords,
|
|
22
|
+
classicEocdNeedsZip64,
|
|
23
|
+
exceedsU32,
|
|
24
|
+
parseZip64Eocd,
|
|
25
|
+
parseZip64Locator,
|
|
26
|
+
writeU64,
|
|
27
|
+
ZIP64_EOCD_RECORD_SIZE,
|
|
28
|
+
ZIP64_LOCATOR_SIZE,
|
|
29
|
+
ZIP64_U32,
|
|
30
|
+
} from '../core/zip64/Zip64';
|
|
21
31
|
import { NEO_CRYPTO_ALGORITHM_AES256_V1 } from '../core/encryption/NeoCrypto';
|
|
22
32
|
import { AesCrypto } from '../core/encryption/AesCrypto';
|
|
23
33
|
import {
|
|
24
34
|
detectLegacyWasmZstd,
|
|
25
35
|
type LegacyZstdDetectResult,
|
|
26
36
|
} from './LegacyZstd';
|
|
37
|
+
import { writeZipFileSync, type ZipBufferMember, type BuildZipBufferOptions } from './buildZipBuffer';
|
|
38
|
+
export type { ZipBufferMember };
|
|
27
39
|
import * as fs from 'fs';
|
|
28
40
|
import * as path from 'path';
|
|
29
41
|
import { minimatch } from 'minimatch';
|
|
@@ -717,6 +729,10 @@ export default class ZipkitNode extends Zipkit {
|
|
|
717
729
|
onHashCalculated?: (entry: ZipEntry, hash: Buffer) => void;
|
|
718
730
|
}
|
|
719
731
|
): Promise<void> {
|
|
732
|
+
if (options?.forceZip64) {
|
|
733
|
+
entry.forceZip64 = true;
|
|
734
|
+
}
|
|
735
|
+
|
|
720
736
|
// Set compression method based on options
|
|
721
737
|
const level = options?.level ?? 6;
|
|
722
738
|
let realCmpMethod: number;
|
|
@@ -859,9 +875,55 @@ export default class ZipkitNode extends Zipkit {
|
|
|
859
875
|
throw new Error(`Compressed size not set for entry: ${entry.filename}`);
|
|
860
876
|
}
|
|
861
877
|
|
|
862
|
-
const
|
|
863
|
-
|
|
864
|
-
|
|
878
|
+
const classicCmp = entry.localHdrOffset + LOCAL_HDR.CMP_SIZE;
|
|
879
|
+
const classicUncmp = entry.localHdrOffset + LOCAL_HDR.UNCMP_SIZE;
|
|
880
|
+
const useZip64Sizes =
|
|
881
|
+
entry.forceZip64 ||
|
|
882
|
+
exceedsU32(entry.uncompressedSize) ||
|
|
883
|
+
exceedsU32(entry.compressedSize);
|
|
884
|
+
|
|
885
|
+
if (useZip64Sizes) {
|
|
886
|
+
// Classic fields stay at 0xFFFFFFFF; update u64 sizes inside local extra 0x0001
|
|
887
|
+
const sentinel = Buffer.alloc(4);
|
|
888
|
+
sentinel.writeUInt32LE(ZIP64_U32, 0);
|
|
889
|
+
fs.writeSync(writer.outputFd, sentinel, 0, 4, classicCmp);
|
|
890
|
+
fs.writeSync(writer.outputFd, sentinel, 0, 4, classicUncmp);
|
|
891
|
+
|
|
892
|
+
const fnameLenBuf = Buffer.alloc(2);
|
|
893
|
+
fs.readSync(writer.outputFd, fnameLenBuf, 0, 2, entry.localHdrOffset + LOCAL_HDR.FNAME_LEN);
|
|
894
|
+
const fnameLen = fnameLenBuf.readUInt16LE(0);
|
|
895
|
+
const extraLenBuf = Buffer.alloc(2);
|
|
896
|
+
fs.readSync(writer.outputFd, extraLenBuf, 0, 2, entry.localHdrOffset + LOCAL_HDR.EXTRA_LEN);
|
|
897
|
+
const extraLen = extraLenBuf.readUInt16LE(0);
|
|
898
|
+
const extraStart = entry.localHdrOffset + LOCAL_HDR.SIZE + fnameLen;
|
|
899
|
+
const extraBuf = Buffer.alloc(extraLen);
|
|
900
|
+
if (extraLen > 0) {
|
|
901
|
+
fs.readSync(writer.outputFd, extraBuf, 0, extraLen, extraStart);
|
|
902
|
+
}
|
|
903
|
+
let patched = false;
|
|
904
|
+
for (let i = 0; i + 4 <= extraBuf.length; ) {
|
|
905
|
+
const id = extraBuf.readUInt16LE(i);
|
|
906
|
+
const len = extraBuf.readUInt16LE(i + 2);
|
|
907
|
+
if (id === HDR_ID.ZIP64 && len >= 16) {
|
|
908
|
+
// Local Zip64 always has uncompressed (8) then compressed (8)
|
|
909
|
+
writeU64(extraBuf, i + 4, entry.uncompressedSize);
|
|
910
|
+
writeU64(extraBuf, i + 12, entry.compressedSize);
|
|
911
|
+
fs.writeSync(writer.outputFd, extraBuf, i + 4, 16, extraStart + i + 4);
|
|
912
|
+
patched = true;
|
|
913
|
+
break;
|
|
914
|
+
}
|
|
915
|
+
i += 4 + len;
|
|
916
|
+
}
|
|
917
|
+
if (!patched) {
|
|
918
|
+
throw new Error(
|
|
919
|
+
`Zip64 local extra (0x0001) missing for large entry: ${entry.filename}`
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
} else {
|
|
923
|
+
const sizeBuffer = Buffer.alloc(4);
|
|
924
|
+
sizeBuffer.writeUInt32LE(entry.compressedSize, 0);
|
|
925
|
+
fs.writeSync(writer.outputFd, sizeBuffer, 0, 4, classicCmp);
|
|
926
|
+
}
|
|
865
927
|
|
|
866
928
|
if (entry.crc !== undefined) {
|
|
867
929
|
const crcBuffer = Buffer.alloc(4);
|
|
@@ -938,6 +1000,7 @@ export default class ZipkitNode extends Zipkit {
|
|
|
938
1000
|
* @param centralDirSize - Size of central directory in bytes
|
|
939
1001
|
* @param centralDirOffset - Offset to start of central directory
|
|
940
1002
|
* @param archiveComment - Optional archive comment (max 65535 bytes)
|
|
1003
|
+
* @param options - Optional Zip64 force and related EOCD flags
|
|
941
1004
|
* @returns Promise that resolves when EOCD is written
|
|
942
1005
|
*/
|
|
943
1006
|
async writeEndOfCentralDirectory(
|
|
@@ -945,53 +1008,20 @@ export default class ZipkitNode extends Zipkit {
|
|
|
945
1008
|
totalEntries: number,
|
|
946
1009
|
centralDirSize: number,
|
|
947
1010
|
centralDirOffset: number,
|
|
948
|
-
archiveComment?: string
|
|
1011
|
+
archiveComment?: string,
|
|
1012
|
+
options?: { forceZip64?: boolean }
|
|
949
1013
|
): Promise<void> {
|
|
950
|
-
const
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
offset += 4;
|
|
960
|
-
|
|
961
|
-
// Number of this disk (2 bytes)
|
|
962
|
-
buffer.writeUInt16LE(0, offset);
|
|
963
|
-
offset += 2;
|
|
964
|
-
|
|
965
|
-
// Number of the disk with the start of the central directory (2 bytes)
|
|
966
|
-
buffer.writeUInt16LE(0, offset);
|
|
967
|
-
offset += 2;
|
|
968
|
-
|
|
969
|
-
// Total number of entries in the central directory on this disk (2 bytes)
|
|
970
|
-
buffer.writeUInt16LE(totalEntries, offset);
|
|
971
|
-
offset += 2;
|
|
972
|
-
|
|
973
|
-
// Total number of entries in the central directory (2 bytes)
|
|
974
|
-
buffer.writeUInt16LE(totalEntries, offset);
|
|
975
|
-
offset += 2;
|
|
976
|
-
|
|
977
|
-
// Size of the central directory (4 bytes)
|
|
978
|
-
buffer.writeUInt32LE(centralDirSize, offset);
|
|
979
|
-
offset += 4;
|
|
980
|
-
|
|
981
|
-
// Offset of start of central directory with respect to the starting disk number (4 bytes)
|
|
982
|
-
buffer.writeUInt32LE(centralDirOffset, offset);
|
|
983
|
-
offset += 4;
|
|
984
|
-
|
|
985
|
-
// ZIP file comment length (2 bytes)
|
|
986
|
-
buffer.writeUInt16LE(commentLength, offset);
|
|
987
|
-
offset += 2;
|
|
988
|
-
|
|
989
|
-
// ZIP file comment (variable length)
|
|
990
|
-
if (commentLength > 0) {
|
|
991
|
-
commentBytes.copy(buffer, offset, 0, commentLength);
|
|
992
|
-
}
|
|
1014
|
+
const buffer = buildEndRecords({
|
|
1015
|
+
totalEntries,
|
|
1016
|
+
centralDirSize,
|
|
1017
|
+
centralDirOffset,
|
|
1018
|
+
zip64EocdOffset: centralDirOffset + centralDirSize,
|
|
1019
|
+
archiveComment,
|
|
1020
|
+
allowSizeOffsetZip64: true,
|
|
1021
|
+
forceZip64: options?.forceZip64 === true,
|
|
1022
|
+
});
|
|
993
1023
|
|
|
994
|
-
// Write EOCD to file
|
|
1024
|
+
// Write EOCD (and Zip64 EOCD + locator when needed) to file
|
|
995
1025
|
await new Promise<void>((resolve, reject) => {
|
|
996
1026
|
writer.outputStream.write(buffer, (error) => {
|
|
997
1027
|
if (error) {
|
|
@@ -1067,7 +1097,9 @@ export default class ZipkitNode extends Zipkit {
|
|
|
1067
1097
|
writer,
|
|
1068
1098
|
entries.length,
|
|
1069
1099
|
centralDirSize,
|
|
1070
|
-
centralDirOffset
|
|
1100
|
+
centralDirOffset,
|
|
1101
|
+
undefined,
|
|
1102
|
+
{ forceZip64: options?.forceZip64 === true }
|
|
1071
1103
|
);
|
|
1072
1104
|
} finally {
|
|
1073
1105
|
await this.finalizeZipFile(writer);
|
|
@@ -1210,6 +1242,21 @@ export default class ZipkitNode extends Zipkit {
|
|
|
1210
1242
|
throw new Error('updateZipFile() - Full implementation pending. Use neozip CLI for now.');
|
|
1211
1243
|
}
|
|
1212
1244
|
|
|
1245
|
+
/**
|
|
1246
|
+
* Stream members to `filePath` (local headers, payloads, central directory, EOCD).
|
|
1247
|
+
* Pass `useSHA256` for Extra Field 0x014E and `additionalExtra` for caller blocks
|
|
1248
|
+
* such as ZipWiki 0x014F. `method` 93 uses zlib zstd at `level` (default 7).
|
|
1249
|
+
* NeoEncrypt is not enabled. Precompressed members are copied without recompression.
|
|
1250
|
+
* Pass `{ forceZip64: true }` to emit Zip64 even for small members (testing).
|
|
1251
|
+
*/
|
|
1252
|
+
writeMembersSync(
|
|
1253
|
+
filePath: string,
|
|
1254
|
+
members: ZipBufferMember[],
|
|
1255
|
+
options?: BuildZipBufferOptions
|
|
1256
|
+
): void {
|
|
1257
|
+
writeZipFileSync(filePath, members, options);
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1213
1260
|
// ============================================================================
|
|
1214
1261
|
// File-based ZIP Loading Methods (merged from ZipLoadEntriesServer)
|
|
1215
1262
|
// ============================================================================
|
|
@@ -1275,9 +1322,17 @@ export default class ZipkitNode extends Zipkit {
|
|
|
1275
1322
|
const zipkit = this as any;
|
|
1276
1323
|
zipkit.centralDirSize = eocdBuffer.readUInt32LE(CENTRAL_END.CENTRAL_DIR_SIZE);
|
|
1277
1324
|
zipkit.centralDirOffset = eocdBuffer.readUInt32LE(CENTRAL_END.CENTRAL_DIR_OFFSET);
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1325
|
+
const volEntries = eocdBuffer.readUInt16LE(CENTRAL_END.VOL_ENTRIES);
|
|
1326
|
+
const totalEntries = eocdBuffer.readUInt16LE(CENTRAL_END.TOTAL_ENTRIES);
|
|
1327
|
+
|
|
1328
|
+
if (
|
|
1329
|
+
classicEocdNeedsZip64({
|
|
1330
|
+
volEntries,
|
|
1331
|
+
totalEntries,
|
|
1332
|
+
centralDirSize: zipkit.centralDirSize,
|
|
1333
|
+
centralDirOffset: zipkit.centralDirOffset,
|
|
1334
|
+
})
|
|
1335
|
+
) {
|
|
1281
1336
|
await this.loadZIP64EOCD(eocdOffset);
|
|
1282
1337
|
}
|
|
1283
1338
|
} else {
|
|
@@ -1307,20 +1362,18 @@ export default class ZipkitNode extends Zipkit {
|
|
|
1307
1362
|
}
|
|
1308
1363
|
|
|
1309
1364
|
// Look for ZIP64 locator
|
|
1310
|
-
const locatorOffset = eocdOffset -
|
|
1311
|
-
const locatorBuffer = Buffer.alloc(
|
|
1312
|
-
await this.fileHandle.read(locatorBuffer, 0,
|
|
1365
|
+
const locatorOffset = eocdOffset - ZIP64_LOCATOR_SIZE;
|
|
1366
|
+
const locatorBuffer = Buffer.alloc(ZIP64_LOCATOR_SIZE);
|
|
1367
|
+
await this.fileHandle.read(locatorBuffer, 0, ZIP64_LOCATOR_SIZE, locatorOffset);
|
|
1313
1368
|
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
zipkit.centralDirOffset = Number(zip64Buffer.readBigUInt64LE(ZIP64_CENTRAL_DIR.CENTRAL_DIR_OFFSET));
|
|
1323
|
-
}
|
|
1369
|
+
const zip64Offset = parseZip64Locator(locatorBuffer);
|
|
1370
|
+
const zip64Buffer = Buffer.alloc(ZIP64_EOCD_RECORD_SIZE);
|
|
1371
|
+
await this.fileHandle.read(zip64Buffer, 0, ZIP64_EOCD_RECORD_SIZE, zip64Offset);
|
|
1372
|
+
|
|
1373
|
+
const z64 = parseZip64Eocd(zip64Buffer);
|
|
1374
|
+
const zipkit = this as any;
|
|
1375
|
+
zipkit.centralDirSize = z64.centralDirSize;
|
|
1376
|
+
zipkit.centralDirOffset = z64.centralDirOffset;
|
|
1324
1377
|
}
|
|
1325
1378
|
|
|
1326
1379
|
/**
|
package/src/node/ZstdNode.ts
CHANGED
|
@@ -54,26 +54,49 @@ export function createDecompressTransform(): zlib.ZstdDecompress {
|
|
|
54
54
|
return zlib.createZstdDecompress();
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
/** Bytes accepted by the buffer zstd codec (Node Buffer or browser ArrayBuffer). */
|
|
58
|
+
export type ZstdInput = Buffer | Uint8Array | ArrayBuffer;
|
|
59
|
+
|
|
60
|
+
export function asZstdBuffer(data: ZstdInput): Buffer {
|
|
61
|
+
if (Buffer.isBuffer(data)) return data;
|
|
62
|
+
if (data instanceof ArrayBuffer) return Buffer.from(data);
|
|
63
|
+
return Buffer.from(data.buffer, data.byteOffset, data.byteLength);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function compress(data: ZstdInput, level: number = 6): Promise<Buffer> {
|
|
58
67
|
assertNativeZstd();
|
|
59
|
-
const result = await zstdCompressAsync(data, compressOptions(level));
|
|
68
|
+
const result = await zstdCompressAsync(asZstdBuffer(data), compressOptions(level));
|
|
60
69
|
return Buffer.from(result);
|
|
61
70
|
}
|
|
62
71
|
|
|
63
|
-
export async function decompress(data:
|
|
72
|
+
export async function decompress(data: ZstdInput): Promise<Buffer> {
|
|
64
73
|
assertNativeZstd();
|
|
65
|
-
const result = await zstdDecompressAsync(data);
|
|
74
|
+
const result = await zstdDecompressAsync(asZstdBuffer(data));
|
|
66
75
|
return Buffer.from(result);
|
|
67
76
|
}
|
|
68
77
|
|
|
69
|
-
export function compressSync(data:
|
|
78
|
+
export function compressSync(data: ZstdInput, level: number = 6): Buffer {
|
|
79
|
+
assertNativeZstd();
|
|
80
|
+
return zlib.zstdCompressSync(asZstdBuffer(data), compressOptions(level));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Compress with a raw zlib zstd level (1–19), without the 1–9 kit scale.
|
|
85
|
+
* ZipWiki pack uses level 7 on this scale.
|
|
86
|
+
*/
|
|
87
|
+
export function compressSyncAtLevel(data: ZstdInput, zstdLevel: number): Buffer {
|
|
70
88
|
assertNativeZstd();
|
|
71
|
-
|
|
89
|
+
const level = Math.min(19, Math.max(1, Math.trunc(zstdLevel)));
|
|
90
|
+
return zlib.zstdCompressSync(asZstdBuffer(data), {
|
|
91
|
+
params: {
|
|
92
|
+
[zlib.constants.ZSTD_c_compressionLevel]: level,
|
|
93
|
+
},
|
|
94
|
+
});
|
|
72
95
|
}
|
|
73
96
|
|
|
74
|
-
export function decompressSync(data:
|
|
97
|
+
export function decompressSync(data: ZstdInput): Buffer {
|
|
75
98
|
assertNativeZstd();
|
|
76
|
-
return zlib.zstdDecompressSync(data);
|
|
99
|
+
return zlib.zstdDecompressSync(asZstdBuffer(data));
|
|
77
100
|
}
|
|
78
101
|
|
|
79
102
|
/**
|
|
@@ -147,7 +170,9 @@ export const ZstdNode = {
|
|
|
147
170
|
compress,
|
|
148
171
|
decompress,
|
|
149
172
|
compressSync,
|
|
173
|
+
compressSyncAtLevel,
|
|
150
174
|
decompressSync,
|
|
175
|
+
asZstdBuffer,
|
|
151
176
|
compressChunks,
|
|
152
177
|
decompressStream,
|
|
153
178
|
};
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory and streaming ZIP writer.
|
|
3
|
+
* Zstd level is the raw zlib level (ZipWiki default 7). NeoEncrypt stays off.
|
|
4
|
+
* Caller extra blocks (ZipWiki 0x014F) are written beside optional 0x014E.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { closeSync, openSync, writeSync } from 'fs';
|
|
8
|
+
import { deflateRawSync } from 'zlib';
|
|
9
|
+
import ZipEntry from '../core/ZipEntry';
|
|
10
|
+
import { crc32, sha256 } from '../core/encryption/ZipCrypto';
|
|
11
|
+
import { compressSyncAtLevel } from './ZstdNode';
|
|
12
|
+
import {
|
|
13
|
+
assertBufferAllowsEntryZip64,
|
|
14
|
+
buildEndRecords,
|
|
15
|
+
} from '../core/zip64/Zip64';
|
|
16
|
+
|
|
17
|
+
export type ZipPrecompressedMember = {
|
|
18
|
+
method: number;
|
|
19
|
+
data: Buffer;
|
|
20
|
+
crc32: number;
|
|
21
|
+
uncompressedSize: number;
|
|
22
|
+
/** Complete extra-field bytes copied onto the local and central headers. */
|
|
23
|
+
extra?: Buffer;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type ZipBufferMember = {
|
|
27
|
+
name: string;
|
|
28
|
+
/** Uncompressed payload. Unused as stored bytes when `precompressed` is set. */
|
|
29
|
+
data: Buffer;
|
|
30
|
+
mtime?: Date;
|
|
31
|
+
/** DOS time word. Wins over `mtime` when set together with `dosDate`. */
|
|
32
|
+
dosTime?: number;
|
|
33
|
+
dosDate?: number;
|
|
34
|
+
/**
|
|
35
|
+
* Complete extra-field records (id, size, payload), e.g. ZipWiki 0x014F.
|
|
36
|
+
* Ignored when `precompressed.extra` is set — that blob is copied as-is.
|
|
37
|
+
*/
|
|
38
|
+
additionalExtra?: Buffer;
|
|
39
|
+
/** Write Extra Field 0x014E (SHA-256 of uncompressed `data`). */
|
|
40
|
+
useSHA256?: boolean;
|
|
41
|
+
/** 0 store, 8 deflate, 93 zstd. Default 93. Ignored when `precompressed` is set. */
|
|
42
|
+
method?: 0 | 8 | 93;
|
|
43
|
+
/** zlib zstd level or raw deflate level. Default 7. */
|
|
44
|
+
level?: number;
|
|
45
|
+
/**
|
|
46
|
+
* Force Zip64 Extra `0x0001` on this member even when sizes fit classic fields.
|
|
47
|
+
* Pair with `forceZip64` on `buildZipBufferSync` / `writeZipFileSync` options
|
|
48
|
+
* (or set on every member) so the EOCD is Zip64 as well.
|
|
49
|
+
*/
|
|
50
|
+
forceZip64?: boolean;
|
|
51
|
+
precompressed?: ZipPrecompressedMember;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
type FramedMember = {
|
|
55
|
+
local: Buffer;
|
|
56
|
+
payload: Buffer;
|
|
57
|
+
central: Buffer;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
function dosFromDate(date: Date): { time: number; date: number } {
|
|
61
|
+
const year = date.getFullYear();
|
|
62
|
+
const month = date.getMonth() + 1;
|
|
63
|
+
const day = date.getDate();
|
|
64
|
+
const hours = date.getHours();
|
|
65
|
+
const minutes = date.getMinutes();
|
|
66
|
+
const seconds = Math.floor(date.getSeconds() / 2);
|
|
67
|
+
const y = Math.max(0, Math.min(127, year - 1980));
|
|
68
|
+
return {
|
|
69
|
+
time: ((hours & 0x1f) << 11) | ((minutes & 0x3f) << 5) | (seconds & 0x1f),
|
|
70
|
+
date: ((y & 0x7f) << 9) | ((month & 0x0f) << 5) | (day & 0x1f),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function compressPayload(
|
|
75
|
+
data: Buffer,
|
|
76
|
+
method: 0 | 8 | 93,
|
|
77
|
+
level: number,
|
|
78
|
+
): { method: number; data: Buffer } {
|
|
79
|
+
if (method === 0 || data.length === 0) {
|
|
80
|
+
return { method: 0, data };
|
|
81
|
+
}
|
|
82
|
+
const compressed =
|
|
83
|
+
method === 8
|
|
84
|
+
? deflateRawSync(data, { level: Math.max(1, Math.min(9, level)) })
|
|
85
|
+
: compressSyncAtLevel(data, Math.max(1, level));
|
|
86
|
+
if (compressed.length >= data.length) {
|
|
87
|
+
return { method: 0, data };
|
|
88
|
+
}
|
|
89
|
+
return { method, data: compressed };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function frameMembers(
|
|
93
|
+
members: ZipBufferMember[],
|
|
94
|
+
opts?: { forceZip64?: boolean }
|
|
95
|
+
): FramedMember[] {
|
|
96
|
+
const packTime = new Date();
|
|
97
|
+
const framed: FramedMember[] = [];
|
|
98
|
+
let offset = 0;
|
|
99
|
+
const forceAll = opts?.forceZip64 === true;
|
|
100
|
+
for (const member of members) {
|
|
101
|
+
const copied = member.precompressed;
|
|
102
|
+
const uncompressed = member.data ?? Buffer.alloc(0);
|
|
103
|
+
const level = member.level ?? 7;
|
|
104
|
+
const requested = member.method ?? 93;
|
|
105
|
+
const payload = copied
|
|
106
|
+
? {
|
|
107
|
+
method: copied.method,
|
|
108
|
+
data: copied.data,
|
|
109
|
+
uncompressedSize: copied.uncompressedSize,
|
|
110
|
+
}
|
|
111
|
+
: {
|
|
112
|
+
...compressPayload(uncompressed, requested, level),
|
|
113
|
+
uncompressedSize: uncompressed.length,
|
|
114
|
+
};
|
|
115
|
+
const crc = copied ? copied.crc32 >>> 0 : crc32(uncompressed) >>> 0;
|
|
116
|
+
const extra = copied?.extra ?? member.additionalExtra;
|
|
117
|
+
const dos =
|
|
118
|
+
member.dosTime !== undefined && member.dosDate !== undefined
|
|
119
|
+
? { time: member.dosTime, date: member.dosDate }
|
|
120
|
+
: dosFromDate(member.mtime ?? packTime);
|
|
121
|
+
|
|
122
|
+
const entry = new ZipEntry(member.name, null, false);
|
|
123
|
+
entry.bitFlags = 0;
|
|
124
|
+
entry.emitUnicodePath = false;
|
|
125
|
+
entry.isUpdated = true;
|
|
126
|
+
entry.filename = member.name;
|
|
127
|
+
entry.cmpMethod = payload.method;
|
|
128
|
+
entry.crc = crc;
|
|
129
|
+
entry.compressedSize = payload.data.length;
|
|
130
|
+
entry.uncompressedSize = payload.uncompressedSize;
|
|
131
|
+
entry.timeDateDOS = ((dos.date & 0xffff) << 16) | (dos.time & 0xffff);
|
|
132
|
+
entry.localHdrOffset = offset;
|
|
133
|
+
entry.forceZip64 = forceAll || member.forceZip64 === true;
|
|
134
|
+
assertBufferAllowsEntryZip64(
|
|
135
|
+
entry.uncompressedSize,
|
|
136
|
+
entry.compressedSize,
|
|
137
|
+
entry.localHdrOffset
|
|
138
|
+
);
|
|
139
|
+
if (!copied && member.useSHA256 === true) {
|
|
140
|
+
entry.sha256 = sha256(uncompressed);
|
|
141
|
+
}
|
|
142
|
+
if (extra && extra.length > 0) {
|
|
143
|
+
entry.additionalExtra = extra;
|
|
144
|
+
}
|
|
145
|
+
const local = entry.createLocalHdr();
|
|
146
|
+
const central = entry.centralDirEntry();
|
|
147
|
+
framed.push({ local, payload: payload.data, central });
|
|
148
|
+
offset += local.length + payload.data.length;
|
|
149
|
+
}
|
|
150
|
+
return framed;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function eocd(
|
|
154
|
+
entryCount: number,
|
|
155
|
+
centralSize: number,
|
|
156
|
+
centralOffset: number,
|
|
157
|
+
forceZip64?: boolean
|
|
158
|
+
): Buffer {
|
|
159
|
+
return buildEndRecords({
|
|
160
|
+
totalEntries: entryCount,
|
|
161
|
+
centralDirSize: centralSize,
|
|
162
|
+
centralDirOffset: centralOffset,
|
|
163
|
+
zip64EocdOffset: centralOffset + centralSize,
|
|
164
|
+
allowSizeOffsetZip64: false,
|
|
165
|
+
forceZip64: forceZip64 === true,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export type BuildZipBufferOptions = {
|
|
170
|
+
/** Force Zip64 EOCD + per-member Extra when sizes fit classic fields. */
|
|
171
|
+
forceZip64?: boolean;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
/** Build a ZIP in one buffer. Kept for tests and atomic in-memory rewrites. */
|
|
175
|
+
export function buildZipBufferSync(
|
|
176
|
+
members: ZipBufferMember[],
|
|
177
|
+
options?: BuildZipBufferOptions
|
|
178
|
+
): Buffer {
|
|
179
|
+
const forceZip64 = options?.forceZip64 === true;
|
|
180
|
+
const framed = frameMembers(members, { forceZip64 });
|
|
181
|
+
const parts: Buffer[] = [];
|
|
182
|
+
let centralSize = 0;
|
|
183
|
+
let payloadBytes = 0;
|
|
184
|
+
for (const part of framed) {
|
|
185
|
+
parts.push(part.local, part.payload);
|
|
186
|
+
payloadBytes += part.local.length + part.payload.length;
|
|
187
|
+
}
|
|
188
|
+
for (const part of framed) {
|
|
189
|
+
parts.push(part.central);
|
|
190
|
+
centralSize += part.central.length;
|
|
191
|
+
}
|
|
192
|
+
parts.push(eocd(framed.length, centralSize, payloadBytes, forceZip64));
|
|
193
|
+
return Buffer.concat(parts);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Stream local headers, payloads, the central directory, and the EOCD to `filePath`. */
|
|
197
|
+
export function writeZipFileSync(
|
|
198
|
+
filePath: string,
|
|
199
|
+
members: ZipBufferMember[],
|
|
200
|
+
options?: BuildZipBufferOptions
|
|
201
|
+
): void {
|
|
202
|
+
const forceZip64 = options?.forceZip64 === true;
|
|
203
|
+
const framed = frameMembers(members, { forceZip64 });
|
|
204
|
+
const fd = openSync(filePath, 'w');
|
|
205
|
+
try {
|
|
206
|
+
let payloadBytes = 0;
|
|
207
|
+
let centralSize = 0;
|
|
208
|
+
for (const part of framed) {
|
|
209
|
+
writeSync(fd, part.local);
|
|
210
|
+
writeSync(fd, part.payload);
|
|
211
|
+
payloadBytes += part.local.length + part.payload.length;
|
|
212
|
+
}
|
|
213
|
+
for (const part of framed) {
|
|
214
|
+
writeSync(fd, part.central);
|
|
215
|
+
centralSize += part.central.length;
|
|
216
|
+
}
|
|
217
|
+
writeSync(fd, eocd(framed.length, centralSize, payloadBytes, forceZip64));
|
|
218
|
+
} finally {
|
|
219
|
+
closeSync(fd);
|
|
220
|
+
}
|
|
221
|
+
}
|
package/src/node/index.ts
CHANGED
|
@@ -34,6 +34,13 @@ export type { LegacyZstdDetectResult, LegacyZstdConfidence, DetectLegacyZstdOpti
|
|
|
34
34
|
|
|
35
35
|
// Efficient ZIP copying using ZipEntry directly
|
|
36
36
|
export { ZipCopyNode } from './ZipCopyNode';
|
|
37
|
+
export { buildZipBufferSync, writeZipFileSync } from './buildZipBuffer';
|
|
38
|
+
export type {
|
|
39
|
+
ZipBufferMember,
|
|
40
|
+
ZipPrecompressedMember,
|
|
41
|
+
BuildZipBufferOptions,
|
|
42
|
+
} from './buildZipBuffer';
|
|
43
|
+
export { inflateZipPayloadSync } from './inflateZipPayload';
|
|
37
44
|
export type {
|
|
38
45
|
CopyOptions,
|
|
39
46
|
CopyResult,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Synchronous inflate for Node readers (store, raw deflate, native zstd).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { inflateRawSync } from 'zlib';
|
|
6
|
+
import { decompressSync } from './ZstdNode';
|
|
7
|
+
|
|
8
|
+
export function inflateZipPayloadSync(method: number, compressed: Buffer): Buffer {
|
|
9
|
+
if (method === 0) {
|
|
10
|
+
return Buffer.from(compressed);
|
|
11
|
+
}
|
|
12
|
+
if (method === 8) {
|
|
13
|
+
return inflateRawSync(compressed);
|
|
14
|
+
}
|
|
15
|
+
if (method === 93) {
|
|
16
|
+
return decompressSync(compressed);
|
|
17
|
+
}
|
|
18
|
+
throw new Error(`unsupported compression method ${method}`);
|
|
19
|
+
}
|
package/src/types/index.ts
CHANGED
|
@@ -8,8 +8,8 @@ export interface NeoZipKitInfo {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export const NEOZIPKIT_INFO: NeoZipKitInfo = {
|
|
11
|
-
version: '
|
|
12
|
-
releaseDate: '2026-
|
|
11
|
+
version: '1.5.0',
|
|
12
|
+
releaseDate: '2026-10-25'
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
// ============================================================================
|