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.
- package/CHANGELOG.md +72 -0
- package/DOCUMENTATION.md +194 -0
- package/LICENSE +22 -0
- package/README.md +504 -0
- package/WHY_NEOZIP.md +212 -0
- package/bin/neolist +16 -0
- package/bin/neounzip +16 -0
- package/bin/neozip +15 -0
- package/dist/neozipkit-bundles/blockchain.js +13091 -0
- package/dist/neozipkit-bundles/browser.js +5733 -0
- package/dist/neozipkit-bundles/core.js +3766 -0
- package/dist/neozipkit-bundles/server.js +14996 -0
- package/dist/neozipkit-wrappers/blockchain/core/contracts.js +16 -0
- package/dist/neozipkit-wrappers/blockchain/index.js +2 -0
- package/dist/neozipkit-wrappers/core/ZipDecompress.js +2 -0
- package/dist/neozipkit-wrappers/core/components/HashCalculator.js +2 -0
- package/dist/neozipkit-wrappers/core/components/Logger.js +2 -0
- package/dist/neozipkit-wrappers/core/constants/Errors.js +2 -0
- package/dist/neozipkit-wrappers/core/constants/Headers.js +2 -0
- package/dist/neozipkit-wrappers/core/encryption/ZipCrypto.js +7 -0
- package/dist/neozipkit-wrappers/core/index.js +3 -0
- package/dist/neozipkit-wrappers/index.js +13 -0
- package/dist/neozipkit-wrappers/server/index.js +2 -0
- package/dist/src/config/ConfigSetup.js +455 -0
- package/dist/src/config/ConfigStore.js +373 -0
- package/dist/src/config/ConfigWizard.js +453 -0
- package/dist/src/config/WalletConfig.js +372 -0
- package/dist/src/exit-codes.js +210 -0
- package/dist/src/index.js +141 -0
- package/dist/src/neolist.js +1194 -0
- package/dist/src/neounzip.js +2177 -0
- package/dist/src/neozip/CommentManager.js +240 -0
- package/dist/src/neozip/blockchain.js +383 -0
- package/dist/src/neozip/createZip.js +2273 -0
- package/dist/src/neozip/file-operations.js +920 -0
- package/dist/src/neozip/types.js +6 -0
- package/dist/src/neozip/user-interaction.js +256 -0
- package/dist/src/neozip/utils.js +96 -0
- package/dist/src/neozip.js +785 -0
- package/dist/src/server/CommentManager.js +240 -0
- package/dist/src/version.js +59 -0
- package/env.example +101 -0
- 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
|