neozipkit 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/README.md +400 -0
  2. package/contracts/Lock.sol +34 -0
  3. package/contracts/bytecode.txt +1 -0
  4. package/contracts/compile-contract.js +168 -0
  5. package/contracts/deploy/deploy-both-contracts.js +286 -0
  6. package/contracts/deploy/deploy-enhanced-contract.js +197 -0
  7. package/contracts/deploy/deploy-private-contract.js +190 -0
  8. package/contracts/deploy/deploy-public-clean.js +1063 -0
  9. package/contracts/deploy/deploy-public-contract.js +1107 -0
  10. package/contracts/deployment.config.example.js +25 -0
  11. package/contracts/remix-compile.js +222 -0
  12. package/contracts/setup-deployment.js +51 -0
  13. package/contracts/src/NZIP-NFT-Private.sol +350 -0
  14. package/contracts/src/NZIP-NFT.sol +265 -0
  15. package/contracts/src/ZipFileNFT-DEPRECATED-6field.sol +260 -0
  16. package/contracts/src/ZipFileNFT-DEPRECATED-Enhanced.sol +289 -0
  17. package/dist/browser/CertificateGeneratorBrowser.d.ts +75 -0
  18. package/dist/browser/CertificateGeneratorBrowser.js +433 -0
  19. package/dist/browser/TokenVerifierBrowser.d.ts +76 -0
  20. package/dist/browser/TokenVerifierBrowser.js +299 -0
  21. package/dist/browser/ZipKitBrowser.d.ts +18 -0
  22. package/dist/browser/ZipKitBrowser.js +250 -0
  23. package/dist/browser/ZipKitStampBrowser.d.ts +2 -0
  24. package/dist/browser/ZipKitStampBrowser.js +12 -0
  25. package/dist/browser-index.d.ts +21 -0
  26. package/dist/browser-index.js +58 -0
  27. package/dist/browser.d.ts +21 -0
  28. package/dist/browser.js +58 -0
  29. package/dist/index.d.ts +29 -0
  30. package/dist/index.js +76 -0
  31. package/dist/server/blockchain/CertificateToken.d.ts +48 -0
  32. package/dist/server/blockchain/CertificateToken.js +386 -0
  33. package/dist/server/blockchain/NFTMinter.d.ts +41 -0
  34. package/dist/server/blockchain/NFTMinter.js +323 -0
  35. package/dist/server/blockchain/TokenQuerier.d.ts +31 -0
  36. package/dist/server/blockchain/TokenQuerier.js +259 -0
  37. package/dist/server/blockchain/TokenVerifier.d.ts +20 -0
  38. package/dist/server/blockchain/TokenVerifier.js +395 -0
  39. package/dist/server/blockchain/WalletAnalyzer.d.ts +81 -0
  40. package/dist/server/blockchain/WalletAnalyzer.js +356 -0
  41. package/dist/server/blockchain/WalletFormatter.d.ts +39 -0
  42. package/dist/server/blockchain/WalletFormatter.js +196 -0
  43. package/dist/server/blockchain/WalletManager.d.ts +46 -0
  44. package/dist/server/blockchain/WalletManager.js +294 -0
  45. package/dist/server/blockchain/ZipMetadata.d.ts +27 -0
  46. package/dist/server/blockchain/ZipMetadata.js +131 -0
  47. package/dist/server/blockchain/ZipTokenProcessor.d.ts +44 -0
  48. package/dist/server/blockchain/ZipTokenProcessor.js +204 -0
  49. package/dist/server/blockchain/constants.d.ts +102 -0
  50. package/dist/server/blockchain/constants.js +968 -0
  51. package/dist/server/blockchain/index.d.ts +9 -0
  52. package/dist/server/blockchain/index.js +44 -0
  53. package/dist/server/blockchain/test-certificate.d.ts +8 -0
  54. package/dist/server/blockchain/test-certificate.js +100 -0
  55. package/dist/server/blockchain/types.d.ts +69 -0
  56. package/dist/server/blockchain/types.js +51 -0
  57. package/dist/server/platform/server/ZipKitServer.d.ts +105 -0
  58. package/dist/server/platform/server/ZipKitServer.js +437 -0
  59. package/dist/server/platform/servercomponents/ZipKitOTS.d.ts +91 -0
  60. package/dist/server/platform/servercomponents/ZipKitOTS.js +271 -0
  61. package/dist/shared/ZipEntry.d.ts +131 -0
  62. package/dist/shared/ZipEntry.js +686 -0
  63. package/dist/shared/ZipKit.d.ts +249 -0
  64. package/dist/shared/ZipKit.js +789 -0
  65. package/dist/shared/components/CertificateOTS.d.ts +15 -0
  66. package/dist/shared/components/CertificateOTS.js +116 -0
  67. package/dist/shared/components/FileSys.d.ts +2 -0
  68. package/dist/shared/components/FileSys.js +113 -0
  69. package/dist/shared/components/HashAccumulator.d.ts +55 -0
  70. package/dist/shared/components/HashAccumulator.js +100 -0
  71. package/dist/shared/components/Support.d.ts +63 -0
  72. package/dist/shared/components/Support.js +70 -0
  73. package/dist/shared/components/Util.d.ts +21 -0
  74. package/dist/shared/components/Util.js +115 -0
  75. package/dist/shared/components/ZipCrypto.d.ts +3 -0
  76. package/dist/shared/components/ZipCrypto.js +78 -0
  77. package/dist/shared/components/merkleTree.d.ts +66 -0
  78. package/dist/shared/components/merkleTree.js +136 -0
  79. package/dist/shared/constants/Errors.d.ts +53 -0
  80. package/dist/shared/constants/Errors.js +69 -0
  81. package/dist/shared/constants/Headers.d.ts +167 -0
  82. package/dist/shared/constants/Headers.js +191 -0
  83. package/dist/shared/types/index.d.ts +21 -0
  84. package/dist/shared/types/index.js +3 -0
  85. package/dist/shared/types.d.ts +33 -0
  86. package/dist/shared/types.js +8 -0
  87. package/dist/shared/version.d.ts +4 -0
  88. package/dist/shared/version.js +7 -0
  89. package/dist/types/index.d.ts +21 -0
  90. package/dist/types/index.js +3 -0
  91. package/examples/unzip-token.ts +488 -0
  92. package/examples/zip-token.ts +604 -0
  93. package/package.json +117 -0
  94. package/src/browser/CertificateGeneratorBrowser.ts +498 -0
  95. package/src/browser/TokenVerifierBrowser.ts +344 -0
  96. package/src/browser/ZipKitBrowser.ts +242 -0
  97. package/src/browser/ZipKitStampBrowser.ts +9 -0
  98. package/src/browser-index.ts +36 -0
  99. package/src/index.ts +48 -0
  100. package/src/server/blockchain/CertificateToken.ts +444 -0
  101. package/src/server/blockchain/NFTMinter.ts +357 -0
  102. package/src/server/blockchain/TokenQuerier.ts +287 -0
  103. package/src/server/blockchain/TokenVerifier.ts +453 -0
  104. package/src/server/blockchain/WalletAnalyzer.ts +397 -0
  105. package/src/server/blockchain/WalletFormatter.ts +206 -0
  106. package/src/server/blockchain/WalletManager.ts +294 -0
  107. package/src/server/blockchain/ZipMetadata.ts +117 -0
  108. package/src/server/blockchain/ZipTokenProcessor.ts +234 -0
  109. package/src/server/blockchain/constants.ts +969 -0
  110. package/src/server/blockchain/index.ts +42 -0
  111. package/src/server/blockchain/test-certificate.ts +137 -0
  112. package/src/server/blockchain/types.ts +134 -0
  113. package/src/server/platform/server/ZipKitServer.ts +459 -0
  114. package/src/server/platform/servercomponents/ZipKitOTS.ts +313 -0
  115. package/src/server/platform/servercomponents/opentimestamps.d.ts +23 -0
  116. package/src/shared/ZipEntry.ts +767 -0
  117. package/src/shared/ZipKit.ts +899 -0
  118. package/src/shared/components/CertificateOTS.ts +132 -0
  119. package/src/shared/components/FileSys.ts +87 -0
  120. package/src/shared/components/HashAccumulator.ts +113 -0
  121. package/src/shared/components/Support.ts +77 -0
  122. package/src/shared/components/Util.ts +113 -0
  123. package/src/shared/components/ZipCrypto.ts +91 -0
  124. package/src/shared/components/merkleTree.ts +162 -0
  125. package/src/shared/constants/Errors.ts +82 -0
  126. package/src/shared/constants/Headers.ts +203 -0
  127. package/src/shared/types/index.ts +25 -0
  128. package/src/shared/types/modules.d.ts +22 -0
  129. package/src/shared/types/opentimestamps.d.ts +23 -0
  130. package/src/shared/types.d.ts +8 -0
  131. package/src/shared/types.ts +43 -0
  132. package/src/shared/version.ts +4 -0
  133. package/src/types/index.ts +25 -0
  134. package/src/types/modules.d.ts +22 -0
  135. package/src/types/opentimestamps.d.ts +23 -0
@@ -0,0 +1,488 @@
1
+ #!/usr/bin/env node
2
+
3
+ // ======================================
4
+ // unzip-token.ts
5
+ // A command-line utility for extracting and managing ZIP archives with advanced features
6
+ // including decryption, blockchain token verification, and SHA-256 validation.
7
+ // Copyright (c) 2025 NeoWare, Inc. All rights reserved.
8
+ // ======================================
9
+
10
+ import { exit } from 'process';
11
+ import * as fs from 'fs';
12
+ import * as path from 'path';
13
+ import readline from 'readline';
14
+ import moment from 'moment-timezone';
15
+
16
+ import {
17
+ NEOZIPKIT_INFO,
18
+ ZipKitServer,
19
+ ZipEntry,
20
+ TIMESTAMP_METADATA,
21
+ TIMESTAMP_SUBMITTED,
22
+ verifyTimestamp
23
+ } from 'neozipkit';
24
+
25
+ import yargs from 'yargs';
26
+ import { hideBin } from 'yargs/helpers';
27
+ import { sprintf } from 'sprintf-js';
28
+
29
+ // Import blockchain components for token verification
30
+ import { NZipTokenInfo } from './blockchain/types';
31
+ import { TOKENIZED_METADATA } from '../src/constants/Headers';
32
+
33
+ // Import token verification functionality
34
+ import {
35
+ checkForTokenization,
36
+ TokenVerificationOptions
37
+ } from './blockchain/TokenVerifier';
38
+
39
+ // Add NeoUnzip version constants
40
+ const NEOUNZIP_VERSION = '0.1.2';
41
+ const NEOUNZIP_RELEASE_DATE = '03-31-2025';
42
+
43
+ // Display copyright and version header on startup
44
+ console.log(`NeoUnzip v${NEOUNZIP_VERSION} (${NEOUNZIP_RELEASE_DATE}) Copyright (c) 2025 NeoWare, Inc. All rights reserved`);
45
+ console.log(`Using NeoZipKit v${NEOZIPKIT_INFO.version} (${NEOZIPKIT_INFO.releaseDate})`);
46
+
47
+ const options = yargs(hideBin(process.argv))
48
+ .usage("Usage: $0 <zipfile> <extract path>")
49
+ .demandCommand(1, "Error - The .zip filename is required.")
50
+ .option('t', { alias: 'test', describe: 'Test archive files', type: 'boolean' })
51
+ .option('u', { alias: 'update', describe: 'Update files, create if neccessary', type: 'boolean' })
52
+ .option('f', { alias: 'freshen', describe: 'Freshen by extracting only if older file on disk.', type: 'boolean' })
53
+ .option('d', { alias: 'directory', describe: 'Specify a different directory', type: 'string' })
54
+ .option('x', { alias: 'exclude', describe: 'Exclude specified files', type: 'array' })
55
+ .option('j', { alias: 'junk', describe: 'Junk paths (don\'t make directories)', type: 'boolean' })
56
+ .option('q', { alias: 'quiet', describe: 'Quiet mode', type: 'boolean' })
57
+ .option('o', { alias: 'overwrite', describe: 'Overwrite files without prompting', type: 'boolean' })
58
+ .option('n', { alias: 'never', describe: 'Never overwrite existing files', type: 'boolean' })
59
+ .option('L', { alias: 'license', describe: 'Display software license', type: 'boolean' })
60
+ .option('H', { alias: 'Hash SHA-256', describe: 'Calculate and validate SHA-256 Hash on entries', type: 'boolean' })
61
+ .option('skip-hash', { describe: 'Skip SHA-256 hash validation', type: 'boolean' })
62
+ .option('skip-token', { describe: 'Skip NFT token verification', type: 'boolean' })
63
+ .option('S', { alias: 'Skip Timestamp', describe: 'Do not validate timestamped Zip', type: 'boolean' })
64
+ .option('C', { alias: 'Timestamp Certificate HTML', describe: 'Create a timestamp certicate as HTML file', type: 'boolean' })
65
+ .option('P', { alias: 'Timestamp Certificate PDF', describe: 'Create a timestamp certicate as PDF file', type: 'boolean' })
66
+ .option('c', { alias: 'certificate', describe: 'Generate token certificate', type: 'boolean' })
67
+ .help('help', 'Show help').alias('help', 'h')
68
+ .wrap(null)
69
+ .epilog('Copyright (c) 2025 NeoWare, Inc. - Type "neounzip "-L" for software license.\n')
70
+ .parseSync()
71
+
72
+ const zipFile = options._[0] as string;
73
+ const zipKit = new ZipKitServer();
74
+
75
+ // Global variables for token verification
76
+ let isTokenizedZip = false;
77
+ let tokenMetadata: NZipTokenInfo | null = null;
78
+ let calculatedMerkleRoot: string | null = null;
79
+
80
+ async function main() {
81
+ try {
82
+ // Check if the file exists before trying to read it
83
+ if (!fs.existsSync(zipFile)) {
84
+ console.error(`Error: Zip file "${zipFile}" does not exist`);
85
+ exit(1);
86
+ }
87
+
88
+ const inBuffer = fs.readFileSync(zipFile);
89
+ zipKit.loadZip(inBuffer);
90
+
91
+ // Check if this is a tokenized ZIP file
92
+ const verificationOptions: TokenVerificationOptions = {
93
+ skipHash: options['skip-hash'],
94
+ skipToken: options['skip-token'],
95
+ generateCertificate: options.c,
96
+ zipFileName: zipFile
97
+ };
98
+
99
+ const result = await checkForTokenization(zipKit, verificationOptions);
100
+
101
+ // Update global variables with results
102
+ isTokenizedZip = result.isTokenized;
103
+ tokenMetadata = result.tokenMetadata;
104
+ calculatedMerkleRoot = result.calculatedMerkleRoot;
105
+
106
+ } catch (error) {
107
+ console.error(`Error: Failed to read zip file "${zipFile}"`);
108
+ console.error(error);
109
+ exit(1);
110
+ }
111
+
112
+ // command options
113
+ if (options.t) {
114
+ // Test the CRC of the zip file
115
+ await extractFiles(true);
116
+ } else if (options.z) {
117
+ // Archive comment onlyzip
118
+ } else if (options.L) {
119
+ displayLicense();
120
+ } else {
121
+ // No options, extract all files
122
+ await extractFiles(false)
123
+ }
124
+ }
125
+
126
+ // Start the main function
127
+ main().catch(error => {
128
+ console.error('Unhandled error:', error);
129
+ exit(1);
130
+ });
131
+
132
+ // Display the software license
133
+ function displayLicense() {
134
+ prt('Copyright (c) 2025 by NeoWare, Inc. All rights reserved\n');
135
+ exit(0);
136
+ }
137
+
138
+ /**
139
+ * Extracts or tests files from the ZIP archive
140
+ * @param test - If true, only tests archive integrity without extracting
141
+ */
142
+ async function extractFiles(test: boolean = false) {
143
+ try {
144
+ let zipEntries = zipKit.getDirectory() as ZipEntry[];
145
+
146
+ // Check if we have any entries
147
+ if (!zipEntries || zipEntries.length === 0) {
148
+ console.log('\nArchive is empty or invalid: ', zipFile);
149
+ return;
150
+ }
151
+
152
+ let zipComment = zipKit.getZipComment();
153
+
154
+ console.log('\nArchive: ', zipFile);
155
+ if (zipComment)
156
+ console.log(zipComment);
157
+
158
+ let _extFiles = 0;
159
+ let _total = 0;
160
+ let _cmpTotal = 0;
161
+ let _password = null;
162
+ let _localData = null;
163
+ let _metaStamp = null;
164
+ for (const entry of zipEntries) {
165
+ // Check if Zip Entry is encrypted
166
+ if (entry.isEncrypted) {
167
+ if (entry.isStrongEncrypt) {
168
+ prt(sprintf(" skipping: %-25s Strong Encryption not supported\n", entry.filename));
169
+ continue;
170
+ }
171
+ for (var i=0; i < 3; i++) {
172
+ if (_password == null) {
173
+ _password = await inputPassword(entry, i > 0);
174
+ }
175
+ _localData = zipKit.decryptEntry(entry, _password);
176
+ if (_localData) break;
177
+ _password = null;
178
+ }
179
+ if (!_localData) {
180
+ prt(sprintf(" skipping: %-25s incorrect password\n", entry.filename));
181
+ continue;
182
+ }
183
+ }
184
+
185
+ test ? prt(sprintf(" Testing: %-25s ", entry.filename)) :
186
+ prt(sprintf(" Extracting: %-25s ", entry.filename));
187
+ try {
188
+ let _outBuf = await zipKit.extract(entry);
189
+
190
+ if (entry.filename === TIMESTAMP_METADATA) {
191
+ _metaStamp = _outBuf;
192
+ console.log(' Timestamp Metadata');
193
+ continue;
194
+ } else if (entry.filename === TIMESTAMP_SUBMITTED) {
195
+ _metaStamp = _outBuf;
196
+ console.log(' Timestamp Submitted');
197
+ continue;
198
+ } else if (entry.filename === TOKENIZED_METADATA) {
199
+ console.log(' NFT Token Metadata (not extracted)');
200
+ continue;
201
+ } else if (!test) {
202
+ const _path = options.d ? options.d + path.sep + entry.filename : entry.filename;
203
+ const _overwrite = await testOverwrite(_path);
204
+ if (_overwrite) {
205
+ if (_outBuf) {
206
+ writeFile(_path, _outBuf, entry, entry.extFileAttr);
207
+ } else {
208
+ prt("Error: No data to write\n");
209
+ continue;
210
+ }
211
+ } else if (options.n) {
212
+ prt("file exists, Skipped\n");
213
+ continue;
214
+ } else {
215
+ prt(" Skipped\n");
216
+ continue;
217
+ }
218
+ }
219
+
220
+ // Automatically validate SHA-256 if available and not skipped
221
+ if (entry.sha256 && !options['skip-hash']) {
222
+ console.log(' OK SHA-256 ✅');
223
+ } else if (entry.sha256 && options['skip-hash']) {
224
+ console.log(' OK (SHA-256 skipped)');
225
+ } else {
226
+ console.log(' OK');
227
+ }
228
+
229
+ _extFiles++;
230
+ _total += entry.uncompressedSize;
231
+ _cmpTotal += entry.compressedSize;
232
+ } catch (e) {
233
+ prt(" Failed\n");
234
+ console.log(e);
235
+ }
236
+ };
237
+ console.log('No Errors where detected in %d files %s.', _extFiles, test ? 'tested' : 'extracted');
238
+
239
+ // Display token verification results if this is a tokenized ZIP
240
+ if (isTokenizedZip && tokenMetadata) {
241
+ console.log('\n🔗 Token Verification Summary:');
242
+ console.log('═'.repeat(50));
243
+
244
+ // Full NFT token info only
245
+ console.log(`Token ID: ${tokenMetadata.tokenId}`);
246
+ console.log(`Network: ${tokenMetadata.network}`);
247
+ console.log(`Contract: ${tokenMetadata.contractAddress}`);
248
+
249
+ if (options['skip-token']) {
250
+ console.log('⏭️ Token verification: SKIPPED');
251
+ } else if (options['skip-hash']) {
252
+ console.log('⏭️ Archive integrity: SKIPPED');
253
+ console.log(' Hash calculation disabled - cannot verify');
254
+ } else if (calculatedMerkleRoot) {
255
+ console.log('🌐 Archive integrity: BLOCKCHAIN VERIFIED');
256
+ console.log(' See verification details above');
257
+ } else {
258
+ console.log('⚠️ Archive integrity: CANNOT VERIFY');
259
+ console.log(' Unable to calculate merkle root for comparison');
260
+ }
261
+ console.log('═'.repeat(50));
262
+ }
263
+
264
+ let _merkleRoot = zipKit.checkForTimestamp();
265
+ if (_merkleRoot) {
266
+ if (options.S) {
267
+ console.log('Archive contains timestamp metadata - Skipping Validation');
268
+ return;
269
+ }
270
+ console.log('Archive is timestamped - Pending Validation');
271
+ if (!_merkleRoot) {
272
+ console.log('No timestamp metadata found');
273
+ console.log('Archive timestamp validation failed!');
274
+ } else {
275
+ // Validate the timestamp
276
+ let _ots = await zipKit.getOts();
277
+ const _timestampResult = await verifyTimestamp(_merkleRoot, _ots);
278
+
279
+ // First check if _timestampResult is not null
280
+ if (_timestampResult) {
281
+ // Type guard to check if we have a successful result with the verified property
282
+ if ('verified' in _timestampResult && _timestampResult.verified) {
283
+ // Check if results property exists
284
+ if ('results' in _timestampResult && _timestampResult.results) {
285
+ for (const chain in _timestampResult.results) {
286
+ let _results = _timestampResult.results[chain];
287
+ const _date = moment(_results.timestamp * 1000).tz(moment.tz.guess()).format('MM-DD-YYYY hh:mma z');
288
+ const _blockchain = chain.charAt(0).toUpperCase() + chain.slice(1);
289
+ console.log('Success! ' + _blockchain + ' block ' + _results.height + ' attests existence as of ' + _date);
290
+ }
291
+ }
292
+
293
+ // Check if upgradedOts property exists
294
+ if ('upgradedOts' in _timestampResult && _timestampResult.upgradedOts) {
295
+ const upgrade = await askUpgrade();
296
+ if (upgrade) {
297
+ let upgradeBuf = Buffer.from(_timestampResult.upgradedOts);
298
+ await zipKit.upgradeOTS(zipFile, upgradeBuf);
299
+ console.log('OTS upgraded - Certificate generation is now possible');
300
+ } else {
301
+ console.log('Certificate generation is not possible until timestamp is validated');
302
+ }
303
+ }
304
+ } else if ('error' in _timestampResult && !_timestampResult.error) {
305
+ console.log('Archive timestamp awaiting submittion to blockchain');
306
+ if (options.C || options.P) {
307
+ console.log('Certificate generation is not possible until timestamp is validated');
308
+ }
309
+ }
310
+ } else {
311
+ console.log('No timestamp information available');
312
+ }
313
+ }
314
+ }
315
+ } catch (error) {
316
+ console.error('Error processing zip file:', error);
317
+ exit(1);
318
+ }
319
+ }
320
+
321
+ /**
322
+ * Writes an extracted file to disk
323
+ * @param filename - Path where the file should be written
324
+ * @param outBuf - Buffer containing file data
325
+ * @param entry - ZIP entry associated with the file
326
+ * @param fileAttr - Optional Unix file permissions
327
+ * @returns boolean indicating success
328
+ */
329
+ function writeFile(filename: string, outBuf: Buffer, entry?: ZipEntry, fileAttr?: number | undefined) {
330
+ // get Unix file permissions
331
+ const _fattr = fileAttr ? (((fileAttr >>> 0) | 0) >> 16) & 0xfff : 0;
332
+
333
+ var fd;
334
+ try {
335
+ fd = fs.openSync(filename, "w", 438); // 0666
336
+ } catch (e) {
337
+ fs.chmodSync(filename, 438);
338
+ fd = fs.openSync(filename, "w", 438);
339
+ }
340
+ if (!fd) return false;
341
+
342
+ try {
343
+ fs.writeSync(fd, outBuf, 0, outBuf.length, 0);
344
+ } finally {
345
+ fs.closeSync(fd);
346
+ }
347
+
348
+ if (_fattr)
349
+ fs.chmodSync(filename, _fattr);
350
+
351
+ // Set file modification time if universal time is available
352
+ if (entry && entry.universalTime) {
353
+ const mtime = new Date(entry.universalTime * 1000); // Convert Unix timestamp (seconds) to milliseconds
354
+ try {
355
+ // Set both access time and modification time to the universal time
356
+ fs.utimesSync(filename, mtime, mtime);
357
+ } catch (e) {
358
+ console.error(`Warning: Could not set file time for ${filename}: ${e}`);
359
+ }
360
+ }
361
+
362
+ // Set owner and group if UID and GID are available (Unix-like systems only)
363
+ if (entry && (entry.uid !== null || entry.gid !== null)) {
364
+ try {
365
+ // Use process functions as fallbacks if they exist (Unix-like systems)
366
+ // Default to 0 (root) if neither entry values nor process functions are available
367
+ const uid = entry.uid !== null ? entry.uid :
368
+ (typeof process.getuid === 'function' ? process.getuid() : 0);
369
+ const gid = entry.gid !== null ? entry.gid :
370
+ (typeof process.getgid === 'function' ? process.getgid() : 0);
371
+
372
+ // Change owner and group of the file
373
+ fs.chownSync(filename, uid, gid);
374
+ } catch (e) {
375
+ // This might fail on Windows or if user doesn't have permission
376
+ console.error(`Warning: Could not set owner/group for ${filename}: ${e}`);
377
+ }
378
+ }
379
+
380
+ return true;
381
+ }
382
+
383
+ /**
384
+ * Checks if a file should be overwritten during extraction
385
+ * @param filename - Path of the file to check
386
+ * @returns Promise<boolean> indicating if file should be overwritten
387
+ */
388
+ async function testOverwrite(filename: string): Promise<boolean> {
389
+ // Check if we need to create a new folder
390
+ const _folder = path.dirname(filename);
391
+ if (!fs.existsSync(_folder)) {
392
+ console.log('Need to create directory: ', _folder);
393
+ // makeDir(_folder);
394
+ }
395
+
396
+ const _stat = fs.statSync(filename);
397
+ if (_stat.isDirectory()) {
398
+ console.log(': ', filename);
399
+ return false;
400
+ }
401
+
402
+ if (!fs.existsSync(filename))
403
+ return true;
404
+
405
+ if (options.n) {
406
+ return false;
407
+ } else if (options.o) {
408
+ return true;
409
+ }
410
+
411
+ return new Promise((resolve) => {
412
+ process.stdin.setRawMode(true);
413
+ process.stdin.resume();
414
+ process.stdout.write("file exists, Overwrite? (y/n/a): ");
415
+
416
+ process.stdin.once('data', (data: Buffer) => {
417
+ const char = data.toString().toLowerCase();
418
+
419
+ // Restore stdin to its previous state
420
+ process.stdin.setRawMode(false);
421
+ process.stdin.pause();
422
+ process.stdout.write(char);
423
+
424
+ if (char === 'y') {
425
+ resolve(true);
426
+ } else if (char === 'a') {
427
+ prt(" Aborted\n");
428
+ exit(0);
429
+ } else {
430
+ resolve(false);
431
+ }
432
+ });
433
+ });
434
+ }
435
+
436
+ /**
437
+ * Prompts user to confirm timestamp upgrade
438
+ * @returns Promise<boolean> indicating user's choice
439
+ */
440
+ async function askUpgrade(): Promise<boolean> {
441
+ return new Promise((resolve) => {
442
+ process.stdin.setRawMode(true);
443
+ process.stdin.resume();
444
+ process.stdout.write("Upgrade NeoTimestamp? (y/n): ");
445
+
446
+ process.stdin.once('data', (data: Buffer) => {
447
+ const char = data.toString().toLowerCase();
448
+
449
+ // Restore stdin to its previous state
450
+ process.stdin.setRawMode(false);
451
+ process.stdin.pause();
452
+ process.stdout.write(char + '\n');
453
+
454
+ if (char === 'y') {
455
+ resolve(true);
456
+ } else {
457
+ resolve(false);
458
+ }
459
+ });
460
+ });
461
+ }
462
+
463
+ /**
464
+ * Prompts for password when extracting encrypted files
465
+ * @param entry - ZIP entry requiring password
466
+ * @param retry - Boolean indicating if this is a retry attempt
467
+ * @returns Promise<string> containing the entered password
468
+ */
469
+ async function inputPassword(entry: ZipEntry, retry: boolean): Promise<string> {
470
+ const rl = readline.createInterface({
471
+ input: process.stdin,
472
+ output: process.stdout,
473
+ terminal: true
474
+ });
475
+
476
+ return new Promise<string>((resolve) => {
477
+ rl.question(retry ?
478
+ 'password incorrect - renter: ' :
479
+ `[${zipFile}] ${entry.filename} password: `, (input: string) => {
480
+ rl.close();
481
+ resolve(input);
482
+ });
483
+ });
484
+ }
485
+
486
+ function prt(msg: string) {
487
+ process.stdout.write(msg);
488
+ }