neozipkit 0.2.1 → 0.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 (301) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +410 -186
  3. package/contracts/ABI.txt +889 -0
  4. package/contracts/Bytecode.txt +1 -0
  5. package/contracts/COMPILATION_NOTES.md +59 -0
  6. package/contracts/QUICK_DEPLOY.md +286 -0
  7. package/contracts/README.md +235 -131
  8. package/contracts/VERIFICATION.md +308 -0
  9. package/contracts/deploy-interactive.js +448 -0
  10. package/contracts/deployment-arbitrum-sepolia.json +12 -0
  11. package/contracts/deployment-arbitrum.json +12 -0
  12. package/contracts/deployment-eth-sepolia.json +12 -0
  13. package/contracts/hardhat.config.js +114 -0
  14. package/contracts/package.json +20 -0
  15. package/contracts/scripts/compile-and-extract.js +119 -0
  16. package/contracts/scripts/verify-contract.js +223 -0
  17. package/contracts/src/{NZIP-NFT-Private.sol → NZIP-NFT-v2.0.sol} +66 -151
  18. package/contracts/src/NZIP-NFT.sol +9 -15
  19. package/dist/blockchain/browser/TokenVerifierBrowser.d.ts +67 -0
  20. package/dist/blockchain/browser/TokenVerifierBrowser.d.ts.map +1 -0
  21. package/dist/blockchain/browser/TokenVerifierBrowser.js +313 -0
  22. package/dist/blockchain/browser/TokenVerifierBrowser.js.map +1 -0
  23. package/dist/blockchain/browser/WalletManagerBrowser.d.ts +98 -0
  24. package/dist/blockchain/browser/WalletManagerBrowser.d.ts.map +1 -0
  25. package/dist/blockchain/browser/WalletManagerBrowser.js +434 -0
  26. package/dist/blockchain/browser/WalletManagerBrowser.js.map +1 -0
  27. package/dist/blockchain/browser/ZipkitMinterBrowser.d.ts +58 -0
  28. package/dist/blockchain/browser/ZipkitMinterBrowser.d.ts.map +1 -0
  29. package/dist/blockchain/browser/ZipkitMinterBrowser.js +317 -0
  30. package/dist/blockchain/browser/ZipkitMinterBrowser.js.map +1 -0
  31. package/dist/blockchain/browser/index.d.ts +11 -0
  32. package/dist/blockchain/browser/index.d.ts.map +1 -0
  33. package/dist/blockchain/browser/index.js +16 -0
  34. package/dist/blockchain/browser/index.js.map +1 -0
  35. package/dist/blockchain/core/WalletManager.d.ts +124 -0
  36. package/dist/blockchain/core/WalletManager.d.ts.map +1 -0
  37. package/dist/blockchain/{ZipKitWallet.js → core/WalletManager.js} +157 -177
  38. package/dist/blockchain/core/WalletManager.js.map +1 -0
  39. package/dist/blockchain/{ZipKitMinter.d.ts → core/ZipkitMinter.d.ts} +46 -11
  40. package/dist/blockchain/core/ZipkitMinter.d.ts.map +1 -0
  41. package/dist/blockchain/core/ZipkitMinter.js +548 -0
  42. package/dist/blockchain/core/ZipkitMinter.js.map +1 -0
  43. package/dist/blockchain/{ZipKitOTS.d.ts → core/ZipkitOTS.d.ts} +6 -15
  44. package/dist/blockchain/core/ZipkitOTS.d.ts.map +1 -0
  45. package/dist/blockchain/{ZipKitOTS.js → core/ZipkitOTS.js} +26 -84
  46. package/dist/blockchain/core/ZipkitOTS.js.map +1 -0
  47. package/dist/blockchain/core/ZipkitVerifier.d.ts +177 -0
  48. package/dist/blockchain/core/ZipkitVerifier.d.ts.map +1 -0
  49. package/dist/blockchain/core/ZipkitVerifier.js +632 -0
  50. package/dist/blockchain/core/ZipkitVerifier.js.map +1 -0
  51. package/dist/blockchain/core/contracts.d.ts +83 -0
  52. package/dist/blockchain/core/contracts.d.ts.map +1 -0
  53. package/dist/blockchain/core/contracts.js +341 -0
  54. package/dist/blockchain/core/contracts.js.map +1 -0
  55. package/dist/blockchain/core/index.d.ts +12 -0
  56. package/dist/blockchain/core/index.d.ts.map +1 -0
  57. package/dist/blockchain/core/index.js +59 -0
  58. package/dist/blockchain/core/index.js.map +1 -0
  59. package/dist/blockchain/index.d.ts +11 -7
  60. package/dist/blockchain/index.d.ts.map +1 -0
  61. package/dist/blockchain/index.js +18 -34
  62. package/dist/blockchain/index.js.map +1 -0
  63. package/dist/blockchain/node/WalletManagerNode.d.ts +47 -0
  64. package/dist/blockchain/node/WalletManagerNode.d.ts.map +1 -0
  65. package/dist/blockchain/node/WalletManagerNode.js +203 -0
  66. package/dist/blockchain/node/WalletManagerNode.js.map +1 -0
  67. package/dist/blockchain/node/index.d.ts +12 -0
  68. package/dist/blockchain/node/index.d.ts.map +1 -0
  69. package/dist/blockchain/node/index.js +24 -0
  70. package/dist/blockchain/node/index.js.map +1 -0
  71. package/dist/browser/ZipkitBrowser.d.ts +27 -0
  72. package/dist/browser/ZipkitBrowser.d.ts.map +1 -0
  73. package/dist/browser/{ZipKitBrowser.js → ZipkitBrowser.js} +96 -43
  74. package/dist/browser/ZipkitBrowser.js.map +1 -0
  75. package/dist/browser/index.d.ts +12 -0
  76. package/dist/browser/index.d.ts.map +1 -0
  77. package/dist/browser/index.js +42 -0
  78. package/dist/browser/index.js.map +1 -0
  79. package/dist/browser-esm/index.d.ts +12 -0
  80. package/dist/browser-esm/index.js +59559 -0
  81. package/dist/browser-esm/index.js.map +7 -0
  82. package/dist/browser-umd/index.d.ts +12 -0
  83. package/dist/browser-umd/index.js +59576 -0
  84. package/dist/browser-umd/index.js.map +7 -0
  85. package/dist/browser-umd/index.min.js +63 -0
  86. package/dist/browser.d.ts +8 -14
  87. package/dist/browser.js +13 -28
  88. package/dist/core/ZipCompress.d.ts +99 -0
  89. package/dist/core/ZipCompress.d.ts.map +1 -0
  90. package/dist/core/ZipCompress.js +309 -0
  91. package/dist/core/ZipCompress.js.map +1 -0
  92. package/dist/core/ZipDecompress.d.ts +58 -0
  93. package/dist/core/ZipDecompress.d.ts.map +1 -0
  94. package/dist/core/ZipDecompress.js +172 -0
  95. package/dist/core/ZipDecompress.js.map +1 -0
  96. package/dist/{shared → core}/ZipEntry.d.ts +8 -1
  97. package/dist/core/ZipEntry.d.ts.map +1 -0
  98. package/dist/{shared → core}/ZipEntry.js +225 -82
  99. package/dist/core/ZipEntry.js.map +1 -0
  100. package/dist/core/Zipkit.d.ts +315 -0
  101. package/dist/core/Zipkit.d.ts.map +1 -0
  102. package/dist/core/Zipkit.js +647 -0
  103. package/dist/core/Zipkit.js.map +1 -0
  104. package/dist/core/components/HashCalculator.d.ts +138 -0
  105. package/dist/core/components/HashCalculator.d.ts.map +1 -0
  106. package/dist/core/components/HashCalculator.js +360 -0
  107. package/dist/core/components/HashCalculator.js.map +1 -0
  108. package/dist/core/components/Logger.d.ts +73 -0
  109. package/dist/core/components/Logger.d.ts.map +1 -0
  110. package/dist/core/components/Logger.js +156 -0
  111. package/dist/core/components/Logger.js.map +1 -0
  112. package/dist/core/components/ProgressTracker.d.ts +43 -0
  113. package/dist/core/components/ProgressTracker.d.ts.map +1 -0
  114. package/dist/core/components/ProgressTracker.js +112 -0
  115. package/dist/core/components/ProgressTracker.js.map +1 -0
  116. package/dist/{shared → core}/components/Support.d.ts +1 -0
  117. package/dist/core/components/Support.d.ts.map +1 -0
  118. package/dist/{shared → core}/components/Support.js +1 -0
  119. package/dist/core/components/Support.js.map +1 -0
  120. package/dist/{shared → core}/components/Util.d.ts +7 -2
  121. package/dist/core/components/Util.d.ts.map +1 -0
  122. package/dist/{shared → core}/components/Util.js +14 -34
  123. package/dist/core/components/Util.js.map +1 -0
  124. package/dist/{shared → core}/constants/Errors.d.ts +1 -2
  125. package/dist/core/constants/Errors.d.ts.map +1 -0
  126. package/dist/{shared → core}/constants/Errors.js +1 -3
  127. package/dist/core/constants/Errors.js.map +1 -0
  128. package/dist/{shared → core}/constants/Headers.d.ts +3 -0
  129. package/dist/core/constants/Headers.d.ts.map +1 -0
  130. package/dist/{shared → core}/constants/Headers.js +20 -17
  131. package/dist/core/constants/Headers.js.map +1 -0
  132. package/dist/core/encryption/Manager.d.ts +58 -0
  133. package/dist/core/encryption/Manager.d.ts.map +1 -0
  134. package/dist/core/encryption/Manager.js +121 -0
  135. package/dist/core/encryption/Manager.js.map +1 -0
  136. package/dist/core/encryption/ZipCrypto.d.ts +172 -0
  137. package/dist/core/encryption/ZipCrypto.d.ts.map +1 -0
  138. package/dist/core/encryption/ZipCrypto.js +554 -0
  139. package/dist/core/encryption/ZipCrypto.js.map +1 -0
  140. package/dist/core/encryption/index.d.ts +9 -0
  141. package/dist/core/encryption/index.d.ts.map +1 -0
  142. package/dist/core/encryption/index.js +17 -0
  143. package/dist/core/encryption/index.js.map +1 -0
  144. package/dist/core/encryption/types.d.ts +29 -0
  145. package/dist/core/encryption/types.d.ts.map +1 -0
  146. package/dist/core/encryption/types.js +12 -0
  147. package/dist/core/encryption/types.js.map +1 -0
  148. package/dist/core/index.d.ts +24 -0
  149. package/dist/core/index.d.ts.map +1 -0
  150. package/dist/core/index.js +57 -0
  151. package/dist/core/index.js.map +1 -0
  152. package/dist/core/version.d.ts +5 -0
  153. package/dist/core/version.d.ts.map +1 -0
  154. package/dist/core/version.js +31 -0
  155. package/dist/core/version.js.map +1 -0
  156. package/dist/index.d.ts +8 -18
  157. package/dist/index.d.ts.map +1 -0
  158. package/dist/index.js +29 -35
  159. package/dist/index.js.map +1 -0
  160. package/dist/node/ZipCompressNode.d.ts +123 -0
  161. package/dist/node/ZipCompressNode.d.ts.map +1 -0
  162. package/dist/node/ZipCompressNode.js +576 -0
  163. package/dist/node/ZipCompressNode.js.map +1 -0
  164. package/dist/node/ZipDecompressNode.d.ts +120 -0
  165. package/dist/node/ZipDecompressNode.d.ts.map +1 -0
  166. package/dist/node/ZipDecompressNode.js +461 -0
  167. package/dist/node/ZipDecompressNode.js.map +1 -0
  168. package/dist/node/ZipkitNode.d.ts +406 -0
  169. package/dist/node/ZipkitNode.d.ts.map +1 -0
  170. package/dist/node/ZipkitNode.js +1305 -0
  171. package/dist/node/ZipkitNode.js.map +1 -0
  172. package/dist/node/index.d.ts +26 -0
  173. package/dist/node/index.d.ts.map +1 -0
  174. package/dist/node/index.js +65 -0
  175. package/dist/node/index.js.map +1 -0
  176. package/dist/types/index.d.ts +59 -0
  177. package/dist/types/index.d.ts.map +1 -0
  178. package/dist/types/index.js +36 -1
  179. package/dist/types/index.js.map +1 -0
  180. package/examples/README.md +269 -0
  181. package/examples/blockchain-tokenize.ts +354 -0
  182. package/examples/create-zip.ts +172 -0
  183. package/examples/extract-zip.ts +158 -0
  184. package/examples/list-zip.ts +161 -0
  185. package/examples/test-files/data.json +116 -0
  186. package/examples/test-files/document.md +80 -0
  187. package/examples/test-files/document.txt +2 -0
  188. package/examples/test-files/file1.txt +48 -0
  189. package/examples/test-files/file2.txt +80 -0
  190. package/examples/tsconfig.json +44 -0
  191. package/examples/unzip-token.ts +491 -0
  192. package/examples/verify-tokenized-zip.ts +305 -0
  193. package/examples/zip-token.ts +604 -0
  194. package/package.json +108 -24
  195. package/src/blockchain/README.md +59 -66
  196. package/src/{browser/TokenVerifierBrowser.ts-remove → blockchain/browser/TokenVerifierBrowser.ts} +125 -79
  197. package/src/blockchain/browser/WalletManagerBrowser.ts +486 -0
  198. package/src/blockchain/browser/ZipkitMinterBrowser.ts +400 -0
  199. package/src/blockchain/browser/index.ts +13 -0
  200. package/src/blockchain/{ZipKitWallet.ts → core/WalletManager.ts} +241 -143
  201. package/src/blockchain/core/ZipkitMinter.ts +716 -0
  202. package/src/blockchain/{ZipKitOTS.ts → core/ZipkitOTS.ts} +28 -62
  203. package/src/blockchain/core/ZipkitVerifier.ts +853 -0
  204. package/src/blockchain/core/contracts.ts +370 -0
  205. package/src/blockchain/core/index.ts +71 -0
  206. package/src/blockchain/index.ts +21 -55
  207. package/src/blockchain/node/WalletManagerNode.ts +171 -0
  208. package/src/blockchain/node/index.ts +40 -0
  209. package/src/browser/{ZipKitBrowser.ts → ZipkitBrowser.ts} +104 -41
  210. package/src/browser/index.esm.ts +37 -0
  211. package/src/browser/index.ts +25 -0
  212. package/src/core/ZipCompress.ts +398 -0
  213. package/src/core/ZipDecompress.ts +212 -0
  214. package/src/{shared → core}/ZipEntry.ts +240 -90
  215. package/src/core/Zipkit.ts +794 -0
  216. package/src/core/components/HashCalculator.ts +384 -0
  217. package/src/core/components/Logger.ts +180 -0
  218. package/src/core/components/ProgressTracker.ts +134 -0
  219. package/src/{shared → core}/components/Util.ts +12 -34
  220. package/src/{shared → core}/constants/Errors.ts +0 -4
  221. package/src/{shared → core}/constants/Headers.ts +19 -17
  222. package/src/core/encryption/Manager.ts +137 -0
  223. package/src/core/encryption/ZipCrypto.ts +650 -0
  224. package/src/core/encryption/index.ts +15 -0
  225. package/src/core/encryption/types.ts +33 -0
  226. package/src/core/index.ts +34 -0
  227. package/src/core/version.ts +33 -0
  228. package/src/index.ts +54 -25
  229. package/src/node/ZipCompressNode.ts +632 -0
  230. package/src/node/ZipDecompressNode.ts +508 -0
  231. package/src/node/ZipkitNode.ts +1559 -0
  232. package/src/node/index.ts +69 -0
  233. package/src/types/index.ts +131 -1
  234. package/src/types/modules.d.ts +1 -11
  235. package/src/types/opentimestamps.d.ts +1 -23
  236. package/contracts/INTEGRATION.md +0 -338
  237. package/contracts/NETWORKS.md +0 -156
  238. package/contracts/NZIP-NFT-bytecode.txt +0 -1
  239. package/contracts/constants.ts +0 -952
  240. package/contracts/deploy-bytecode.mjs +0 -94
  241. package/dist/blockchain/CertificateToken.d.ts +0 -48
  242. package/dist/blockchain/CertificateToken.js +0 -386
  243. package/dist/blockchain/ZipKitMinter.js +0 -347
  244. package/dist/blockchain/ZipKitNFT.d.ts +0 -3
  245. package/dist/blockchain/ZipKitNFT.js +0 -7
  246. package/dist/blockchain/ZipKitVerifier.d.ts +0 -69
  247. package/dist/blockchain/ZipKitVerifier.js +0 -289
  248. package/dist/blockchain/ZipKitWallet.d.ts +0 -69
  249. package/dist/blockchain/contracts.d.ts +0 -14
  250. package/dist/blockchain/contracts.js +0 -77
  251. package/dist/blockchain/types.d.ts +0 -47
  252. package/dist/blockchain/types.js +0 -30
  253. package/dist/browser/ZipKitBrowser.d.ts +0 -18
  254. package/dist/browser/ZipKitStampBrowser.d.ts +0 -2
  255. package/dist/browser/ZipKitStampBrowser.js +0 -12
  256. package/dist/browser-index.d.ts +0 -18
  257. package/dist/browser-index.js +0 -57
  258. package/dist/server/ZipKitServer.d.ts +0 -74
  259. package/dist/server/ZipKitServer.js +0 -310
  260. package/dist/shared/ZipKit.d.ts +0 -243
  261. package/dist/shared/ZipKit.js +0 -772
  262. package/dist/shared/components/CertificateOTS.d.ts +0 -15
  263. package/dist/shared/components/CertificateOTS.js +0 -116
  264. package/dist/shared/components/FileSys.d.ts +0 -2
  265. package/dist/shared/components/FileSys.js +0 -113
  266. package/dist/shared/components/HashAccumulator.d.ts +0 -55
  267. package/dist/shared/components/HashAccumulator.js +0 -100
  268. package/dist/shared/components/ZipCrypto.d.ts +0 -3
  269. package/dist/shared/components/ZipCrypto.js +0 -78
  270. package/dist/shared/components/merkleTree.d.ts +0 -66
  271. package/dist/shared/components/merkleTree.js +0 -136
  272. package/dist/shared/types/index.d.ts +0 -21
  273. package/dist/shared/types/index.js +0 -3
  274. package/dist/shared/types.d.ts +0 -33
  275. package/dist/shared/types.js +0 -8
  276. package/dist/shared/version.d.ts +0 -2
  277. package/dist/shared/version.js +0 -6
  278. package/src/blockchain/CertificateToken.ts +0 -444
  279. package/src/blockchain/ZipKitMinter.ts +0 -470
  280. package/src/blockchain/ZipKitNFT.ts +0 -12
  281. package/src/blockchain/ZipKitVerifier.ts +0 -382
  282. package/src/blockchain/contracts.ts +0 -88
  283. package/src/blockchain/types.ts +0 -91
  284. package/src/browser/CertificateGeneratorBrowser.ts-remove +0 -498
  285. package/src/browser/ZipKitStampBrowser.ts +0 -9
  286. package/src/browser-index.ts +0 -36
  287. package/src/server/README.md +0 -25
  288. package/src/server/ZipKitServer.ts +0 -305
  289. package/src/shared/ZipKit.ts +0 -881
  290. package/src/shared/components/CertificateOTS.ts +0 -132
  291. package/src/shared/components/FileSys.ts +0 -87
  292. package/src/shared/components/HashAccumulator.ts +0 -113
  293. package/src/shared/components/ZipCrypto.ts +0 -91
  294. package/src/shared/components/merkleTree.ts +0 -162
  295. package/src/shared/types/index.ts +0 -25
  296. package/src/shared/types/modules.d.ts +0 -22
  297. package/src/shared/types/opentimestamps.d.ts +0 -23
  298. package/src/shared/types.d.ts +0 -8
  299. package/src/shared/types.ts +0 -43
  300. package/src/shared/version.ts +0 -3
  301. /package/src/{shared → core}/components/Support.ts +0 -0
@@ -0,0 +1,1305 @@
1
+ "use strict";
2
+ // ======================================
3
+ // ZipkitNode.ts - Node.js File-Based ZIP Operations
4
+ // Copyright (c) 2025 NeoWare, Inc. All rights reserved.
5
+ // ======================================
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || (function () {
23
+ var ownKeys = function(o) {
24
+ ownKeys = Object.getOwnPropertyNames || function (o) {
25
+ var ar = [];
26
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
27
+ return ar;
28
+ };
29
+ return ownKeys(o);
30
+ };
31
+ return function (mod) {
32
+ if (mod && mod.__esModule) return mod;
33
+ var result = {};
34
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
35
+ __setModuleDefault(result, mod);
36
+ return result;
37
+ };
38
+ })();
39
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
40
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
41
+ };
42
+ var __importDefault = (this && this.__importDefault) || function (mod) {
43
+ return (mod && mod.__esModule) ? mod : { "default": mod };
44
+ };
45
+ Object.defineProperty(exports, "__esModule", { value: true });
46
+ exports.Errors = exports.ZipEntry = void 0;
47
+ const core_1 = __importDefault(require("../core"));
48
+ const ZipEntry_1 = __importDefault(require("../core/ZipEntry"));
49
+ exports.ZipEntry = ZipEntry_1.default;
50
+ const Errors_1 = __importDefault(require("../core/constants/Errors"));
51
+ exports.Errors = Errors_1.default;
52
+ const ZipCompressNode_1 = require("./ZipCompressNode");
53
+ const ZipDecompressNode_1 = require("./ZipDecompressNode");
54
+ const Headers_1 = require("../core/constants/Headers");
55
+ const fs = __importStar(require("fs"));
56
+ const path = __importStar(require("path"));
57
+ const minimatch_1 = require("minimatch");
58
+ // Re-export everything from core Zipkit
59
+ __exportStar(require("../core"), exports);
60
+ // ======================================
61
+ // ZipkitNode
62
+ // ======================================
63
+ /**
64
+ * ZipkitNode - Node.js file-based ZIP operations
65
+ *
66
+ * Extends Zipkit to provide file I/O operations for Node.js environments.
67
+ * Similar to ZipkitBrowser which provides Blob operations for browser environments.
68
+ *
69
+ * @example
70
+ * ```typescript
71
+ * const zip = new ZipkitNode();
72
+ * await zip.loadZipFile('archive.zip');
73
+ * await zip.extractToFile(entry, './output/file.txt');
74
+ * ```
75
+ */
76
+ class ZipkitNode extends core_1.default {
77
+ // Note: centralDirSize and centralDirOffset are inherited from Zipkit base class
78
+ constructor(config) {
79
+ super(config);
80
+ // Override _zipkitCmp to use ZipCompressNode instead of ZipCompress (lazy-loaded)
81
+ this._zipkitCmpNode = null;
82
+ // Override _zipkitDeCmp to use ZipDecompressNode instead of ZipDecompress (lazy-loaded)
83
+ this._zipkitDeCmpNode = null;
84
+ // File-based ZIP loading properties (merged from ZipLoadEntriesServer)
85
+ this.fileHandle = null;
86
+ this.filePath = null;
87
+ this.fileSize = 0;
88
+ // Note: ZipCompressNode and ZipDecompressNode are lazy-loaded when first accessed
89
+ // They will override the base class _zipkitCmp and _zipkitDeCmp on first access
90
+ }
91
+ /**
92
+ * Lazy-load ZipCompressNode instance and override base class _zipkitCmp
93
+ * @returns ZipCompressNode instance (created on first access)
94
+ */
95
+ getZipCompressNode() {
96
+ if (!this._zipkitCmpNode) {
97
+ this._zipkitCmpNode = new ZipCompressNode_1.ZipCompressNode(this);
98
+ // Override the base class _zipkitCmp with ZipCompressNode
99
+ const zipkit = this;
100
+ zipkit._zipkitCmp = this._zipkitCmpNode;
101
+ }
102
+ return this._zipkitCmpNode;
103
+ }
104
+ /**
105
+ * Lazy-load ZipDecompressNode instance and override base class _zipkitDeCmp
106
+ * @returns ZipDecompressNode instance (created on first access)
107
+ */
108
+ getZipDecompressNode() {
109
+ if (!this._zipkitDeCmpNode) {
110
+ this._zipkitDeCmpNode = new ZipDecompressNode_1.ZipDecompressNode(this);
111
+ // Override the base class _zipkitDeCmp with ZipDecompressNode
112
+ const zipkit = this;
113
+ zipkit._zipkitDeCmp = this._zipkitDeCmpNode;
114
+ }
115
+ return this._zipkitDeCmpNode;
116
+ }
117
+ // ============================================================================
118
+ // File Loading Methods
119
+ // ============================================================================
120
+ /**
121
+ * Load ZIP file from file path (streaming mode)
122
+ *
123
+ * **Required**: You must call this method before calling `getDirectory()` or any other ZIP operations.
124
+ * This method:
125
+ * 1. Resets all ZIP data
126
+ * 2. Opens the file handle
127
+ * 3. Loads EOCD and parses central directory
128
+ * 4. Populates this.zipEntries[] array
129
+ *
130
+ * @param filePath - Path to the ZIP file to load
131
+ * @returns Promise<ZipEntry[]> Array of all entries in the ZIP file
132
+ * @throws Error if Node.js environment not available
133
+ */
134
+ async loadZipFile(filePath) {
135
+ // Access private members via type assertion (ZipkitServer extends Zipkit)
136
+ const zipkit = this;
137
+ zipkit.resetZipData();
138
+ // Reset file-based data
139
+ this.resetFileData();
140
+ this.filePath = filePath;
141
+ // Open file handle
142
+ this.fileHandle = await this.openFileHandle(filePath);
143
+ const stats = await this.fileHandle.stat();
144
+ this.fileSize = stats.size;
145
+ // Load EOCD to get central directory info (sets zipComment internally)
146
+ await this.loadEOCD();
147
+ // Load central directory in chunks
148
+ const entries = [];
149
+ let offset = zipkit.centralDirOffset;
150
+ let remaining = zipkit.centralDirSize;
151
+ const bufferSize = this.getBufferSize();
152
+ while (remaining > 0) {
153
+ const currentBufferSize = Math.min(bufferSize, remaining);
154
+ const chunk = Buffer.alloc(currentBufferSize);
155
+ await this.fileHandle.read(chunk, 0, currentBufferSize, offset);
156
+ // Parse entries from chunk
157
+ let chunkOffset = 0;
158
+ while (chunkOffset < chunk.length) {
159
+ if (chunk.readUInt32LE(chunkOffset) !== Headers_1.CENTRAL_DIR.SIGNATURE) {
160
+ break; // End of central directory
161
+ }
162
+ // Parse central directory entry
163
+ const entry = new ZipEntry_1.default(null, null, false);
164
+ const entryData = chunk.subarray(chunkOffset);
165
+ const remainingData = entry.readZipEntry(entryData);
166
+ entries.push(entry);
167
+ // Move to next entry
168
+ chunkOffset += (entryData.length - remainingData.length);
169
+ }
170
+ offset += currentBufferSize;
171
+ remaining -= currentBufferSize;
172
+ }
173
+ // Store entries in zipEntries[] array (single source of truth)
174
+ this.zipEntries = entries;
175
+ return entries;
176
+ }
177
+ /**
178
+ * Alias for loadZipFile() for consistency
179
+ * @param filePath - Path to the ZIP file to load
180
+ * @returns Promise<ZipEntry[]> Array of all entries in the ZIP file
181
+ */
182
+ async loadZipFromFile(filePath) {
183
+ return this.loadZipFile(filePath);
184
+ }
185
+ // ============================================================================
186
+ // File Extraction Methods
187
+ // ============================================================================
188
+ /**
189
+ * Extract file directly to disk with true streaming (no memory buffering)
190
+ * Wrapper for ZipDecompress.extractToFile()
191
+ *
192
+ * Note: ZSTD codec is lazily initialized on first use (module-level singleton).
193
+ * Initialization happens automatically when needed.
194
+ *
195
+ * @param entry - ZIP entry to extract
196
+ * @param outputPath - Path where the file should be written
197
+ * @param options - Optional extraction options:
198
+ * - skipHashCheck: Skip hash verification (default: false)
199
+ * - onProgress: Callback function receiving bytes extracted as parameter
200
+ * @returns Promise that resolves when extraction is complete
201
+ * @throws Error if not a File-based ZIP
202
+ */
203
+ async extractToFile(entry, outputPath, options) {
204
+ return this.getZipDecompressNode().extractToFile(entry, outputPath, options);
205
+ }
206
+ /**
207
+ * Alias for extractToFile() for consistency
208
+ * @param entry - ZIP entry to extract
209
+ * @param outputPath - Path where the file should be written
210
+ * @param options - Optional extraction options
211
+ * @returns Promise that resolves when extraction is complete
212
+ */
213
+ async extractEntryToFile(entry, outputPath, options) {
214
+ return this.extractToFile(entry, outputPath, options);
215
+ }
216
+ // ============================================================================
217
+ // File-Based Compression Methods (ZipCompressNode wrappers)
218
+ // ============================================================================
219
+ /**
220
+ * Compress data for a ZIP entry (Buffer-based)
221
+ * Override to use ZipCompressNode instead of ZipCompress
222
+ *
223
+ * @param entry - ZIP entry to compress
224
+ * @param data - Buffer containing data to compress
225
+ * @param options - Compression options
226
+ * @param onOutputBuffer - Optional callback for streaming output
227
+ * @returns Promise resolving to Buffer containing compressed data
228
+ */
229
+ async compressData(entry, data, options, onOutputBuffer) {
230
+ return this.getZipCompressNode().compressData(entry, data, options, onOutputBuffer);
231
+ }
232
+ /**
233
+ * Compress a file from disk
234
+ * Wrapper for ZipCompressNode.compressFile()
235
+ *
236
+ * @param filePath - Path to the file to compress
237
+ * @param entry - ZIP entry to compress (filename should already be set)
238
+ * @param options - Optional compression options
239
+ * @returns Promise resolving to Buffer containing compressed data
240
+ */
241
+ async compressFile(filePath, entry, options) {
242
+ return this.getZipCompressNode().compressFile(filePath, entry, options);
243
+ }
244
+ /**
245
+ * Compress a file from disk using streaming for large files
246
+ * Wrapper for ZipCompressNode.compressFileStream()
247
+ *
248
+ * @param filePath - Path to the file to compress
249
+ * @param entry - ZIP entry to compress (filename should already be set)
250
+ * @param options - Optional compression options
251
+ * @param onOutputBuffer - Optional callback for streaming output
252
+ * @returns Promise resolving to Buffer containing compressed data
253
+ */
254
+ async compressFileStream(filePath, entry, options, onOutputBuffer) {
255
+ return this.getZipCompressNode().compressFileStream(filePath, entry, options, onOutputBuffer);
256
+ }
257
+ /**
258
+ * Extract all entries from ZIP to a directory
259
+ *
260
+ * @param outputDir - Directory where files should be extracted
261
+ * @param options - Optional extraction options:
262
+ * - skipHashCheck: Skip hash verification (default: false)
263
+ * - onProgress: Callback function receiving (entry, bytes) as parameters
264
+ * - preservePaths: Preserve directory structure (default: true)
265
+ * @returns Promise that resolves when all extractions are complete
266
+ * @throws Error if not a File-based ZIP
267
+ */
268
+ async extractAll(outputDir, options) {
269
+ const entries = this.zipEntries;
270
+ const preservePaths = options?.preservePaths !== false;
271
+ // Ensure output directory exists
272
+ if (!fs.existsSync(outputDir)) {
273
+ fs.mkdirSync(outputDir, { recursive: true });
274
+ }
275
+ for (const entry of entries) {
276
+ if (!entry.filename)
277
+ continue;
278
+ // Determine output path
279
+ let outputPath;
280
+ if (preservePaths) {
281
+ // Preserve directory structure
282
+ outputPath = path.join(outputDir, entry.filename);
283
+ // Create parent directories if needed
284
+ const parentDir = path.dirname(outputPath);
285
+ if (!fs.existsSync(parentDir)) {
286
+ fs.mkdirSync(parentDir, { recursive: true });
287
+ }
288
+ }
289
+ else {
290
+ // Extract to flat structure (filename only)
291
+ const filename = path.basename(entry.filename);
292
+ outputPath = path.join(outputDir, filename);
293
+ }
294
+ // Extract entry
295
+ await this.extractToFile(entry, outputPath, {
296
+ skipHashCheck: options?.skipHashCheck,
297
+ onProgress: options?.onProgress ? (bytes) => options.onProgress(entry, bytes) : undefined
298
+ });
299
+ }
300
+ }
301
+ // ============================================================================
302
+ // ZIP File Creation Subfunctions
303
+ // ============================================================================
304
+ /**
305
+ * Initialize ZIP file for writing
306
+ * Creates output file with seek capability and returns writer object
307
+ *
308
+ * @param outputPath - Path where the ZIP file should be created
309
+ * @returns Promise resolving to ZipFileWriter object
310
+ */
311
+ async initializeZipFile(outputPath) {
312
+ // Ensure parent directory exists
313
+ const parentDir = path.dirname(outputPath);
314
+ if (parentDir && parentDir !== '.' && !fs.existsSync(parentDir)) {
315
+ fs.mkdirSync(parentDir, { recursive: true });
316
+ }
317
+ // Open file for writing with seek capability
318
+ const outputFd = fs.openSync(outputPath, 'w+');
319
+ const outputStream = fs.createWriteStream(outputPath);
320
+ return {
321
+ outputFd,
322
+ outputStream,
323
+ currentPosition: 0,
324
+ entryPositions: new Map()
325
+ };
326
+ }
327
+ /**
328
+ * Prepare ZipEntry from file path
329
+ * Validates file exists and creates entry with metadata from file stats
330
+ *
331
+ * @param filePath - Path to the file
332
+ * @param entryName - Optional entry name (defaults to basename)
333
+ * @returns Promise resolving to ZipEntry ready for compression
334
+ */
335
+ async prepareEntryFromFile(filePath, entryName) {
336
+ if (!fs.existsSync(filePath)) {
337
+ throw new Error(`File not found: ${filePath}`);
338
+ }
339
+ const stats = fs.statSync(filePath);
340
+ if (!stats.isFile()) {
341
+ throw new Error(`Path is not a file: ${filePath}`);
342
+ }
343
+ // Use provided entry name or default to basename
344
+ const name = entryName || path.basename(filePath);
345
+ const entry = this.createZipEntry(name);
346
+ // Set entry metadata from file stats
347
+ entry.uncompressedSize = stats.size;
348
+ entry.timeDateDOS = entry.setDateTime(stats.mtime);
349
+ entry.lastModTimeDate = entry.timeDateDOS;
350
+ return entry;
351
+ }
352
+ /**
353
+ * Write a ZIP entry to the file
354
+ * Handles sequential write: header (placeholder) → compress → data → update header
355
+ *
356
+ * @param writer - ZipFileWriter object
357
+ * @param entry - ZipEntry to write
358
+ * @param filePath - Path to source file
359
+ * @param options - Optional compression options
360
+ * @param callbacks - Optional callbacks for progress and hash calculation
361
+ * @returns Promise that resolves when entry is written
362
+ */
363
+ async writeZipEntry(writer, entry, filePath, options, callbacks) {
364
+ // Set compression method based on options
365
+ const level = options?.level ?? 6;
366
+ if (level === 0) {
367
+ entry.cmpMethod = 0; // STORED
368
+ }
369
+ else if (options?.useZstd !== false) {
370
+ entry.cmpMethod = 93; // ZSTD
371
+ }
372
+ else {
373
+ entry.cmpMethod = 8; // DEFLATED
374
+ }
375
+ // Step 1: Create local header with placeholder compressed size (0)
376
+ entry.compressedSize = 0; // Placeholder - will be updated after compression
377
+ entry.localHdrOffset = writer.currentPosition;
378
+ const localHeader = entry.createLocalHdr();
379
+ // Step 2: Write local header to file
380
+ await new Promise((resolve, reject) => {
381
+ writer.outputStream.write(localHeader, (error) => {
382
+ if (error) {
383
+ reject(error);
384
+ }
385
+ else {
386
+ writer.currentPosition += localHeader.length;
387
+ writer.entryPositions.set(entry.filename || '', entry.localHdrOffset);
388
+ resolve();
389
+ }
390
+ });
391
+ });
392
+ // Step 3: Compress file and write data
393
+ const bufferSize = options?.bufferSize || this.getBufferSize();
394
+ const useZstd = options?.useZstd !== false;
395
+ const shouldUseChunked = !useZstd && entry.uncompressedSize && entry.uncompressedSize > bufferSize;
396
+ if (shouldUseChunked) {
397
+ // Use streaming compression for large files
398
+ // Data is written directly via onOutputBuffer callback
399
+ const onOutputBuffer = async (data) => {
400
+ await new Promise((resolve, reject) => {
401
+ writer.outputStream.write(data, (error) => {
402
+ if (error) {
403
+ reject(error);
404
+ }
405
+ else {
406
+ writer.currentPosition += data.length;
407
+ if (callbacks?.onProgress) {
408
+ callbacks.onProgress(entry, data.length);
409
+ }
410
+ resolve();
411
+ }
412
+ });
413
+ });
414
+ };
415
+ // compressFileStream will set entry.compressedSize and entry.crc
416
+ await this.compressFileStream(filePath, entry, options, onOutputBuffer);
417
+ }
418
+ else {
419
+ // Use regular buffer compression for small files
420
+ // compressFile will set entry.compressedSize and entry.crc
421
+ const compressedData = await this.compressFile(filePath, entry, options);
422
+ // Write compressed data to file
423
+ await new Promise((resolve, reject) => {
424
+ writer.outputStream.write(compressedData, (error) => {
425
+ if (error) {
426
+ reject(error);
427
+ }
428
+ else {
429
+ writer.currentPosition += compressedData.length;
430
+ if (callbacks?.onProgress) {
431
+ callbacks.onProgress(entry, compressedData.length);
432
+ }
433
+ resolve();
434
+ }
435
+ });
436
+ });
437
+ }
438
+ // Step 4: Update compressed size and CRC in local header
439
+ // entry.compressedSize and entry.crc are set by compression methods
440
+ if (entry.compressedSize === undefined) {
441
+ throw new Error(`Compressed size not set for entry: ${entry.filename}`);
442
+ }
443
+ const compressedSizeOffset = entry.localHdrOffset + 18;
444
+ const sizeBuffer = Buffer.alloc(4);
445
+ sizeBuffer.writeUInt32LE(entry.compressedSize, 0);
446
+ fs.writeSync(writer.outputFd, sizeBuffer, 0, 4, compressedSizeOffset);
447
+ if (entry.crc !== undefined) {
448
+ const crcOffset = entry.localHdrOffset + 14;
449
+ const crcBuffer = Buffer.alloc(4);
450
+ crcBuffer.writeUInt32LE(entry.crc, 0);
451
+ fs.writeSync(writer.outputFd, crcBuffer, 0, 4, crcOffset);
452
+ }
453
+ // Call hash callback if provided
454
+ if (callbacks?.onHashCalculated && entry.sha256) {
455
+ const hashBuffer = Buffer.from(entry.sha256, 'hex');
456
+ callbacks.onHashCalculated(entry, hashBuffer);
457
+ }
458
+ }
459
+ /**
460
+ * Write central directory entries to ZIP file
461
+ *
462
+ * @param writer - ZipFileWriter object
463
+ * @param entries - Array of ZipEntry objects
464
+ * @param options - Optional options for archive comment and progress
465
+ * @returns Promise resolving to central directory size in bytes
466
+ */
467
+ async writeCentralDirectory(writer, entries, options) {
468
+ const centralDirStart = writer.currentPosition;
469
+ // Update entry local header offsets from tracked positions
470
+ for (const entry of entries) {
471
+ const actualPosition = writer.entryPositions.get(entry.filename || '');
472
+ if (actualPosition !== undefined) {
473
+ entry.localHdrOffset = actualPosition;
474
+ }
475
+ }
476
+ // Write central directory entries
477
+ for (const entry of entries) {
478
+ const centralDirEntry = entry.centralDirEntry();
479
+ await new Promise((resolve, reject) => {
480
+ writer.outputStream.write(centralDirEntry, (error) => {
481
+ if (error) {
482
+ reject(error);
483
+ }
484
+ else {
485
+ writer.currentPosition += centralDirEntry.length;
486
+ if (options?.onProgress) {
487
+ options.onProgress(entry);
488
+ }
489
+ resolve();
490
+ }
491
+ });
492
+ });
493
+ }
494
+ return writer.currentPosition - centralDirStart;
495
+ }
496
+ /**
497
+ * Write End of Central Directory record
498
+ *
499
+ * @param writer - ZipFileWriter object
500
+ * @param totalEntries - Total number of entries in ZIP
501
+ * @param centralDirSize - Size of central directory in bytes
502
+ * @param centralDirOffset - Offset to start of central directory
503
+ * @param archiveComment - Optional archive comment (max 65535 bytes)
504
+ * @returns Promise that resolves when EOCD is written
505
+ */
506
+ async writeEndOfCentralDirectory(writer, totalEntries, centralDirSize, centralDirOffset, archiveComment) {
507
+ const comment = archiveComment || '';
508
+ const commentBytes = Buffer.from(comment, 'utf8');
509
+ const commentLength = Math.min(commentBytes.length, 0xFFFF); // Max 65535 bytes
510
+ const buffer = Buffer.alloc(22 + commentLength);
511
+ let offset = 0;
512
+ // End of central directory signature (4 bytes)
513
+ buffer.writeUInt32LE(0x06054b50, offset);
514
+ offset += 4;
515
+ // Number of this disk (2 bytes)
516
+ buffer.writeUInt16LE(0, offset);
517
+ offset += 2;
518
+ // Number of the disk with the start of the central directory (2 bytes)
519
+ buffer.writeUInt16LE(0, offset);
520
+ offset += 2;
521
+ // Total number of entries in the central directory on this disk (2 bytes)
522
+ buffer.writeUInt16LE(totalEntries, offset);
523
+ offset += 2;
524
+ // Total number of entries in the central directory (2 bytes)
525
+ buffer.writeUInt16LE(totalEntries, offset);
526
+ offset += 2;
527
+ // Size of the central directory (4 bytes)
528
+ buffer.writeUInt32LE(centralDirSize, offset);
529
+ offset += 4;
530
+ // Offset of start of central directory with respect to the starting disk number (4 bytes)
531
+ buffer.writeUInt32LE(centralDirOffset, offset);
532
+ offset += 4;
533
+ // ZIP file comment length (2 bytes)
534
+ buffer.writeUInt16LE(commentLength, offset);
535
+ offset += 2;
536
+ // ZIP file comment (variable length)
537
+ if (commentLength > 0) {
538
+ commentBytes.copy(buffer, offset, 0, commentLength);
539
+ }
540
+ // Write EOCD to file
541
+ await new Promise((resolve, reject) => {
542
+ writer.outputStream.write(buffer, (error) => {
543
+ if (error) {
544
+ reject(error);
545
+ }
546
+ else {
547
+ writer.currentPosition += buffer.length;
548
+ resolve();
549
+ }
550
+ });
551
+ });
552
+ }
553
+ /**
554
+ * Finalize ZIP file by closing handles
555
+ *
556
+ * @param writer - ZipFileWriter object
557
+ * @returns Promise that resolves when file is closed
558
+ */
559
+ async finalizeZipFile(writer) {
560
+ // Close file descriptor
561
+ fs.closeSync(writer.outputFd);
562
+ // Close write stream
563
+ return new Promise((resolve, reject) => {
564
+ writer.outputStream.end((error) => {
565
+ if (error) {
566
+ reject(error);
567
+ }
568
+ else {
569
+ resolve();
570
+ }
571
+ });
572
+ });
573
+ }
574
+ // ============================================================================
575
+ // File Creation Methods
576
+ // ============================================================================
577
+ /**
578
+ * Create a ZIP file from multiple file paths
579
+ * Simple API that uses the modular subfunctions
580
+ *
581
+ * @param filePaths - Array of file paths to add to ZIP
582
+ * @param outputPath - Path where the ZIP file should be created
583
+ * @param options - Optional compression options
584
+ * @returns Promise that resolves when ZIP creation is complete
585
+ */
586
+ async createZipFromFiles(filePaths, outputPath, options) {
587
+ // Initialize ZIP file
588
+ const writer = await this.initializeZipFile(outputPath);
589
+ try {
590
+ // Process each file
591
+ for (const filePath of filePaths) {
592
+ // Validate and create entry
593
+ const entry = await this.prepareEntryFromFile(filePath);
594
+ // Write entry to ZIP
595
+ await this.writeZipEntry(writer, entry, filePath, options);
596
+ }
597
+ // Write central directory
598
+ const entries = this.getDirectory();
599
+ const centralDirOffset = writer.currentPosition;
600
+ const centralDirSize = await this.writeCentralDirectory(writer, entries);
601
+ // Write EOCD
602
+ await this.writeEndOfCentralDirectory(writer, entries.length, centralDirSize, centralDirOffset);
603
+ }
604
+ finally {
605
+ await this.finalizeZipFile(writer);
606
+ }
607
+ }
608
+ /**
609
+ * Add a file to the current ZIP
610
+ *
611
+ * @param filePath - Path to the file to add
612
+ * @param entryName - Name to use in ZIP (defaults to filename)
613
+ * @param options - Optional compression options
614
+ * @returns Promise resolving to the created ZipEntry
615
+ */
616
+ async addFileToZip(filePath, entryName, options) {
617
+ // Use provided entry name or derive from file path
618
+ const name = entryName || path.basename(filePath);
619
+ const entry = this.createZipEntry(name);
620
+ // Use ZipCompressNode.compressFile() which handles file I/O and compression
621
+ await this.getZipCompressNode().compressFile(filePath, entry, options);
622
+ // Add to entries
623
+ this.zipEntries.push(entry);
624
+ return entry;
625
+ }
626
+ // ============================================================================
627
+ // File Management Methods
628
+ // ============================================================================
629
+ /**
630
+ * Get underlying file handle for advanced operations
631
+ *
632
+ * @returns StreamingFileHandle if file is loaded
633
+ * @throws Error if file handle not available
634
+ */
635
+ getFileHandle() {
636
+ if (!this.fileHandle) {
637
+ throw new Error('File handle not available');
638
+ }
639
+ return this.fileHandle;
640
+ }
641
+ /**
642
+ * Close file handle explicitly
643
+ *
644
+ * @returns Promise that resolves when file is closed
645
+ */
646
+ async closeFile() {
647
+ if (this.fileHandle) {
648
+ await this.fileHandle.close();
649
+ this.fileHandle = null;
650
+ }
651
+ }
652
+ /**
653
+ * Copy entry from another ZIP (compatibility method)
654
+ * Reads the local header and compressed data from the file and returns it as a Buffer
655
+ * This is used when updating an existing ZIP file to copy unchanged entries
656
+ *
657
+ * @param entry - ZIP entry to copy
658
+ * @returns Promise resolving to Buffer containing local header + compressed data
659
+ * @throws Error if file handle not available
660
+ */
661
+ async copyEntry(entry) {
662
+ if (!this.fileHandle) {
663
+ throw new Error('File handle not available');
664
+ }
665
+ // Read local file header (30 bytes)
666
+ const localHeaderBuffer = Buffer.alloc(Headers_1.LOCAL_HDR.SIZE);
667
+ await this.fileHandle.read(localHeaderBuffer, 0, Headers_1.LOCAL_HDR.SIZE, entry.localHdrOffset);
668
+ // Verify signature
669
+ if (localHeaderBuffer.readUInt32LE(0) !== Headers_1.LOCAL_HDR.SIGNATURE) {
670
+ throw new Error(Errors_1.default.INVALID_CEN);
671
+ }
672
+ // Extract header information
673
+ const filenameLength = localHeaderBuffer.readUInt16LE(Headers_1.LOCAL_HDR.FNAME_LEN);
674
+ const extraFieldLength = localHeaderBuffer.readUInt16LE(Headers_1.LOCAL_HDR.EXTRA_LEN);
675
+ const bitFlags = localHeaderBuffer.readUInt16LE(Headers_1.LOCAL_HDR.FLAGS);
676
+ // Check for encryption header
677
+ let encryptionHeaderLength = 0;
678
+ if (bitFlags & Headers_1.GP_FLAG.ENCRYPTED) {
679
+ encryptionHeaderLength = Headers_1.ENCRYPT_HDR_SIZE;
680
+ }
681
+ // Calculate sizes
682
+ const localHeaderSize = Headers_1.LOCAL_HDR.SIZE + filenameLength + extraFieldLength;
683
+ const totalLocalEntrySize = localHeaderSize + encryptionHeaderLength + entry.compressedSize;
684
+ // Read the entire local entry (header + filename + extra field + encryption header + compressed data)
685
+ const entryBuffer = Buffer.alloc(totalLocalEntrySize);
686
+ await this.fileHandle.read(entryBuffer, 0, totalLocalEntrySize, entry.localHdrOffset);
687
+ return entryBuffer;
688
+ }
689
+ // ============================================================================
690
+ // File Update Methods
691
+ // ============================================================================
692
+ /**
693
+ * Update existing ZIP file
694
+ *
695
+ * This is a placeholder for future implementation.
696
+ * Full implementation would require:
697
+ * - Reading existing ZIP structure
698
+ * - Identifying entries to update/add/remove
699
+ * - Writing updated ZIP file
700
+ *
701
+ * @param zipPath - Path to the ZIP file to update
702
+ * @param updates - Update operations (add, update, remove entries)
703
+ * @returns Promise that resolves when update is complete
704
+ */
705
+ async updateZipFile(zipPath, updates) {
706
+ // Placeholder for future implementation
707
+ // This would require significant ZIP file manipulation logic
708
+ throw new Error('updateZipFile() - Full implementation pending. Use neozip CLI for now.');
709
+ }
710
+ // ============================================================================
711
+ // File-based ZIP Loading Methods (merged from ZipLoadEntriesServer)
712
+ // ============================================================================
713
+ /**
714
+ * Open file handle for streaming mode
715
+ */
716
+ async openFileHandle(filePath) {
717
+ const handle = await fs.promises.open(filePath, 'r');
718
+ return {
719
+ async read(buffer, offset, length, position) {
720
+ const result = await handle.read(buffer, offset, length, position);
721
+ return result.bytesRead;
722
+ },
723
+ async stat() {
724
+ const stats = await handle.stat();
725
+ return { size: stats.size };
726
+ },
727
+ async close() {
728
+ await handle.close();
729
+ }
730
+ };
731
+ }
732
+ /**
733
+ * Load End of Central Directory (EOCD) in streaming mode
734
+ */
735
+ async loadEOCD() {
736
+ if (!this.fileHandle) {
737
+ throw new Error('File handle not available');
738
+ }
739
+ // Read potential EOCD area (last 65KB + 22 bytes)
740
+ const searchSize = Math.min(0xFFFF + 22, this.fileSize);
741
+ const searchStart = this.fileSize - searchSize;
742
+ const buffer = Buffer.alloc(searchSize);
743
+ try {
744
+ await this.fileHandle.read(buffer, 0, searchSize, searchStart);
745
+ // Find EOCD signature
746
+ let eocdOffset = -1;
747
+ for (let i = buffer.length - 22; i >= 0; i--) {
748
+ if (buffer[i] === 0x50) { // Quick 'P' check
749
+ if (buffer.readUInt32LE(i) === Headers_1.CENTRAL_END.SIGNATURE) {
750
+ eocdOffset = searchStart + i;
751
+ break;
752
+ }
753
+ }
754
+ }
755
+ if (eocdOffset === -1) {
756
+ throw new Error(Errors_1.default.INVALID_FORMAT);
757
+ }
758
+ // Parse EOCD
759
+ const eocdBuffer = Buffer.alloc(22);
760
+ await this.fileHandle.read(eocdBuffer, 0, 22, eocdOffset);
761
+ if (eocdBuffer.readUInt32LE(0) === Headers_1.CENTRAL_END.SIGNATURE) {
762
+ // Standard ZIP format
763
+ const zipkit = this;
764
+ zipkit.centralDirSize = eocdBuffer.readUInt32LE(Headers_1.CENTRAL_END.CENTRAL_DIR_SIZE);
765
+ zipkit.centralDirOffset = eocdBuffer.readUInt32LE(Headers_1.CENTRAL_END.CENTRAL_DIR_OFFSET);
766
+ // Handle ZIP64
767
+ if (zipkit.centralDirOffset === 0xFFFFFFFF) {
768
+ await this.loadZIP64EOCD(eocdOffset);
769
+ }
770
+ }
771
+ else {
772
+ throw new Error(Errors_1.default.INVALID_FORMAT);
773
+ }
774
+ // Load ZIP comment
775
+ const commentLength = eocdBuffer.readUInt16LE(Headers_1.CENTRAL_END.ZIP_COMMENT_LEN);
776
+ if (commentLength > 0) {
777
+ const commentBuffer = Buffer.alloc(commentLength);
778
+ await this.fileHandle.read(commentBuffer, 0, commentLength, eocdOffset + 22);
779
+ const zipkitAny = this;
780
+ zipkitAny.zipComment = commentBuffer.toString();
781
+ }
782
+ }
783
+ finally {
784
+ // Clean up search buffer to help GC (can be up to 65KB)
785
+ // Note: Buffer will be GC'd when it goes out of scope, but explicit cleanup helps
786
+ }
787
+ }
788
+ /**
789
+ * Load ZIP64 End of Central Directory
790
+ */
791
+ async loadZIP64EOCD(eocdOffset) {
792
+ if (!this.fileHandle) {
793
+ throw new Error('File handle not available');
794
+ }
795
+ // Look for ZIP64 locator
796
+ const locatorOffset = eocdOffset - 20;
797
+ const locatorBuffer = Buffer.alloc(20);
798
+ await this.fileHandle.read(locatorBuffer, 0, 20, locatorOffset);
799
+ if (locatorBuffer.readUInt32LE(0) === Headers_1.ZIP64_CENTRAL_END.SIGNATURE) {
800
+ // Read ZIP64 EOCD
801
+ const zip64Offset = locatorBuffer.readBigUInt64LE(8);
802
+ const zip64Buffer = Buffer.alloc(56);
803
+ await this.fileHandle.read(zip64Buffer, 0, 56, Number(zip64Offset));
804
+ const zipkit = this;
805
+ zipkit.centralDirSize = Number(zip64Buffer.readBigUInt64LE(Headers_1.ZIP64_CENTRAL_DIR.CENTRAL_DIR_SIZE));
806
+ zipkit.centralDirOffset = Number(zip64Buffer.readBigUInt64LE(Headers_1.ZIP64_CENTRAL_DIR.CENTRAL_DIR_OFFSET));
807
+ }
808
+ }
809
+ /**
810
+ * Reset file-based ZIP data to initial state
811
+ */
812
+ resetFileData() {
813
+ this.fileHandle = null;
814
+ this.filePath = null;
815
+ this.fileSize = 0;
816
+ // Note: centralDirSize and centralDirOffset are reset in base class resetZipData()
817
+ const zipkit = this;
818
+ zipkit.centralDirSize = 0;
819
+ zipkit.centralDirOffset = 0;
820
+ }
821
+ // ============================================================================
822
+ // ZIP File Extraction Subfunctions
823
+ // ============================================================================
824
+ /**
825
+ * Filter ZIP entries based on include/exclude patterns
826
+ *
827
+ * @param entries - Array of ZipEntry objects to filter
828
+ * @param options - Optional filtering options
829
+ * @returns Filtered array of ZipEntry objects
830
+ */
831
+ filterEntries(entries, options) {
832
+ const skipMetadata = options?.skipMetadata !== false;
833
+ return entries.filter(entry => {
834
+ const filename = entry.filename || '';
835
+ // Skip metadata files if requested
836
+ if (skipMetadata && (filename.startsWith('META-INF/') || filename === 'META-INF')) {
837
+ return false;
838
+ }
839
+ // Skip directories
840
+ if (entry.isDirectory) {
841
+ return false;
842
+ }
843
+ // If no filtering patterns, include all
844
+ if (!options?.include && !options?.exclude) {
845
+ return true;
846
+ }
847
+ const fileName = path.basename(filename);
848
+ const relativePath = path.relative(process.cwd(), filename);
849
+ // Check include patterns first (if any)
850
+ if (options.include && options.include.length > 0) {
851
+ const matchesInclude = options.include.some(pattern => (0, minimatch_1.minimatch)(fileName, pattern) || (0, minimatch_1.minimatch)(relativePath, pattern) || (0, minimatch_1.minimatch)(filename, pattern));
852
+ if (!matchesInclude) {
853
+ return false;
854
+ }
855
+ }
856
+ // Check exclude patterns
857
+ if (options.exclude && options.exclude.length > 0) {
858
+ const matchesExclude = options.exclude.some(pattern => (0, minimatch_1.minimatch)(fileName, pattern) || (0, minimatch_1.minimatch)(relativePath, pattern) || (0, minimatch_1.minimatch)(filename, pattern));
859
+ if (matchesExclude) {
860
+ return false;
861
+ }
862
+ }
863
+ return true;
864
+ });
865
+ }
866
+ /**
867
+ * Prepare extraction path for a ZIP entry
868
+ *
869
+ * @param entry - ZipEntry to extract
870
+ * @param destination - Destination directory
871
+ * @param options - Optional path options
872
+ * @returns Absolute output path for the entry
873
+ */
874
+ prepareExtractionPath(entry, destination, options) {
875
+ const filename = entry.filename || '';
876
+ // Determine output path
877
+ let outputPath;
878
+ if (options?.junkPaths) {
879
+ // Extract to flat structure (filename only)
880
+ outputPath = path.join(destination, path.basename(filename));
881
+ }
882
+ else {
883
+ // Preserve directory structure
884
+ outputPath = path.join(destination, filename);
885
+ }
886
+ // Ensure parent directory exists
887
+ const parentDir = path.dirname(outputPath);
888
+ if (parentDir && parentDir !== '.' && !fs.existsSync(parentDir)) {
889
+ fs.mkdirSync(parentDir, { recursive: true });
890
+ }
891
+ return path.resolve(outputPath);
892
+ }
893
+ /**
894
+ * Extract timestamps from ZIP entry
895
+ *
896
+ * @param entry - ZipEntry to extract timestamps from
897
+ * @returns Object with mtime, atime, ctime (Date objects or null)
898
+ */
899
+ extractEntryTimestamps(entry) {
900
+ let mtime = null;
901
+ let atime = null;
902
+ let ctime = null;
903
+ // Try extended timestamps first (most accurate)
904
+ if (entry.ntfsTime) {
905
+ const ntfs = entry.ntfsTime;
906
+ if (ntfs.mtime)
907
+ mtime = new Date(ntfs.mtime);
908
+ if (ntfs.atime)
909
+ atime = new Date(ntfs.atime);
910
+ if (ntfs.ctime)
911
+ ctime = new Date(ntfs.ctime);
912
+ }
913
+ else if (entry.extendedTime) {
914
+ const ext = entry.extendedTime;
915
+ if (ext.mtime)
916
+ mtime = new Date(ext.mtime);
917
+ if (ext.atime)
918
+ atime = new Date(ext.atime);
919
+ if (ext.ctime)
920
+ ctime = new Date(ext.ctime);
921
+ }
922
+ // Fall back to standard timestamps if extended not available
923
+ if (!mtime) {
924
+ if (entry.parseDateTime && entry.lastModTimeDate) {
925
+ // Use the parseDateTime method if available
926
+ const parsedDate = entry.parseDateTime(entry.lastModTimeDate);
927
+ mtime = parsedDate ? new Date(parsedDate) : null;
928
+ }
929
+ else if (entry.lastModTimeDate) {
930
+ mtime = new Date(entry.lastModTimeDate);
931
+ }
932
+ else if (entry.timeDateDOS) {
933
+ // timeDateDOS is in seconds since 1970, convert to milliseconds
934
+ mtime = new Date(entry.timeDateDOS * 1000);
935
+ }
936
+ }
937
+ return { mtime, atime, ctime };
938
+ }
939
+ /**
940
+ * Determine if an entry should be extracted based on overwrite logic
941
+ *
942
+ * @param entry - ZipEntry to check
943
+ * @param outputPath - Path where file would be extracted
944
+ * @param options - Optional overwrite options
945
+ * @returns Promise resolving to decision object
946
+ */
947
+ async shouldExtractEntry(entry, outputPath, options) {
948
+ const fileExists = fs.existsSync(outputPath);
949
+ // If file doesn't exist, always extract (unless freshenOnly mode)
950
+ if (!fileExists) {
951
+ if (options?.freshenOnly) {
952
+ return { shouldExtract: false, reason: 'not in destination' };
953
+ }
954
+ // For updateOnly or normal mode, extract new files
955
+ return { shouldExtract: true };
956
+ }
957
+ // File exists - apply overwrite logic
958
+ if (options?.never) {
959
+ return { shouldExtract: false, reason: 'never overwrite' };
960
+ }
961
+ if (options?.overwrite) {
962
+ return { shouldExtract: true };
963
+ }
964
+ if (options?.freshenOnly || options?.updateOnly) {
965
+ // Compare timestamps to determine if archive file is newer
966
+ const existingStats = fs.statSync(outputPath);
967
+ const timestamps = this.extractEntryTimestamps(entry);
968
+ const archiveDate = timestamps.mtime || new Date(0);
969
+ if (archiveDate <= existingStats.mtime) {
970
+ return { shouldExtract: false, reason: 'not newer' };
971
+ }
972
+ // File in archive is newer, proceed with extraction
973
+ return { shouldExtract: true };
974
+ }
975
+ // Interactive mode - use callback if provided
976
+ if (options?.onOverwritePrompt) {
977
+ const response = await options.onOverwritePrompt(entry.filename || '');
978
+ if (response === 'n') {
979
+ return { shouldExtract: false, reason: 'user declined' };
980
+ }
981
+ else if (response === 'q') {
982
+ return { shouldExtract: false, reason: 'user aborted' };
983
+ }
984
+ else if (response === 'y' || response === 'a') {
985
+ return { shouldExtract: true };
986
+ }
987
+ }
988
+ // Default: skip if exists and no overwrite option
989
+ return { shouldExtract: false, reason: 'file exists' };
990
+ }
991
+ /**
992
+ * Restore entry metadata (timestamps and permissions) to extracted file
993
+ *
994
+ * @param filePath - Path to the extracted file
995
+ * @param entry - ZipEntry that was extracted
996
+ * @param options - Optional metadata options
997
+ */
998
+ restoreEntryMetadata(filePath, entry, options) {
999
+ const preserveTimestamps = options?.preserveTimestamps !== false;
1000
+ const preservePermissions = options?.preservePermissions === true;
1001
+ // Restore timestamps
1002
+ if (preserveTimestamps) {
1003
+ try {
1004
+ const timestamps = this.extractEntryTimestamps(entry);
1005
+ if (timestamps.mtime && timestamps.atime) {
1006
+ fs.utimesSync(filePath, timestamps.atime, timestamps.mtime);
1007
+ }
1008
+ else if (timestamps.mtime) {
1009
+ // If we only have modification time, use it for both
1010
+ fs.utimesSync(filePath, timestamps.mtime, timestamps.mtime);
1011
+ }
1012
+ }
1013
+ catch (error) {
1014
+ // Don't fail extraction if timestamp restoration fails
1015
+ // Some filesystems don't support timestamp modification
1016
+ }
1017
+ }
1018
+ // Restore permissions (Unix only)
1019
+ if (preservePermissions && process.platform !== 'win32') {
1020
+ try {
1021
+ // Restore UID/GID if available
1022
+ if (entry.uid !== null && entry.uid !== undefined &&
1023
+ entry.gid !== null && entry.gid !== undefined) {
1024
+ // Only root can change ownership to different users
1025
+ if (process.getuid && process.getuid() === 0) {
1026
+ // Running as root - can change both UID and GID
1027
+ fs.chownSync(filePath, entry.uid, entry.gid);
1028
+ }
1029
+ else {
1030
+ // Not running as root - try to change group only if we're a member
1031
+ try {
1032
+ fs.chownSync(filePath, -1, entry.gid); // -1 means don't change UID
1033
+ }
1034
+ catch (error) {
1035
+ // Ignore errors - insufficient privileges
1036
+ }
1037
+ }
1038
+ }
1039
+ // Restore file mode if available
1040
+ if (entry.extFileAttr) {
1041
+ // Extract permission bits from external file attributes
1042
+ const permissions = (entry.extFileAttr >>> 16) & 0o777;
1043
+ if (permissions > 0) {
1044
+ fs.chmodSync(filePath, permissions);
1045
+ }
1046
+ }
1047
+ }
1048
+ catch (error) {
1049
+ // Don't fail extraction if permission restoration fails
1050
+ }
1051
+ }
1052
+ }
1053
+ /**
1054
+ * Extract a single entry to a file path
1055
+ * Handles symlinks, hardlinks, timestamps, and permissions
1056
+ *
1057
+ * @param entry - ZipEntry to extract
1058
+ * @param outputPath - Path where file should be extracted
1059
+ * @param options - Optional extraction options
1060
+ * @returns Promise resolving to extraction result
1061
+ */
1062
+ async extractEntryToPath(entry, outputPath, options) {
1063
+ const filename = entry.filename || '';
1064
+ try {
1065
+ // Check if entry is a symbolic link
1066
+ const isSymlink = entry.isSymlink && entry.linkTarget;
1067
+ const S_IFLNK = 0o120000;
1068
+ const fileType = entry.extFileAttr ? ((entry.extFileAttr >>> 16) & 0o170000) : 0;
1069
+ const isSymlinkByAttr = fileType === S_IFLNK;
1070
+ if ((isSymlink || isSymlinkByAttr) && options?.symlinks) {
1071
+ // Handle symbolic link
1072
+ let linkTarget = entry.linkTarget;
1073
+ if (!linkTarget) {
1074
+ // Extract target from file content
1075
+ const bufferBased = !this.fileHandle;
1076
+ if (bufferBased) {
1077
+ const data = await this.extract(entry, options?.skipHashCheck);
1078
+ if (data) {
1079
+ linkTarget = data.toString('utf8');
1080
+ }
1081
+ }
1082
+ else {
1083
+ // For file-based, extract to temp file and read
1084
+ const tempPath = path.join(require('os').tmpdir(), `neozip-symlink-${Date.now()}-${process.pid}`);
1085
+ try {
1086
+ await this.extractToFile(entry, tempPath, {
1087
+ skipHashCheck: options?.skipHashCheck
1088
+ });
1089
+ const data = fs.readFileSync(tempPath, 'utf8');
1090
+ linkTarget = data;
1091
+ // Clean up temp file
1092
+ fs.unlinkSync(tempPath);
1093
+ }
1094
+ catch (error) {
1095
+ // Clean up temp file if it exists
1096
+ if (fs.existsSync(tempPath)) {
1097
+ try {
1098
+ fs.unlinkSync(tempPath);
1099
+ }
1100
+ catch (cleanupError) {
1101
+ // Ignore cleanup errors
1102
+ }
1103
+ }
1104
+ return { success: false, bytesExtracted: 0, error: `Could not extract symbolic link target: ${error instanceof Error ? error.message : String(error)}` };
1105
+ }
1106
+ }
1107
+ }
1108
+ if (linkTarget && process.platform !== 'win32') {
1109
+ try {
1110
+ fs.symlinkSync(linkTarget, outputPath);
1111
+ return { success: true, bytesExtracted: Buffer.byteLength(linkTarget, 'utf8') };
1112
+ }
1113
+ catch (error) {
1114
+ return { success: false, bytesExtracted: 0, error: `Failed to create symbolic link: ${error instanceof Error ? error.message : String(error)}` };
1115
+ }
1116
+ }
1117
+ else {
1118
+ return { success: false, bytesExtracted: 0, error: 'Symbolic links not supported on this platform' };
1119
+ }
1120
+ }
1121
+ // Check if entry is a hard link
1122
+ const isHardLink = entry.isHardLink && entry.originalEntry;
1123
+ if (isHardLink && options?.hardLinks) {
1124
+ // Handle hard link
1125
+ const originalEntry = entry.originalEntry;
1126
+ const outDir = path.dirname(outputPath);
1127
+ const originalPath = path.resolve(outDir, originalEntry);
1128
+ if (fs.existsSync(originalPath) && process.platform !== 'win32') {
1129
+ try {
1130
+ fs.linkSync(originalPath, outputPath);
1131
+ return { success: true, bytesExtracted: 0 }; // No actual bytes extracted for hard links
1132
+ }
1133
+ catch (error) {
1134
+ return { success: false, bytesExtracted: 0, error: `Failed to create hard link: ${error instanceof Error ? error.message : String(error)}` };
1135
+ }
1136
+ }
1137
+ else {
1138
+ return { success: false, bytesExtracted: 0, error: 'Hard links not supported or original file not found' };
1139
+ }
1140
+ }
1141
+ // Regular file extraction
1142
+ // Check if we're in buffer-based or file-based mode
1143
+ const bufferBased = !this.fileHandle;
1144
+ const fileBased = !!this.fileHandle;
1145
+ // Use temp file for overwrite safety
1146
+ const fileExists = fs.existsSync(outputPath);
1147
+ const needsTempFile = fileExists;
1148
+ const tempPath = needsTempFile
1149
+ ? path.join(require('os').tmpdir(), `neozip-extract-${Date.now()}-${process.pid}-${path.basename(outputPath).replace(/[^a-zA-Z0-9]/g, '_')}`)
1150
+ : outputPath;
1151
+ let bytesExtracted = 0;
1152
+ let extractionSucceeded = false;
1153
+ try {
1154
+ if (bufferBased) {
1155
+ // Buffer-based (in-memory) mode: extract to buffer, then write to file
1156
+ const data = await this.extract(entry, options?.skipHashCheck);
1157
+ if (!data) {
1158
+ return { success: false, bytesExtracted: 0, error: 'Extraction returned no data' };
1159
+ }
1160
+ // Write buffer to temp file
1161
+ fs.writeFileSync(tempPath, data);
1162
+ bytesExtracted = data.length;
1163
+ extractionSucceeded = true;
1164
+ if (options?.onProgress) {
1165
+ options.onProgress(entry, bytesExtracted);
1166
+ }
1167
+ }
1168
+ else if (fileBased) {
1169
+ // File-based mode: use direct streaming extraction to temp file
1170
+ await this.extractToFile(entry, tempPath, {
1171
+ skipHashCheck: options?.skipHashCheck,
1172
+ onProgress: (bytes) => {
1173
+ bytesExtracted = bytes;
1174
+ if (options?.onProgress) {
1175
+ options.onProgress(entry, bytes);
1176
+ }
1177
+ }
1178
+ });
1179
+ // If we get here, extraction succeeded
1180
+ extractionSucceeded = true;
1181
+ }
1182
+ else {
1183
+ return { success: false, bytesExtracted: 0, error: 'ZIP file not loaded or unknown backend type' };
1184
+ }
1185
+ // If extraction succeeded and we used a temp file, replace the original
1186
+ if (extractionSucceeded && needsTempFile) {
1187
+ // Delete the original file
1188
+ fs.unlinkSync(outputPath);
1189
+ // Move temp file to final location
1190
+ fs.renameSync(tempPath, outputPath);
1191
+ }
1192
+ // Restore metadata (timestamps and permissions)
1193
+ this.restoreEntryMetadata(outputPath, entry, {
1194
+ preserveTimestamps: options?.preserveTimestamps,
1195
+ preservePermissions: options?.preservePermissions
1196
+ });
1197
+ return { success: true, bytesExtracted };
1198
+ }
1199
+ catch (error) {
1200
+ // Clean up temp file if it exists
1201
+ if (needsTempFile && fs.existsSync(tempPath)) {
1202
+ try {
1203
+ fs.unlinkSync(tempPath);
1204
+ }
1205
+ catch (cleanupError) {
1206
+ // Ignore cleanup errors
1207
+ }
1208
+ }
1209
+ return {
1210
+ success: false,
1211
+ bytesExtracted: 0,
1212
+ error: error instanceof Error ? error.message : String(error)
1213
+ };
1214
+ }
1215
+ }
1216
+ catch (error) {
1217
+ return {
1218
+ success: false,
1219
+ bytesExtracted: 0,
1220
+ error: error instanceof Error ? error.message : String(error)
1221
+ };
1222
+ }
1223
+ }
1224
+ /**
1225
+ * Extract all files from a ZIP archive to a destination directory
1226
+ * Simple API that uses the modular subfunctions
1227
+ *
1228
+ * @param archivePath - Path to the ZIP file
1229
+ * @param destination - Directory where files should be extracted
1230
+ * @param options - Optional extraction options
1231
+ * @returns Promise resolving to extraction statistics
1232
+ */
1233
+ async extractZipFile(archivePath, destination, options) {
1234
+ // Ensure destination directory exists
1235
+ if (!fs.existsSync(destination)) {
1236
+ fs.mkdirSync(destination, { recursive: true });
1237
+ }
1238
+ // Load ZIP file if not already loaded or if path changed
1239
+ if (!this.fileHandle || this.filePath !== archivePath) {
1240
+ await this.loadZipFile(archivePath);
1241
+ }
1242
+ // Set password if provided (needed for decryption)
1243
+ if (options?.password) {
1244
+ this.password = options.password;
1245
+ }
1246
+ // Get all entries
1247
+ const entries = this.getDirectory();
1248
+ // Filter entries
1249
+ const filteredEntries = this.filterEntries(entries, {
1250
+ include: options?.include,
1251
+ exclude: options?.exclude,
1252
+ skipMetadata: true
1253
+ });
1254
+ // Extract each entry
1255
+ let filesExtracted = 0;
1256
+ let bytesExtracted = 0;
1257
+ let alwaysOverwrite = false; // Track "always" response from user
1258
+ for (const entry of filteredEntries) {
1259
+ // Prepare output path
1260
+ const outputPath = this.prepareExtractionPath(entry, destination, {
1261
+ junkPaths: options?.junkPaths
1262
+ });
1263
+ // Check if should extract
1264
+ const decision = await this.shouldExtractEntry(entry, outputPath, {
1265
+ overwrite: options?.overwrite || alwaysOverwrite,
1266
+ never: false,
1267
+ freshenOnly: false,
1268
+ updateOnly: false,
1269
+ onOverwritePrompt: async (filename) => {
1270
+ if (options?.onOverwritePrompt) {
1271
+ const response = await options.onOverwritePrompt(filename);
1272
+ if (response === 'a') {
1273
+ alwaysOverwrite = true;
1274
+ }
1275
+ return response;
1276
+ }
1277
+ return 'n'; // Default to no if no callback provided
1278
+ }
1279
+ });
1280
+ if (!decision.shouldExtract) {
1281
+ // Check if user aborted
1282
+ if (decision.reason === 'user aborted') {
1283
+ break; // Stop extraction
1284
+ }
1285
+ continue;
1286
+ }
1287
+ // Extract entry
1288
+ const result = await this.extractEntryToPath(entry, outputPath, {
1289
+ skipHashCheck: options?.skipHashCheck,
1290
+ preserveTimestamps: options?.preserveTimestamps !== false,
1291
+ preservePermissions: options?.preservePermissions,
1292
+ symlinks: options?.symlinks,
1293
+ hardLinks: options?.hardLinks,
1294
+ onProgress: options?.onProgress
1295
+ });
1296
+ if (result.success) {
1297
+ filesExtracted++;
1298
+ bytesExtracted += result.bytesExtracted;
1299
+ }
1300
+ }
1301
+ return { filesExtracted, bytesExtracted };
1302
+ }
1303
+ }
1304
+ exports.default = ZipkitNode;
1305
+ //# sourceMappingURL=ZipkitNode.js.map