neozip-cli 0.70.0-alpha

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 (43) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/DOCUMENTATION.md +194 -0
  3. package/LICENSE +22 -0
  4. package/README.md +504 -0
  5. package/WHY_NEOZIP.md +212 -0
  6. package/bin/neolist +16 -0
  7. package/bin/neounzip +16 -0
  8. package/bin/neozip +15 -0
  9. package/dist/neozipkit-bundles/blockchain.js +13091 -0
  10. package/dist/neozipkit-bundles/browser.js +5733 -0
  11. package/dist/neozipkit-bundles/core.js +3766 -0
  12. package/dist/neozipkit-bundles/server.js +14996 -0
  13. package/dist/neozipkit-wrappers/blockchain/core/contracts.js +16 -0
  14. package/dist/neozipkit-wrappers/blockchain/index.js +2 -0
  15. package/dist/neozipkit-wrappers/core/ZipDecompress.js +2 -0
  16. package/dist/neozipkit-wrappers/core/components/HashCalculator.js +2 -0
  17. package/dist/neozipkit-wrappers/core/components/Logger.js +2 -0
  18. package/dist/neozipkit-wrappers/core/constants/Errors.js +2 -0
  19. package/dist/neozipkit-wrappers/core/constants/Headers.js +2 -0
  20. package/dist/neozipkit-wrappers/core/encryption/ZipCrypto.js +7 -0
  21. package/dist/neozipkit-wrappers/core/index.js +3 -0
  22. package/dist/neozipkit-wrappers/index.js +13 -0
  23. package/dist/neozipkit-wrappers/server/index.js +2 -0
  24. package/dist/src/config/ConfigSetup.js +455 -0
  25. package/dist/src/config/ConfigStore.js +373 -0
  26. package/dist/src/config/ConfigWizard.js +453 -0
  27. package/dist/src/config/WalletConfig.js +372 -0
  28. package/dist/src/exit-codes.js +210 -0
  29. package/dist/src/index.js +141 -0
  30. package/dist/src/neolist.js +1194 -0
  31. package/dist/src/neounzip.js +2177 -0
  32. package/dist/src/neozip/CommentManager.js +240 -0
  33. package/dist/src/neozip/blockchain.js +383 -0
  34. package/dist/src/neozip/createZip.js +2273 -0
  35. package/dist/src/neozip/file-operations.js +920 -0
  36. package/dist/src/neozip/types.js +6 -0
  37. package/dist/src/neozip/user-interaction.js +256 -0
  38. package/dist/src/neozip/utils.js +96 -0
  39. package/dist/src/neozip.js +785 -0
  40. package/dist/src/server/CommentManager.js +240 -0
  41. package/dist/src/version.js +59 -0
  42. package/env.example +101 -0
  43. package/package.json +175 -0
@@ -0,0 +1,1194 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * NEOLIST - Standalone ZIP archive listing tool
5
+ * Usage: neolist [options] <archive>
6
+ */
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
40
+ var __importDefault = (this && this.__importDefault) || function (mod) {
41
+ return (mod && mod.__esModule) ? mod : { "default": mod };
42
+ };
43
+ Object.defineProperty(exports, "__esModule", { value: true });
44
+ exports.main = main;
45
+ exports.parseArgs = parseArgs;
46
+ exports.showHelp = showHelp;
47
+ exports.showExtendedHelp = showExtendedHelp;
48
+ const fs = __importStar(require("fs"));
49
+ const server_1 = require('../neozipkit-wrappers/server');
50
+ const src_1 = require('../neozipkit-wrappers');
51
+ const chalk_1 = __importDefault(require("chalk"));
52
+ const ora_1 = __importDefault(require("ora"));
53
+ const version_1 = require("./version");
54
+ const exit_codes_1 = require("./exit-codes");
55
+ /**
56
+ * Helper function to check if ZIP is file-based
57
+ */
58
+ function isFileBased(zip) {
59
+ try {
60
+ zip.getFileHandle();
61
+ return true;
62
+ }
63
+ catch {
64
+ return false;
65
+ }
66
+ }
67
+ /**
68
+ * Logging function that respects quiet mode
69
+ */
70
+ function log(message, options = { verbose: false, quiet: false, json: false, short: false, unix: false, basic: false, opt1: false, opt2: false, optT: false, optC: false }) {
71
+ if (!options.quiet) {
72
+ console.log(message);
73
+ }
74
+ }
75
+ /**
76
+ * Error logging function (always shows errors)
77
+ */
78
+ function logError(message) {
79
+ console.error(message);
80
+ }
81
+ /**
82
+ * Debug logging function (only shows in debug mode)
83
+ */
84
+ function logDebug(message, options = { verbose: false, quiet: false, json: false, short: false, unix: false, basic: false, opt1: false, opt2: false, optT: false, optC: false }) {
85
+ if (options.debug && !options.quiet) {
86
+ console.log(`🐛 DEBUG: ${message}`);
87
+ }
88
+ }
89
+ /**
90
+ * Pad string to the right
91
+ */
92
+ function padRight(s, n) {
93
+ return (s + ' '.repeat(n)).slice(0, n);
94
+ }
95
+ /**
96
+ * Format bytes for display
97
+ */
98
+ function formatBytes(bytes) {
99
+ const units = ['B', 'KB', 'MB', 'GB'];
100
+ let i = 0;
101
+ let v = bytes;
102
+ while (v >= 1024 && i < units.length - 1) {
103
+ v /= 1024;
104
+ i++;
105
+ }
106
+ return `${v.toFixed(i === 0 ? 0 : 1)} ${units[i]}`;
107
+ }
108
+ /**
109
+ * Pad string to the left
110
+ */
111
+ function padLeft(s, n) {
112
+ return ((' '.repeat(n)) + s).slice(-n);
113
+ }
114
+ /**
115
+ * Convert date to DOS date parts
116
+ */
117
+ function toDateParts(date) {
118
+ const year = date.getFullYear();
119
+ const month = (date.getMonth() + 1).toString().padStart(2, '0');
120
+ const day = date.getDate().toString().padStart(2, '0');
121
+ const hours = date.getHours().toString().padStart(2, '0');
122
+ const minutes = date.getMinutes().toString().padStart(2, '0');
123
+ return {
124
+ date: `${month}-${day}-${year}`,
125
+ time: `${hours}:${minutes}`
126
+ };
127
+ }
128
+ /**
129
+ * Convert date to decimal format (YYYYMMDD.HHMMSS)
130
+ */
131
+ function toDecimalTime(date) {
132
+ const year = date.getFullYear();
133
+ const month = (date.getMonth() + 1).toString().padStart(2, '0');
134
+ const day = date.getDate().toString().padStart(2, '0');
135
+ const hours = date.getHours().toString().padStart(2, '0');
136
+ const minutes = date.getMinutes().toString().padStart(2, '0');
137
+ const seconds = date.getSeconds().toString().padStart(2, '0');
138
+ return `${year}${month}${day}.${hours}${minutes}${seconds}`;
139
+ }
140
+ /**
141
+ * Get method label
142
+ */
143
+ function methodLabel(method) {
144
+ switch (method.toLowerCase()) {
145
+ case 'stored': return 'Stored';
146
+ case 'deflate': return 'Deflate';
147
+ case 'deflate64': return 'Deflate64';
148
+ case 'bzip2': return 'BZip2';
149
+ case 'lzma': return 'LZMA';
150
+ case 'ppmd': return 'PPMd';
151
+ case 'zstd': return 'Zstd';
152
+ default: return method;
153
+ }
154
+ }
155
+ /**
156
+ * Get method name from method code
157
+ */
158
+ function getMethodName(method) {
159
+ switch (method) {
160
+ case 0: return 'stored';
161
+ case 8: return 'deflate';
162
+ case 9: return 'deflate64';
163
+ case 12: return 'bzip2';
164
+ case 14: return 'lzma';
165
+ case 95: return 'ppmd';
166
+ case 93: return 'zstd'; // Z Standard compression
167
+ default: return `unknown (${method})`;
168
+ }
169
+ }
170
+ /**
171
+ * Get extra field type name from header ID
172
+ */
173
+ function getExtraFieldTypeName(headerId) {
174
+ switch (headerId) {
175
+ case 0x0001: return 'ZIP64 Extended Information';
176
+ case 0x0007: return 'AV Info';
177
+ case 0x0008: return 'Reserved for extended language encoding data';
178
+ case 0x0009: return 'OS/2 Extended Attributes';
179
+ case 0x000a: return 'NTFS';
180
+ case 0x000c: return 'OpenVMS';
181
+ case 0x000d: return 'Unix';
182
+ case 0x000e: return 'Reserved for patch descriptor';
183
+ case 0x000f: return 'PKCS#7 Store for X.509 Certificates';
184
+ case 0x0014: return 'X.509 Certificate ID and Signature for individual file';
185
+ case 0x0015: return 'X.509 Certificate ID for Central Directory';
186
+ case 0x0016: return 'X.509 Certificate ID for Central Directory';
187
+ case 0x0017: return 'Strong Encryption Header';
188
+ case 0x0018: return 'Record Management Controls';
189
+ case 0x0019: return 'PKCS#7 Encryption Recipient Certificate List';
190
+ case 0x0065: return 'IBM S/390 (Z390), AS/400 (I400) uncompressed attributes';
191
+ case 0x0066: return 'Reserved for IBM S/390 (Z390), AS/400 (I400) compressed attributes';
192
+ case 0x4690: return 'POSZIP';
193
+ case 0x5455: return 'Extended Timestamp';
194
+ case 0x554e: return 'UNIX Extra Field (UZIP)';
195
+ case 0x5855: return 'Info-ZIP Unicode Path Extra Field';
196
+ case 0x6375: return 'Info-ZIP Unicode Comment Extra Field';
197
+ case 0x7075: return 'Info-ZIP Unicode Path Extra Field';
198
+ case 0x7855: return 'Unix Symbolic Link Extra Field';
199
+ case 0x7865: return 'Unix Hard Link Extra Field';
200
+ case 0x7875: return 'Unix UID/GID Extra Field';
201
+ case 0x014E: return 'SHA-256 Hash Extra Field';
202
+ default: return `Unknown (0x${headerId.toString(16).padStart(4, '0')})`;
203
+ }
204
+ }
205
+ /**
206
+ * Get the actual uncompressed size, checking for ZIP64 values
207
+ */
208
+ function getActualUncompressedSize(entry) {
209
+ // Check if this is a ZIP64 file (32-bit size is 0xFFFFFFFF)
210
+ if (entry.uncompressedSize === 0xFFFFFFFF && entry.extraField) {
211
+ // Look for ZIP64 Extended Information (0x0001)
212
+ let offset = 0;
213
+ while (offset < entry.extraField.length - 4) {
214
+ const headerId = entry.extraField.readUInt16LE(offset);
215
+ const dataSize = entry.extraField.readUInt16LE(offset + 2);
216
+ if (headerId === 0x0001 && dataSize >= 8) {
217
+ // Read the ZIP64 uncompressed size (first 8 bytes)
218
+ return Number(entry.extraField.readBigUInt64LE(offset + 4));
219
+ }
220
+ offset += 4 + dataSize;
221
+ }
222
+ }
223
+ // Return the regular 32-bit size
224
+ return entry.uncompressedSize || 0;
225
+ }
226
+ /**
227
+ * Get the actual compressed size, checking for ZIP64 values
228
+ */
229
+ function getActualCompressedSize(entry) {
230
+ // Check if this is a ZIP64 file (32-bit size is 0xFFFFFFFF)
231
+ if (entry.compressedSize === 0xFFFFFFFF && entry.extraField) {
232
+ // Look for ZIP64 Extended Information (0x0001)
233
+ let offset = 0;
234
+ while (offset < entry.extraField.length - 4) {
235
+ const headerId = entry.extraField.readUInt16LE(offset);
236
+ const dataSize = entry.extraField.readUInt16LE(offset + 2);
237
+ if (headerId === 0x0001 && dataSize >= 16) {
238
+ // Read the ZIP64 compressed size (second 8 bytes)
239
+ return Number(entry.extraField.readBigUInt64LE(offset + 12));
240
+ }
241
+ offset += 4 + dataSize;
242
+ }
243
+ }
244
+ // Return the regular 32-bit size
245
+ return entry.compressedSize || 0;
246
+ }
247
+ /**
248
+ * Format entry name for display (handles blockchain metadata)
249
+ */
250
+ function formatEntryName(entry) {
251
+ if (entry.filename === src_1.TOKENIZED_METADATA) {
252
+ return `${src_1.TOKENIZED_METADATA} (NFT Token)`;
253
+ }
254
+ if (entry.filename === src_1.TIMESTAMP_METADATA) {
255
+ return `${src_1.TIMESTAMP_METADATA} (Timestamp Metadata)`;
256
+ }
257
+ if (entry.filename === src_1.TIMESTAMP_SUBMITTED) {
258
+ return `${src_1.TIMESTAMP_SUBMITTED} (Timestamp Submitted)`;
259
+ }
260
+ return entry.filename;
261
+ }
262
+ /**
263
+ * Read archive entries
264
+ */
265
+ async function readArchiveEntries(archivePath, debugMode = false, quiet = false, inMemory = false) {
266
+ let spinner = null;
267
+ try {
268
+ if (!quiet) {
269
+ spinner = (0, ora_1.default)('Reading archive...').start();
270
+ }
271
+ // Create ZipkitServer instance
272
+ const zip = new server_1.ZipkitServer();
273
+ let entries;
274
+ if (inMemory) {
275
+ // For Buffer-based loading, read the entire file into buffer and get entries in one call
276
+ const buffer = fs.readFileSync(archivePath);
277
+ entries = zip.loadZip(buffer);
278
+ }
279
+ else {
280
+ // For file-based loading
281
+ entries = await zip.loadZipFile(archivePath);
282
+ }
283
+ // Return the actual ZipEntry instances which now include symbolic and hard link properties
284
+ const zipEntries = entries;
285
+ if (!quiet) {
286
+ spinner.succeed(chalk_1.default.green('Archive read successfully'));
287
+ }
288
+ return { entries: zipEntries, zip };
289
+ }
290
+ catch (error) {
291
+ if (spinner) {
292
+ spinner.fail(chalk_1.default.red('Failed to read archive'));
293
+ }
294
+ throw error;
295
+ }
296
+ }
297
+ /**
298
+ * Output entries in short format (names only)
299
+ */
300
+ function outputShort(entries, archivePath, zip, options = { verbose: false, quiet: false, json: false, short: false, unix: false, basic: false, opt1: false, opt2: false, optT: false, optC: false }) {
301
+ entries.forEach(entry => {
302
+ console.log(formatEntryName(entry));
303
+ });
304
+ // Show archive comment by default unless suppressed
305
+ if (!options.noComments) {
306
+ const archiveComment = zip.getZipComment();
307
+ if (archiveComment && archiveComment.trim()) {
308
+ log(archiveComment, options);
309
+ }
310
+ }
311
+ }
312
+ /**
313
+ * Output entries in filenames-only format
314
+ */
315
+ function outputFilenamesOnly(entries, archivePath, zip, options = { verbose: false, quiet: false, json: false, short: false, unix: false, basic: false, opt1: false, opt2: false, optT: false, optC: false }) {
316
+ entries.forEach(entry => {
317
+ console.log(formatEntryName(entry));
318
+ });
319
+ }
320
+ /**
321
+ * Output entries in filenames with header/totals format
322
+ */
323
+ function outputFilenamesWithHeader(entries, archivePath, zip, options = { verbose: false, quiet: false, json: false, short: false, unix: false, basic: false, opt1: false, opt2: false, optT: false, optC: false }) {
324
+ // -2 allows -h and -t options, so we need to check for them
325
+ // For now, just output filenames like -1, but this can be extended
326
+ entries.forEach(entry => {
327
+ console.log(formatEntryName(entry));
328
+ });
329
+ }
330
+ /**
331
+ * Output entries in sortable decimal time format
332
+ */
333
+ function outputDecimalTime(entries, archivePath, zip, options = { verbose: false, quiet: false, json: false, short: false, unix: false, basic: false, opt1: false, opt2: false, optT: false, optC: false }) {
334
+ const stats = fs.statSync(archivePath);
335
+ log(`Archive: ${archivePath}`, options);
336
+ log(`Zip file size: ${stats.size} bytes, number of entries: ${entries.length}`, options);
337
+ entries.forEach(entry => {
338
+ const modifiedDate = entry.parseDateTime(entry.timeDateDOS) || new Date();
339
+ const decimalTime = toDecimalTime(modifiedDate);
340
+ const method = methodLabel(getMethodName(entry.cmpMethod));
341
+ const compressedSize = entry.compressedSize;
342
+ const uncompressedSize = entry.uncompressedSize;
343
+ const ratio = uncompressedSize > 0 ? Math.round((1 - compressedSize / uncompressedSize) * 100) : 0;
344
+ const crc = ((entry.crc || 0) >>> 0).toString(16).toUpperCase().padStart(8, '0');
345
+ const version = `${Math.floor(entry.verExtract / 10)}.${entry.verExtract % 10}`;
346
+ console.log(`?--------- ${version} unx ${padLeft(uncompressedSize.toString(), 8)} ${method} ${crc} ${decimalTime} ${formatEntryName(entry)}`);
347
+ });
348
+ }
349
+ /**
350
+ * Output entries in table format - enhanced to match working example
351
+ */
352
+ function outputTable(entries, archivePath, verbose, zip, options = { verbose: false, quiet: false, json: false, short: false, unix: false, basic: false, opt1: false, opt2: false, optT: false, optC: false }) {
353
+ const stats = fs.statSync(archivePath);
354
+ log(`Archive: ${archivePath}`, options);
355
+ if (verbose) {
356
+ // Detailed verbose format matching working example
357
+ const sep = ' ';
358
+ const lengthW = 8, sizeW = 7, cmprW = 4, methodW = 6, dateW = 10, timeW = 5, crcW = 8;
359
+ log(padLeft('Length', lengthW) + sep +
360
+ padRight('Method', methodW) + sep +
361
+ padLeft('Size', sizeW) + ' ' +
362
+ padRight('Cmpr', cmprW) + ' ' +
363
+ padRight('Date', dateW) + ' ' +
364
+ padRight('Time', timeW) + ' ' +
365
+ padRight('CRC-32', crcW) + sep +
366
+ 'Name', options);
367
+ log('-------- ------ ------- ---- ---------- ----- -------- ----', options);
368
+ let totalLen = 0;
369
+ let totalCmp = 0;
370
+ entries.forEach(entry => {
371
+ const unc = getActualUncompressedSize(entry);
372
+ const cmp = getActualCompressedSize(entry);
373
+ totalLen += unc;
374
+ totalCmp += cmp;
375
+ const cmpr = unc > 0 ? padRight(String(Math.max(0, Math.round((1 - cmp / unc) * 100))) + '%', cmprW) : padRight('0%', cmprW);
376
+ const modifiedDate = entry.parseDateTime(entry.timeDateDOS) || new Date();
377
+ const { date, time } = toDateParts(modifiedDate);
378
+ const crc = ((entry.crc || 0) >>> 0).toString(16).padStart(8, '0');
379
+ log(padLeft(String(unc), lengthW) + sep +
380
+ padRight(methodLabel(getMethodName(entry.cmpMethod)), methodW) + sep +
381
+ padLeft(String(cmp), sizeW) + ' ' +
382
+ cmpr + ' ' +
383
+ padRight(date, dateW) + ' ' +
384
+ padRight(time, timeW) + ' ' +
385
+ padRight(crc, crcW) + sep +
386
+ (formatEntryName(entry) || ''), options);
387
+ // Show file comment below the file entry
388
+ if (entry.comment && !options.noComments) {
389
+ log(entry.comment, options);
390
+ }
391
+ });
392
+ log('-------- ------ ------- ---- ---------- ----- -------- ----', options);
393
+ const filesCount = entries.length;
394
+ const compressionRatio = totalLen > 0 ? Math.round((1 - totalCmp / totalLen) * 100) : 0;
395
+ log(padLeft(String(totalLen), lengthW) + ' ' +
396
+ padLeft(String(totalCmp), sizeW) + ' ' +
397
+ padRight(`${compressionRatio}%`, 4) + ' ' +
398
+ padRight(`${filesCount} files`, 0), options);
399
+ }
400
+ else {
401
+ // Simple format
402
+ const sep = ' ';
403
+ const lengthW = 8, dateW = 10, timeW = 5;
404
+ log(padLeft('Length', lengthW) + sep +
405
+ padRight('Date', dateW) + ' ' +
406
+ padRight('Time', timeW) + sep +
407
+ 'Name', options);
408
+ log('-'.repeat(lengthW) + sep +
409
+ '-'.repeat(dateW) + ' ' +
410
+ '-'.repeat(timeW) + sep +
411
+ '----', options);
412
+ let totalLen = 0;
413
+ entries.forEach(entry => {
414
+ const modifiedDate = entry.parseDateTime(entry.timeDateDOS) || new Date();
415
+ const { date, time } = toDateParts(modifiedDate);
416
+ const unc = getActualUncompressedSize(entry);
417
+ totalLen += unc;
418
+ log(padLeft(String(unc), lengthW) + sep +
419
+ padRight(date, dateW) + ' ' +
420
+ padRight(time, timeW) + sep +
421
+ (formatEntryName(entry) || ''), options);
422
+ // Show file comment below the file entry
423
+ if (entry.comment && !options.noComments) {
424
+ log(entry.comment, options);
425
+ }
426
+ });
427
+ log('-'.repeat(lengthW) + sep +
428
+ ' '.repeat(dateW) + ' ' +
429
+ ' '.repeat(timeW) + sep +
430
+ '-------', options);
431
+ log(padLeft(String(totalLen), lengthW) + sep +
432
+ ' '.repeat(dateW) + ' ' +
433
+ ' '.repeat(timeW) + sep +
434
+ `${entries.length} files`, options);
435
+ }
436
+ // File comments are now shown inline below each file entry
437
+ // Show archive comment by default (like unzip -l) unless suppressed
438
+ if (!options.noComments) {
439
+ const archiveComment = zip.getZipComment();
440
+ if (archiveComment && archiveComment.trim()) {
441
+ log(archiveComment, options);
442
+ }
443
+ }
444
+ }
445
+ /**
446
+ * Output entries in verbose format with central directory details
447
+ */
448
+ function outputVerbose(entries, archivePath, zip, options = { verbose: false, quiet: false, json: false, short: false, unix: false, basic: false, opt1: false, opt2: false, optT: false, optC: false }) {
449
+ const stats = fs.statSync(archivePath);
450
+ log(`Archive: ${archivePath}`, options);
451
+ log(`Central Directory Information:`, options);
452
+ log('', options);
453
+ entries.forEach((entry, index) => {
454
+ // Use ZipEntry's showVerboseInfo() method for detailed information
455
+ log(`File ${index + 1}: ${formatEntryName(entry)}`, options);
456
+ // Call showVerboseInfo() directly - it will output to console
457
+ entry.showVerboseInfo();
458
+ log('', options);
459
+ });
460
+ // Summary
461
+ const totalSize = entries.reduce((sum, entry) => sum + getActualUncompressedSize(entry), 0);
462
+ const totalCompressedSize = entries.reduce((sum, entry) => sum + getActualCompressedSize(entry), 0);
463
+ const compressionRatio = totalSize > 0 ? Math.round((1 - totalCompressedSize / totalSize) * 100) : 0;
464
+ log('📊 Archive Summary:', options);
465
+ log(` Files: ${entries.length}`, options);
466
+ log(` Uncompressed: ${formatBytes(totalSize)}`, options);
467
+ log(` Compressed: ${formatBytes(totalCompressedSize)}`, options);
468
+ log(` Compression: ${compressionRatio}%`, options);
469
+ log(` Archive size: ${formatBytes(stats.size)}`, options);
470
+ }
471
+ /**
472
+ * Output entries in long format
473
+ */
474
+ function outputUnix(entries, archivePath, zip, options = { verbose: false, quiet: false, json: false, short: false, unix: false, basic: false, opt1: false, opt2: false, optT: false, optC: false }) {
475
+ const stats = fs.statSync(archivePath);
476
+ log(`Archive: ${archivePath}`, options);
477
+ log(`Zip file size: ${stats.size} bytes, number of entries: ${entries.length}`, options);
478
+ entries.forEach(entry => {
479
+ // Format: permissions version os size method date time filename
480
+ // Example: ?--------- 3.0 unx 111261 bx u093 25-Sep-22 10:19 bib
481
+ // File permissions (simplified - ? for unknown, - for file, d for directory, l for symlink)
482
+ let permissions = '?---------'; // Default
483
+ if (entry.isDirectory) {
484
+ permissions = 'd---------';
485
+ }
486
+ else if (entry.isSymlink) {
487
+ permissions = 'l---------';
488
+ }
489
+ else {
490
+ permissions = '?---------';
491
+ }
492
+ // Version (extract version / 10)
493
+ const version = `${(entry.verExtract || 30) / 10}`;
494
+ // OS detection
495
+ let os = 'unx'; // Default to Unix
496
+ if (entry.isMSDOS)
497
+ os = 'dos';
498
+ else if (entry.isMacOS)
499
+ os = 'mac';
500
+ else if (entry.isLinux)
501
+ os = 'unx';
502
+ else if (entry.isUnixLike)
503
+ os = 'unx';
504
+ // Size (uncompressed)
505
+ const size = entry.uncompressedSize || 0;
506
+ // Compression method
507
+ const methodName = getMethodName(entry.cmpMethod);
508
+ let method = 'bx'; // Default
509
+ switch (methodName.toLowerCase()) {
510
+ case 'stored':
511
+ method = 'st';
512
+ break;
513
+ case 'deflate':
514
+ method = 'df';
515
+ break;
516
+ case 'deflate64':
517
+ method = 'df';
518
+ break;
519
+ case 'bzip2':
520
+ method = 'b2';
521
+ break;
522
+ case 'lzma':
523
+ method = 'lm';
524
+ break;
525
+ case 'ppmd':
526
+ method = 'pm';
527
+ break;
528
+ case 'zstd':
529
+ method = 'zs';
530
+ break;
531
+ default:
532
+ method = 'bx';
533
+ break;
534
+ }
535
+ // Compression method code
536
+ const methodCode = methodName.toLowerCase() === 'zstd' ? 'u093' : 'u008';
537
+ // Date format (DD-MMM-YY)
538
+ const date = entry.parseDateTime(entry.timeDateDOS) || new Date();
539
+ const day = date.getDate().toString().padStart(2, '0');
540
+ const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
541
+ 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
542
+ const month = monthNames[date.getMonth()];
543
+ const year = date.getFullYear().toString().slice(-2);
544
+ const dateStr = `${day}-${month}-${year}`;
545
+ // Time format (HH:MM)
546
+ const timeStr = `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
547
+ // Filename with link information
548
+ let filename = formatEntryName(entry);
549
+ if (entry.isSymlink && entry.linkTarget) {
550
+ filename += ` -> ${entry.linkTarget}`;
551
+ }
552
+ else if (entry.isHardLink && entry.originalEntry) {
553
+ filename += ` [hard link to ${entry.originalEntry}]`;
554
+ }
555
+ // Format the line
556
+ const line = `${permissions} ${version} ${os} ${size.toString().padStart(8)} ${method} ${methodCode} ${dateStr} ${timeStr} ${filename}`;
557
+ log(line, options);
558
+ });
559
+ // Summary line
560
+ const totalSize = entries.reduce((sum, entry) => sum + (entry.uncompressedSize || 0), 0);
561
+ const totalCompressedSize = entries.reduce((sum, entry) => sum + (entry.compressedSize || 0), 0);
562
+ const compressionRatio = totalSize > 0 ? Math.round((1 - totalCompressedSize / totalSize) * 100) : 0;
563
+ log(`${entries.length} files, ${totalSize} bytes uncompressed, ${totalCompressedSize} bytes compressed: ${compressionRatio}.${Math.round((compressionRatio % 1) * 10)}%`, options);
564
+ // File comments are now shown inline below each file entry
565
+ // Show archive comment by default (like unzip -l) unless suppressed
566
+ if (!options.noComments) {
567
+ const archiveComment = zip.getZipComment();
568
+ if (archiveComment && archiveComment.trim()) {
569
+ log(archiveComment, options);
570
+ }
571
+ }
572
+ }
573
+ /**
574
+ * Sort entries based on the specified criteria
575
+ */
576
+ function sortEntries(entries, sortBy) {
577
+ if (!sortBy) {
578
+ return entries;
579
+ }
580
+ return [...entries].sort((a, b) => {
581
+ switch (sortBy) {
582
+ case 'name':
583
+ return a.filename.localeCompare(b.filename);
584
+ case 'size':
585
+ return (b.uncompressedSize || 0) - (a.uncompressedSize || 0); // Descending order (largest first)
586
+ case 'date':
587
+ const dateA = a.parseDateTime(a.timeDateDOS) || new Date(0);
588
+ const dateB = b.parseDateTime(b.timeDateDOS) || new Date(0);
589
+ return dateB.getTime() - dateA.getTime(); // Descending order (newest first)
590
+ default:
591
+ return 0;
592
+ }
593
+ });
594
+ }
595
+ /**
596
+ * Filter entries based on include/exclude patterns
597
+ */
598
+ function filterEntries(entries, options) {
599
+ if (!options.include && !options.exclude) {
600
+ return entries;
601
+ }
602
+ return entries.filter(entry => {
603
+ const fileName = entry.filename;
604
+ // Check exclude patterns first
605
+ if (options.exclude && options.exclude.length > 0) {
606
+ for (const pattern of options.exclude) {
607
+ if (matchesPattern(fileName, pattern, options.optC)) {
608
+ return false; // Exclude this file
609
+ }
610
+ }
611
+ }
612
+ // Check include patterns
613
+ if (options.include && options.include.length > 0) {
614
+ for (const pattern of options.include) {
615
+ if (matchesPattern(fileName, pattern, options.optC)) {
616
+ return true; // Include this file
617
+ }
618
+ }
619
+ return false; // Not in any include pattern
620
+ }
621
+ return true; // No exclude patterns matched
622
+ });
623
+ }
624
+ /**
625
+ * Check if a filename matches a pattern (supports wildcards)
626
+ */
627
+ function matchesPattern(fileName, pattern, caseInsensitive = false) {
628
+ // Convert glob pattern to regex
629
+ const regexPattern = pattern
630
+ .replace(/\./g, '\\.') // Escape dots
631
+ .replace(/\*/g, '.*') // Convert * to .*
632
+ .replace(/\?/g, '.'); // Convert ? to .
633
+ const flags = caseInsensitive ? 'i' : '';
634
+ const regex = new RegExp(`^${regexPattern}$`, flags);
635
+ return regex.test(fileName);
636
+ }
637
+ /**
638
+ * Get operating system name from ZIP OS code
639
+ */
640
+ function getOSName(osCode) {
641
+ const osNames = {
642
+ 0: 'MS-DOS and OS/2',
643
+ 1: 'Amiga',
644
+ 2: 'OpenVMS',
645
+ 3: 'UNIX',
646
+ 4: 'VM/CMS',
647
+ 5: 'Atari ST',
648
+ 6: 'OS/2 H.P.F.S.',
649
+ 7: 'Macintosh',
650
+ 8: 'Z-System',
651
+ 9: 'CP/M',
652
+ 10: 'Windows NTFS',
653
+ 11: 'MVS',
654
+ 12: 'VSE',
655
+ 13: 'Acorn Risc',
656
+ 14: 'VFAT',
657
+ 15: 'Alternate MVS',
658
+ 16: 'BeOS',
659
+ 17: 'Tandem',
660
+ 18: 'OS/400',
661
+ 19: 'OS X'
662
+ };
663
+ return osNames[osCode] || `Unknown (${osCode})`;
664
+ }
665
+ /**
666
+ * Output entries in JSON format
667
+ */
668
+ function outputJson(entries, archivePath, zip, options = { verbose: false, quiet: false, json: false, short: false, unix: false, basic: false, opt1: false, opt2: false, optT: false, optC: false }) {
669
+ const stats = fs.statSync(archivePath);
670
+ const result = {
671
+ archive: archivePath,
672
+ size: stats.size,
673
+ fileCount: entries.length,
674
+ files: entries.map(entry => {
675
+ const fileInfo = {
676
+ name: entry.filename,
677
+ size: entry.uncompressedSize,
678
+ compressedSize: entry.compressedSize,
679
+ crc32: entry.crc,
680
+ method: getMethodName(entry.cmpMethod),
681
+ isDirectory: entry.isDirectory,
682
+ modified: (entry.parseDateTime(entry.timeDateDOS) || new Date()).toISOString()
683
+ };
684
+ // Add SHA-256 if present
685
+ if (entry.sha256) {
686
+ fileInfo.sha256 = entry.sha256;
687
+ }
688
+ // Add extended date information if present
689
+ if (entry.timeDateDOS) {
690
+ fileInfo.dosTimestamp = entry.timeDateDOS;
691
+ }
692
+ // Add platform information if present
693
+ if (entry.platform) {
694
+ fileInfo.platform = entry.platform;
695
+ }
696
+ // Add file attributes if present
697
+ if (entry.intFileAttr !== undefined) {
698
+ fileInfo.internalAttributes = entry.intFileAttr;
699
+ }
700
+ if (entry.extFileAttr !== undefined) {
701
+ fileInfo.externalAttributes = entry.extFileAttr;
702
+ }
703
+ // Add security information if present
704
+ if (entry.isEncrypted !== undefined) {
705
+ fileInfo.isEncrypted = entry.isEncrypted;
706
+ }
707
+ if (entry.isStrongEncrypt !== undefined) {
708
+ fileInfo.isStrongEncrypt = entry.isStrongEncrypt;
709
+ }
710
+ // Add symbolic link information if present
711
+ if (entry.isSymlink !== undefined) {
712
+ fileInfo.isSymlink = entry.isSymlink;
713
+ }
714
+ if (entry.linkTarget) {
715
+ fileInfo.linkTarget = entry.linkTarget;
716
+ }
717
+ // Add hard link information if present
718
+ if (entry.isHardLink !== undefined) {
719
+ fileInfo.isHardLink = entry.isHardLink;
720
+ }
721
+ if (entry.originalEntry) {
722
+ fileInfo.originalEntry = entry.originalEntry;
723
+ }
724
+ if (entry.inode !== undefined) {
725
+ fileInfo.inode = entry.inode;
726
+ }
727
+ // Add version information if present (properly parsed from ZIP format)
728
+ if (entry.verMadeBy !== undefined) {
729
+ const upperByte = (entry.verMadeBy >> 8) & 0xFF;
730
+ const lowerByte = entry.verMadeBy & 0xFF;
731
+ const majorVersion = Math.floor(lowerByte / 10);
732
+ const minorVersion = lowerByte % 10;
733
+ fileInfo.versionMadeBy = `${majorVersion}.${minorVersion}`;
734
+ fileInfo.osMadeBy = upperByte; // Operating system code
735
+ fileInfo.osMadeByName = getOSName(upperByte);
736
+ }
737
+ if (entry.verExtract !== undefined) {
738
+ const upperByte = (entry.verExtract >> 8) & 0xFF;
739
+ const lowerByte = entry.verExtract & 0xFF;
740
+ const majorVersion = Math.floor(lowerByte / 10);
741
+ const minorVersion = lowerByte % 10;
742
+ fileInfo.versionExtract = `${majorVersion}.${minorVersion}`;
743
+ fileInfo.osExtract = upperByte; // Operating system code
744
+ fileInfo.osExtractName = getOSName(upperByte);
745
+ }
746
+ // Add Unix-specific information if present
747
+ if (entry.uid !== undefined) {
748
+ fileInfo.uid = entry.uid;
749
+ }
750
+ if (entry.gid !== undefined) {
751
+ fileInfo.gid = entry.gid;
752
+ }
753
+ // Add file comment if present (unless suppressed)
754
+ if (!options.noComments && entry.comment) {
755
+ fileInfo.comment = entry.comment;
756
+ }
757
+ return fileInfo;
758
+ }),
759
+ summary: {
760
+ totalSize: entries.reduce((sum, entry) => sum + (entry.uncompressedSize || 0), 0),
761
+ totalCompressedSize: entries.reduce((sum, entry) => sum + (entry.compressedSize || 0), 0),
762
+ compressionRatio: entries.length > 0 ?
763
+ entries.reduce((sum, entry) => sum + ((entry.uncompressedSize || 0) - (entry.compressedSize || 0)), 0) /
764
+ entries.reduce((sum, entry) => sum + (entry.uncompressedSize || 0), 0) * 100 : 0
765
+ },
766
+ tokenized: entries.some(entry => entry.filename === 'META-INF/NZIP.TOKEN'),
767
+ ...(!options.noComments && zip.getZipComment() ? {
768
+ archiveComment: zip.getZipComment()
769
+ } : {})
770
+ };
771
+ log(JSON.stringify(result, null, 2), options);
772
+ }
773
+ /**
774
+ * Parse command line arguments
775
+ */
776
+ function parseArgs(args) {
777
+ const options = {
778
+ verbose: false,
779
+ quiet: false,
780
+ json: false,
781
+ short: false,
782
+ unix: false,
783
+ basic: false,
784
+ opt1: false,
785
+ opt2: false,
786
+ optT: false,
787
+ optC: false,
788
+ include: [],
789
+ exclude: [],
790
+ sortBy: undefined,
791
+ noComments: false,
792
+ debug: false
793
+ };
794
+ let archive = '';
795
+ for (let i = 0; i < args.length; i++) {
796
+ const arg = args[i];
797
+ switch (arg) {
798
+ case '-h':
799
+ showHelp();
800
+ process.exit(0);
801
+ break;
802
+ case '-h2':
803
+ case '--help-extended':
804
+ showExtendedHelp();
805
+ process.exit(0);
806
+ break;
807
+ case '--help':
808
+ showHelp();
809
+ process.exit(0);
810
+ break;
811
+ case '-V':
812
+ case '--version':
813
+ console.log(`NeoList Version: ${version_1.APP_VERSION} (${version_1.APP_RELEASE_DATE})`);
814
+ process.exit(0);
815
+ break;
816
+ case '-v':
817
+ case '--verbose':
818
+ options.verbose = true;
819
+ break;
820
+ case '-q':
821
+ case '--quiet':
822
+ options.quiet = true;
823
+ break;
824
+ case '-j':
825
+ case '--json':
826
+ options.json = true;
827
+ break;
828
+ case '-s':
829
+ case '--short':
830
+ options.short = true;
831
+ break;
832
+ case '-u':
833
+ case '--unix':
834
+ options.unix = true;
835
+ break;
836
+ case '-b':
837
+ case '--basic':
838
+ options.basic = true;
839
+ break;
840
+ case '-1':
841
+ options.opt1 = true;
842
+ break;
843
+ case '-2':
844
+ options.opt2 = true;
845
+ break;
846
+ case '-T':
847
+ options.optT = true;
848
+ break;
849
+ case '-C':
850
+ options.optC = true;
851
+ break;
852
+ case '-i':
853
+ case '--include':
854
+ if (i + 1 < args.length) {
855
+ options.include.push(args[++i]);
856
+ }
857
+ else {
858
+ console.error('Error: --include requires a pattern');
859
+ showHelp();
860
+ (0, exit_codes_1.exitZipinfo)(exit_codes_1.ZIPINFO_EXIT_CODES.INVALID_OPTIONS);
861
+ }
862
+ break;
863
+ case '-x':
864
+ case '--exclude':
865
+ if (i + 1 < args.length) {
866
+ options.exclude.push(args[++i]);
867
+ }
868
+ else {
869
+ console.error('Error: --exclude requires a pattern');
870
+ showHelp();
871
+ (0, exit_codes_1.exitZipinfo)(exit_codes_1.ZIPINFO_EXIT_CODES.INVALID_OPTIONS);
872
+ }
873
+ break;
874
+ case '-S':
875
+ case '--sort':
876
+ if (i + 1 < args.length) {
877
+ const sortType = args[++i].toLowerCase();
878
+ if (sortType === 'name' || sortType === 'size' || sortType === 'date') {
879
+ options.sortBy = sortType;
880
+ }
881
+ else {
882
+ console.error(`Error: Invalid sort type '${sortType}'. Must be 'name', 'size', or 'date'`);
883
+ showHelp();
884
+ (0, exit_codes_1.exitZipinfo)(exit_codes_1.ZIPINFO_EXIT_CODES.INVALID_OPTIONS);
885
+ }
886
+ }
887
+ else {
888
+ console.error('Error: --sort requires a sort type (name, size, or date)');
889
+ showHelp();
890
+ (0, exit_codes_1.exitZipinfo)(exit_codes_1.ZIPINFO_EXIT_CODES.INVALID_OPTIONS);
891
+ }
892
+ break;
893
+ case '--no-comments':
894
+ options.noComments = true;
895
+ break;
896
+ case '--debug':
897
+ options.debug = true;
898
+ break;
899
+ case '--in-memory':
900
+ options.inMemory = true;
901
+ break;
902
+ default:
903
+ if (arg.startsWith('-')) {
904
+ console.error(`Error: Unknown option ${arg}`);
905
+ showHelp();
906
+ (0, exit_codes_1.exitZipinfo)(exit_codes_1.ZIPINFO_EXIT_CODES.INVALID_OPTIONS);
907
+ }
908
+ else if (!archive) {
909
+ archive = arg;
910
+ }
911
+ break;
912
+ }
913
+ }
914
+ if (!archive) {
915
+ console.error('Error: Archive name is required');
916
+ showHelp();
917
+ (0, exit_codes_1.exitZipinfo)(exit_codes_1.ZIPINFO_EXIT_CODES.INVALID_OPTIONS);
918
+ }
919
+ if (!fs.existsSync(archive)) {
920
+ console.error(`Error: Archive not found: ${archive}`);
921
+ (0, exit_codes_1.exitZipinfo)(exit_codes_1.ZIPINFO_EXIT_CODES.FILES_NOT_FOUND);
922
+ }
923
+ return { archive, options };
924
+ }
925
+ /**
926
+ * Show help information
927
+ */
928
+ function showHelp() {
929
+ console.log(`
930
+ NeoList Version: ${version_1.APP_VERSION} (${version_1.APP_RELEASE_DATE})
931
+
932
+ Usage: neolist [options] <archive>
933
+
934
+ Options:
935
+ -h, --help Show this help message
936
+ -V, --version Show version information
937
+ -v, --verbose Detailed central directory information
938
+ -q, --quiet Suppress output
939
+ -j, --json Output in JSON format
940
+ -s, --short Short output (names only)
941
+ -u, --unix Unix-style output
942
+ -b, --basic Basic table output
943
+ -1 Filenames only, one per line
944
+ -2 Filenames with header/totals support
945
+ -T Sortable decimal time format
946
+ -C Case-insensitive filename matching
947
+ -i, --include Include only files matching pattern (can be used multiple times)
948
+ -x, --exclude Exclude files matching pattern (can be used multiple times)
949
+ -S, --sort Sort files by name, size, or date
950
+ --no-comments Suppress archive and file comments (shown by default)
951
+ --debug Enable debug output
952
+ --in-memory Force in-memory processing mode
953
+
954
+ Arguments:
955
+ <archive> ZIP file to list
956
+
957
+ Examples:
958
+ neolist output/calgary.nzip
959
+ neolist -b output/calgary.nzip
960
+ neolist -u output/calgary.nzip
961
+ `);
962
+ }
963
+ /**
964
+ * Show extended help information (equivalent to InfoZip's -h2)
965
+ */
966
+ function showExtendedHelp() {
967
+ console.log('Extended Help for NeoList');
968
+ console.log('');
969
+ console.log('NeoList is a next-generation ZIP listing utility that builds upon the strengths');
970
+ console.log('of the ZIP format while incorporating blockchain verification and modern features.');
971
+ console.log('');
972
+ console.log('Basic command line:');
973
+ console.log(' neolist [options] archive [file_patterns...]');
974
+ console.log('');
975
+ console.log('Some examples:');
976
+ console.log(' List all files in archive: neolist archive.nzip');
977
+ console.log(' List specific files: neolist archive.nzip "*.txt"');
978
+ console.log(' Verbose listing with details: neolist -v archive.nzip');
979
+ console.log('');
980
+ console.log('Basic modes:');
981
+ console.log(' -l list archive contents (default)');
982
+ console.log(' -v verbose listing with details');
983
+ console.log(' -s short listing (file names only)');
984
+ console.log(' -u unix-style listing');
985
+ console.log('');
986
+ console.log('Basic options:');
987
+ console.log(' -q quiet operation');
988
+ console.log(' -v verbose operation');
989
+ console.log(' -s short listing (file names only)');
990
+ console.log(' -u unix-style listing');
991
+ console.log(' -j json output format');
992
+ console.log(' -x exclude files matching patterns');
993
+ console.log(' -i include only files matching patterns');
994
+ console.log('');
995
+ console.log('Syntax:');
996
+ console.log(' The full command line syntax is:');
997
+ console.log('');
998
+ console.log(' neolist [options] archive [file_patterns...]');
999
+ console.log('');
1000
+ console.log(' Archive is the ZIP file to list');
1001
+ console.log(' File patterns specify which files to list (default: all files)');
1002
+ console.log('');
1003
+ console.log('Options and Values:');
1004
+ console.log(' For short options that take values, use -ovalue or -o value or -o=value');
1005
+ console.log(' For long option values, use either --longoption=value or --longoption value');
1006
+ console.log(' For example:');
1007
+ console.log(' neolist --sort=size --exclude "*.tmp" archive.nzip');
1008
+ console.log('');
1009
+ console.log('File Patterns:');
1010
+ console.log(' NeoList supports the following patterns:');
1011
+ console.log(' ? matches any single character');
1012
+ console.log(' * matches any number of characters, including zero');
1013
+ console.log(' [list] matches char in list (regex), can do range [a-f], all but [!bf]');
1014
+ console.log(' Examples:');
1015
+ console.log(' neolist archive.nzip "*.txt" # List only .txt files');
1016
+ console.log(' neolist archive.nzip "test[0-9].*" # List test0.txt, test1.txt, etc.');
1017
+ console.log('');
1018
+ console.log('Include and Exclude:');
1019
+ console.log(' -i pattern pattern ... include only files that match a pattern');
1020
+ console.log(' -x pattern pattern ... exclude files that match a pattern');
1021
+ console.log(' Patterns are paths with optional wildcards and match paths as stored in');
1022
+ console.log(' archive. Exclude and include lists end at next option or end of line.');
1023
+ console.log('');
1024
+ console.log('Sorting Options:');
1025
+ console.log(' --sort name sort by file name (alphabetical)');
1026
+ console.log(' --sort size sort by file size (largest first)');
1027
+ console.log(' --sort date sort by modification date (newest first)');
1028
+ console.log(' --sort method sort by compression method');
1029
+ console.log(' --sort ratio sort by compression ratio');
1030
+ console.log('');
1031
+ console.log('Output Formats:');
1032
+ console.log(' -s short listing (file names only)');
1033
+ console.log(' -u unix-style listing (like ls -l)');
1034
+ console.log(' -v verbose listing with details');
1035
+ console.log(' --json JSON output format');
1036
+ console.log(' --csv CSV output format');
1037
+ console.log('');
1038
+ console.log('Display Options:');
1039
+ console.log(' --no-comments suppress archive and file comments');
1040
+ console.log(' --no-timestamps suppress timestamp information');
1041
+ console.log(' --no-permissions suppress permission information');
1042
+ console.log(' --no-compression suppress compression details');
1043
+ console.log('');
1044
+ console.log('Blockchain Features:');
1045
+ console.log(' --skip-blockchain disable blockchain verification');
1046
+ console.log(' --network <net> specify blockchain network (base-sepolia, ethereum, etc.)');
1047
+ console.log(' --wallet <addr> specify wallet address for verification');
1048
+ console.log(' --ots-verify verify OTS (OpenTimestamps) signatures');
1049
+ console.log(' --ots-skip skip OTS verification');
1050
+ console.log('');
1051
+ console.log('Filtering and Search:');
1052
+ console.log(' --grep <pattern> search for files matching pattern in names');
1053
+ console.log(' --grep-i <pattern> case-insensitive search');
1054
+ console.log(' --size-min <size> show files larger than size');
1055
+ console.log(' --size-max <size> show files smaller than size');
1056
+ console.log(' --date-from <date> show files modified after date');
1057
+ console.log(' --date-to <date> show files modified before date');
1058
+ console.log('');
1059
+ console.log('Progress and Verbosity:');
1060
+ console.log(' -v verbose operation');
1061
+ console.log(' -q quiet operation');
1062
+ console.log(' --debug show debug information');
1063
+ console.log(' --progress show progress bar for large archives');
1064
+ console.log('');
1065
+ console.log('More option highlights:');
1066
+ console.log(' --show-files show files to be listed and exit');
1067
+ console.log(' --count-only show only file count');
1068
+ console.log(' --size-only show only total size');
1069
+ console.log(' --compression-stats show compression statistics');
1070
+ console.log('');
1071
+ console.log('For more information, visit: https://github.com/NeoWareInc/neozip-cli');
1072
+ console.log('For detailed API documentation, see: https://docs.neoware.io/neozip');
1073
+ }
1074
+ /**
1075
+ * Main execution function
1076
+ */
1077
+ async function main() {
1078
+ // Declare variables outside try block for cleanup in catch
1079
+ let zip = null;
1080
+ let options = null;
1081
+ try {
1082
+ // Parse command line arguments
1083
+ const args = process.argv.slice(2);
1084
+ const parsed = parseArgs(args);
1085
+ const { archive } = parsed;
1086
+ options = parsed.options;
1087
+ if (options.debug) {
1088
+ logDebug('Debug mode enabled', options);
1089
+ logDebug(`Command line arguments: ${process.argv.join(' ')}`, options);
1090
+ logDebug(`Working directory: ${process.cwd()}`, options);
1091
+ logDebug(`Node.js version: ${process.version}`, options);
1092
+ logDebug(`Platform: ${process.platform} ${process.arch}`, options);
1093
+ logDebug(`Archive file: ${archive}`, options);
1094
+ log('', options);
1095
+ }
1096
+ // Read archive entries
1097
+ const result = await readArchiveEntries(archive, options.debug, options.quiet || options.opt1 || options.opt2 || options.optT, options.inMemory);
1098
+ const { entries: allEntries } = result;
1099
+ zip = result.zip;
1100
+ if (options.debug) {
1101
+ logDebug(`Archive loaded: ${allEntries.length} entries found`, options);
1102
+ }
1103
+ // Apply include/exclude filtering
1104
+ const filteredEntries = filterEntries(allEntries, options);
1105
+ if (options.debug) {
1106
+ logDebug(`After filtering: ${filteredEntries.length} entries`, options);
1107
+ }
1108
+ // Apply sorting
1109
+ const entries = sortEntries(filteredEntries, options.sortBy);
1110
+ if (options.json) {
1111
+ outputJson(entries, archive, zip, options);
1112
+ }
1113
+ else if (options.opt1) {
1114
+ outputFilenamesOnly(entries, archive, zip, options);
1115
+ }
1116
+ else if (options.opt2) {
1117
+ outputFilenamesWithHeader(entries, archive, zip, options);
1118
+ }
1119
+ else if (options.optT) {
1120
+ outputDecimalTime(entries, archive, zip, options);
1121
+ }
1122
+ else if (options.short) {
1123
+ outputShort(entries, archive, zip, options);
1124
+ }
1125
+ else if (options.verbose) {
1126
+ outputVerbose(entries, archive, zip, options);
1127
+ }
1128
+ else if (options.unix) {
1129
+ outputUnix(entries, archive, zip, options);
1130
+ }
1131
+ else if (options.basic) {
1132
+ outputTable(entries, archive, false, zip, options);
1133
+ }
1134
+ else {
1135
+ // Default is now the unzip -v format (verbose table)
1136
+ outputTable(entries, archive, true, zip, options);
1137
+ }
1138
+ // Don't show success message for filenames-only modes
1139
+ if (!options.opt1 && !options.opt2 && !options.optT) {
1140
+ log('✓ NeoList completed successfully', options);
1141
+ }
1142
+ // Close file handle if file-based ZIP was loaded
1143
+ if (zip && options && !options.inMemory && isFileBased(zip)) {
1144
+ try {
1145
+ await zip.closeFile();
1146
+ }
1147
+ catch (closeError) {
1148
+ // Ignore cleanup errors - file may already be closed
1149
+ if (options.debug) {
1150
+ logDebug(`Note: Error closing file handle: ${closeError instanceof Error ? closeError.message : String(closeError)}`, options);
1151
+ }
1152
+ }
1153
+ }
1154
+ }
1155
+ catch (error) {
1156
+ // Close file handle if file-based ZIP was loaded (cleanup on error)
1157
+ if (zip && options && !options.inMemory && isFileBased(zip)) {
1158
+ try {
1159
+ await zip.closeFile();
1160
+ }
1161
+ catch (closeError) {
1162
+ // Ignore cleanup errors
1163
+ }
1164
+ }
1165
+ logError(`Error: ${error instanceof Error ? error.message : String(error)}`);
1166
+ if (process.argv.includes('--debug')) {
1167
+ logError(error instanceof Error ? (error.stack || String(error)) : String(error));
1168
+ }
1169
+ // Determine appropriate exit code based on error type
1170
+ const errorMessage = error instanceof Error ? error.message : String(error);
1171
+ if (errorMessage.includes('ENOENT') || errorMessage.includes('not found')) {
1172
+ (0, exit_codes_1.exitZipinfo)(exit_codes_1.ZIPINFO_EXIT_CODES.FILES_NOT_FOUND, `Error: ${errorMessage}`);
1173
+ }
1174
+ else if (errorMessage.includes('password') || errorMessage.includes('encrypted')) {
1175
+ (0, exit_codes_1.exitZipinfo)(exit_codes_1.ZIPINFO_EXIT_CODES.PASSWORD_PROTECTED, `Error: ${errorMessage}`);
1176
+ }
1177
+ else if (errorMessage.includes('ZIP format') || errorMessage.includes('corrupt')) {
1178
+ (0, exit_codes_1.exitZipinfo)(exit_codes_1.ZIPINFO_EXIT_CODES.SEVERE_ZIP_ERROR, `Error: ${errorMessage}`);
1179
+ }
1180
+ else if (errorMessage.includes('memory') || errorMessage.includes('allocation')) {
1181
+ (0, exit_codes_1.exitZipinfo)(exit_codes_1.ZIPINFO_EXIT_CODES.MEMORY_ERROR, `Error: ${errorMessage}`);
1182
+ }
1183
+ else {
1184
+ (0, exit_codes_1.exitZipinfo)(exit_codes_1.ZIPINFO_EXIT_CODES.ZIP_FORMAT_ERROR, `Error: ${errorMessage}`);
1185
+ }
1186
+ }
1187
+ }
1188
+ // Always run main when this module is loaded
1189
+ // This allows it to work both when called directly and when imported by index.js
1190
+ main().catch(err => {
1191
+ logError(`Fatal error: ${err}`);
1192
+ (0, exit_codes_1.exitZipinfo)(exit_codes_1.ZIPINFO_EXIT_CODES.ZIP_FORMAT_ERROR);
1193
+ });
1194
+ //# sourceMappingURL=neolist.js.map