dir-archiver 2.2.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/core.js ADDED
@@ -0,0 +1,549 @@
1
+ import { createReadStream, createWriteStream, existsSync, promises as fsPromises, statSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { Readable, Writable } from 'node:stream';
4
+ import { DirArchiverError } from './errors.js';
5
+ import { loadRuntimeBindings } from './runtime/index.js';
6
+ const DIRECTORY_TO_SINGLE_FILE_CODEC = {
7
+ gz: 'tar.gz',
8
+ bz2: 'tar.bz2',
9
+ xz: 'tar.xz',
10
+ zst: 'tar.zst',
11
+ br: 'tar.br'
12
+ };
13
+ const writeUnsupportedFormats = new Set(['tar.bz2', 'bz2', 'tar.xz', 'xz']);
14
+ /**
15
+ * Opens an archive input with bytefold runtime bindings.
16
+ */
17
+ export const open = async (input, options = {}) => {
18
+ const runtime = await loadRuntimeBindings();
19
+ return runtime.openArchive(input, toArchiveOpenOptions(options));
20
+ };
21
+ /**
22
+ * Detects archive format and exposes bytefold detection metadata.
23
+ */
24
+ export const detect = async (input, options = {}) => {
25
+ const reader = await open(input, options);
26
+ try {
27
+ return {
28
+ format: reader.format,
29
+ detection: reader.detection
30
+ };
31
+ }
32
+ finally {
33
+ await disposeArchiveReader(reader);
34
+ }
35
+ };
36
+ /**
37
+ * Lists archive entries without extracting to disk.
38
+ */
39
+ export const list = async (input, options = {}) => {
40
+ const reader = await open(input, options);
41
+ try {
42
+ const entries = [];
43
+ for await (const entry of reader.entries()) {
44
+ entries.push({
45
+ format: entry.format,
46
+ name: entry.name,
47
+ size: entry.size.toString(),
48
+ isDirectory: entry.isDirectory,
49
+ isSymlink: entry.isSymlink,
50
+ ...(typeof entry.linkName === 'string' ? { linkName: entry.linkName } : {})
51
+ });
52
+ }
53
+ return {
54
+ format: reader.format,
55
+ detection: reader.detection,
56
+ entries
57
+ };
58
+ }
59
+ finally {
60
+ await disposeArchiveReader(reader);
61
+ }
62
+ };
63
+ /**
64
+ * Runs bytefold audit checks for the selected safety profile.
65
+ */
66
+ export const audit = async (input, options = {}) => {
67
+ const reader = await open(input, options);
68
+ try {
69
+ return await reader.audit(toAuditOptions(options));
70
+ }
71
+ finally {
72
+ await disposeArchiveReader(reader);
73
+ }
74
+ };
75
+ /**
76
+ * Writes a normalized deterministic archive when supported by the format.
77
+ */
78
+ export const normalize = async (input, destination, options = {}) => {
79
+ const reader = await open(input, options);
80
+ try {
81
+ if (typeof reader.normalizeToWritable !== 'function') {
82
+ throw new DirArchiverError('DIRARCHIVER_NORMALIZE_UNSUPPORTED', `Normalize is unavailable for format "${reader.format}".`);
83
+ }
84
+ const destinationPath = path.resolve(destination);
85
+ await ensureParentDirectory(destinationPath);
86
+ const writable = createFileWritable(destinationPath);
87
+ const report = await reader.normalizeToWritable(writable, toNormalizeOptions(options));
88
+ return {
89
+ format: reader.format,
90
+ report
91
+ };
92
+ }
93
+ finally {
94
+ await disposeArchiveReader(reader);
95
+ }
96
+ };
97
+ /**
98
+ * Extracts entries to a destination directory with safety enforcement.
99
+ */
100
+ export const extract = async (input, destination, options = {}) => {
101
+ var _a;
102
+ const reader = await open(input, options);
103
+ try {
104
+ const profile = (_a = options.profile) !== null && _a !== void 0 ? _a : 'strict';
105
+ const issues = [];
106
+ const destinationRoot = path.resolve(destination);
107
+ await fsPromises.mkdir(destinationRoot, { recursive: true });
108
+ if (profile !== 'compat') {
109
+ if (profile === 'agent') {
110
+ try {
111
+ await reader.assertSafe(toAuditOptions({
112
+ ...options,
113
+ profile
114
+ }));
115
+ }
116
+ catch (error) {
117
+ throw new DirArchiverError('DIRARCHIVER_UNSUPPORTED_ENTRY', 'Archive assertSafe failed under agent safety profile.', { cause: error });
118
+ }
119
+ }
120
+ const auditReport = await reader.audit(toAuditOptions({
121
+ ...options,
122
+ profile
123
+ }));
124
+ if (!auditReport.ok) {
125
+ const hasTraversalIssue = auditReport.issues.some((issue) => issue.code.includes('TRAVERSAL')
126
+ || issue.code.includes('ABSOLUTE'));
127
+ throw new DirArchiverError(hasTraversalIssue ? 'DIRARCHIVER_PATH_TRAVERSAL' : 'DIRARCHIVER_UNSUPPORTED_ENTRY', 'Archive audit failed under strict safety profile.', {
128
+ context: {
129
+ issues: auditReport.issues
130
+ }
131
+ });
132
+ }
133
+ issues.push(...auditReport.issues);
134
+ }
135
+ let extractedFiles = 0;
136
+ let extractedDirectories = 0;
137
+ let skippedEntries = 0;
138
+ let totalExtractedBytes = 0;
139
+ for await (const entry of reader.entries()) {
140
+ const destinationPath = resolveEntryDestination(destinationRoot, entry.name);
141
+ if (entry.isDirectory) {
142
+ await fsPromises.mkdir(destinationPath, { recursive: true });
143
+ extractedDirectories += 1;
144
+ continue;
145
+ }
146
+ if (entry.isSymlink) {
147
+ if (options.allowSymlinks !== true) {
148
+ skippedEntries += 1;
149
+ continue;
150
+ }
151
+ if (typeof entry.linkName !== 'string' || entry.linkName.length === 0) {
152
+ throw new DirArchiverError('DIRARCHIVER_UNSUPPORTED_ENTRY', `Symlink "${entry.name}" is missing a link target.`);
153
+ }
154
+ const symlinkTarget = normalizeSymlinkTarget(entry.linkName);
155
+ await ensureParentDirectory(destinationPath);
156
+ await fsPromises.symlink(symlinkTarget, destinationPath);
157
+ continue;
158
+ }
159
+ if (typeof entry.linkName === 'string' && entry.linkName.length > 0) {
160
+ throw new DirArchiverError('DIRARCHIVER_UNSUPPORTED_ENTRY', `Hard link entry "${entry.name}" is not supported by dir-archiver v3.`);
161
+ }
162
+ const stream = await entry.open();
163
+ const bytes = await readAllBytes(stream, options.maxEntryBytes, options.maxTotalExtractedBytes, totalExtractedBytes);
164
+ totalExtractedBytes += bytes.length;
165
+ await ensureParentDirectory(destinationPath);
166
+ await fsPromises.writeFile(destinationPath, bytes);
167
+ extractedFiles += 1;
168
+ }
169
+ return {
170
+ format: reader.format,
171
+ destination: destinationRoot,
172
+ extractedFiles,
173
+ extractedDirectories,
174
+ skippedEntries,
175
+ issues
176
+ };
177
+ }
178
+ finally {
179
+ await disposeArchiveReader(reader);
180
+ }
181
+ };
182
+ /**
183
+ * Writes an archive from a file or directory source.
184
+ */
185
+ export const write = async (source, destination, options = {}) => {
186
+ var _a, _b;
187
+ const runtime = await loadRuntimeBindings();
188
+ const sourcePath = path.resolve(source);
189
+ const destinationPath = path.resolve(destination);
190
+ const requestedFormat = (_b = (_a = options.format) !== null && _a !== void 0 ? _a : inferFormatFromDestination(destinationPath)) !== null && _b !== void 0 ? _b : 'zip';
191
+ const sourceStats = await fsPromises.lstat(sourcePath);
192
+ const sourceIsDirectory = sourceStats.isDirectory();
193
+ const wrappedFormat = sourceIsDirectory
194
+ ? DIRECTORY_TO_SINGLE_FILE_CODEC[requestedFormat]
195
+ : undefined;
196
+ const wrappedDirectoryCodec = typeof wrappedFormat === 'string';
197
+ const outputFormat = wrappedFormat !== null && wrappedFormat !== void 0 ? wrappedFormat : requestedFormat;
198
+ if (writeUnsupportedFormats.has(outputFormat)) {
199
+ throw new DirArchiverError('DIRARCHIVER_UNSUPPORTED_ENTRY', `Write format "${outputFormat}" is unsupported by bytefold writers.`);
200
+ }
201
+ await ensureParentDirectory(destinationPath);
202
+ const writer = runtime.createArchiveWriter(outputFormat, createFileWritable(destinationPath));
203
+ const entries = sourceIsDirectory
204
+ ? await collectDirectoryEntries(sourcePath, options)
205
+ : [{
206
+ sourcePath,
207
+ archivePath: path.basename(sourcePath).replace(/\\/g, '/')
208
+ }];
209
+ for (const entry of entries) {
210
+ const bytes = await fsPromises.readFile(entry.sourcePath);
211
+ await writer.add(entry.archivePath, bytes);
212
+ }
213
+ await writer.close();
214
+ return {
215
+ format: outputFormat,
216
+ source: sourcePath,
217
+ destination: destinationPath,
218
+ entryCount: entries.length,
219
+ wrappedDirectoryCodec
220
+ };
221
+ };
222
+ const collectDirectoryEntries = async (sourcePath, options) => {
223
+ var _a;
224
+ const includeBaseDirectory = options.includeBaseDirectory === true;
225
+ const followSymlinks = options.followSymlinks === true;
226
+ const sourceBaseName = path.basename(sourcePath);
227
+ const excludeMatcher = createExcludeMatcher(sourcePath, (_a = options.exclude) !== null && _a !== void 0 ? _a : []);
228
+ const visitedDirectories = new Set();
229
+ const toVisit = [sourcePath];
230
+ const files = [];
231
+ while (toVisit.length > 0) {
232
+ const nextDirectory = toVisit.pop();
233
+ if (!nextDirectory) {
234
+ continue;
235
+ }
236
+ if (followSymlinks) {
237
+ const real = await fsPromises.realpath(nextDirectory).catch(() => null);
238
+ if (!real || visitedDirectories.has(real)) {
239
+ continue;
240
+ }
241
+ visitedDirectories.add(real);
242
+ }
243
+ const directoryEntries = await fsPromises.readdir(nextDirectory, { withFileTypes: true });
244
+ directoryEntries.sort((left, right) => left.name.localeCompare(right.name));
245
+ for (const entry of directoryEntries) {
246
+ const sourceEntryPath = path.join(nextDirectory, entry.name);
247
+ const relativePath = path.relative(sourcePath, sourceEntryPath);
248
+ if (excludeMatcher.isExcluded(relativePath)) {
249
+ continue;
250
+ }
251
+ if (entry.isDirectory()) {
252
+ toVisit.push(sourceEntryPath);
253
+ continue;
254
+ }
255
+ if (entry.isFile()) {
256
+ files.push({
257
+ sourcePath: sourceEntryPath,
258
+ archivePath: toArchivePath(relativePath, includeBaseDirectory ? sourceBaseName : undefined)
259
+ });
260
+ continue;
261
+ }
262
+ if (!entry.isSymbolicLink() || !followSymlinks) {
263
+ continue;
264
+ }
265
+ const stats = await fsPromises.stat(sourceEntryPath).catch(() => null);
266
+ if (!stats) {
267
+ continue;
268
+ }
269
+ if (stats.isDirectory()) {
270
+ toVisit.push(sourceEntryPath);
271
+ }
272
+ else if (stats.isFile()) {
273
+ files.push({
274
+ sourcePath: sourceEntryPath,
275
+ archivePath: toArchivePath(relativePath, includeBaseDirectory ? sourceBaseName : undefined)
276
+ });
277
+ }
278
+ }
279
+ }
280
+ files.sort((left, right) => left.archivePath.localeCompare(right.archivePath));
281
+ return files;
282
+ };
283
+ const toArchiveOpenOptions = (options) => {
284
+ const archiveOptions = {};
285
+ if (options.format !== undefined) {
286
+ archiveOptions.format = options.format;
287
+ }
288
+ if (options.profile !== undefined) {
289
+ archiveOptions.profile = options.profile;
290
+ }
291
+ if (options.isStrict !== undefined) {
292
+ archiveOptions.isStrict = options.isStrict;
293
+ }
294
+ if (options.limits !== undefined) {
295
+ archiveOptions.limits = options.limits;
296
+ }
297
+ if (options.signal !== undefined) {
298
+ archiveOptions.signal = options.signal;
299
+ }
300
+ if (options.password !== undefined) {
301
+ archiveOptions.password = options.password;
302
+ }
303
+ if (options.filename !== undefined) {
304
+ archiveOptions.filename = options.filename;
305
+ }
306
+ return archiveOptions;
307
+ };
308
+ const toAuditOptions = (options) => {
309
+ const auditOptions = {};
310
+ if (options.profile !== undefined) {
311
+ auditOptions.profile = options.profile;
312
+ }
313
+ if (options.isStrict !== undefined) {
314
+ auditOptions.isStrict = options.isStrict;
315
+ }
316
+ if (options.limits !== undefined) {
317
+ auditOptions.limits = options.limits;
318
+ }
319
+ if (options.signal !== undefined) {
320
+ auditOptions.signal = options.signal;
321
+ }
322
+ return auditOptions;
323
+ };
324
+ const toNormalizeOptions = (options) => {
325
+ var _a;
326
+ const normalizeOptions = {
327
+ isDeterministic: (_a = options.deterministic) !== null && _a !== void 0 ? _a : true
328
+ };
329
+ if (options.limits !== undefined) {
330
+ normalizeOptions.limits = options.limits;
331
+ }
332
+ if (options.signal !== undefined) {
333
+ normalizeOptions.signal = options.signal;
334
+ }
335
+ return normalizeOptions;
336
+ };
337
+ const createExcludeMatcher = (sourcePath, excludes) => {
338
+ const excludedPaths = new Set();
339
+ const excludedNames = new Set();
340
+ const caseInsensitive = process.platform === 'win32';
341
+ for (const rawExclude of excludes) {
342
+ const normalizedExclude = normalizeExcludeInput(rawExclude, sourcePath);
343
+ if (!normalizedExclude) {
344
+ continue;
345
+ }
346
+ const hasSeparator = normalizedExclude.includes('/') || normalizedExclude.includes('\\') || normalizedExclude.includes(path.sep);
347
+ const normalizedValue = normalizeCase(normalizedExclude, caseInsensitive);
348
+ excludedPaths.add(normalizedValue);
349
+ if (!hasSeparator) {
350
+ excludedNames.add(normalizedValue);
351
+ }
352
+ }
353
+ return {
354
+ isExcluded(relativePath) {
355
+ const normalizedRelativePath = normalizeCase(path.normalize(relativePath), caseInsensitive);
356
+ if (excludedPaths.has(normalizedRelativePath)) {
357
+ return true;
358
+ }
359
+ const baseName = normalizeCase(path.basename(normalizedRelativePath), caseInsensitive);
360
+ return excludedNames.has(baseName);
361
+ }
362
+ };
363
+ };
364
+ const normalizeExcludeInput = (excludeRaw, sourcePath) => {
365
+ if (typeof excludeRaw !== 'string') {
366
+ return undefined;
367
+ }
368
+ const trimmed = excludeRaw.trim();
369
+ if (trimmed.length === 0) {
370
+ return undefined;
371
+ }
372
+ let normalized = path.normalize(trimmed.replace(/\\/g, path.sep));
373
+ if (normalized === '.' || normalized === path.sep) {
374
+ return undefined;
375
+ }
376
+ if (path.isAbsolute(normalized)) {
377
+ const relativeCandidate = path.relative(sourcePath, normalized);
378
+ const isInsideSource = relativeCandidate.length > 0
379
+ && !relativeCandidate.startsWith('..')
380
+ && !path.isAbsolute(relativeCandidate);
381
+ if (isInsideSource) {
382
+ normalized = path.normalize(relativeCandidate);
383
+ }
384
+ }
385
+ normalized = trimTrailingPathSeparators(normalized);
386
+ if (normalized.length === 0 || normalized === '.') {
387
+ return undefined;
388
+ }
389
+ return normalized;
390
+ };
391
+ const normalizeCase = (value, caseInsensitive) => {
392
+ return caseInsensitive ? value.toLowerCase() : value;
393
+ };
394
+ const trimTrailingPathSeparators = (value) => {
395
+ let end = value.length;
396
+ while (end > 0) {
397
+ const code = value.charCodeAt(end - 1);
398
+ if (code === 47 || code === 92) {
399
+ end -= 1;
400
+ continue;
401
+ }
402
+ break;
403
+ }
404
+ return value.slice(0, end);
405
+ };
406
+ const toArchivePath = (relativePath, baseDirectory) => {
407
+ const normalized = relativePath.replace(/\\/g, '/');
408
+ if (baseDirectory) {
409
+ return path.posix.join(baseDirectory, normalized);
410
+ }
411
+ return normalized;
412
+ };
413
+ const normalizeArchiveEntryName = (entryName) => {
414
+ const normalizedSlashes = entryName.replace(/\\/g, '/');
415
+ if (normalizedSlashes.length === 0) {
416
+ throw new DirArchiverError('DIRARCHIVER_PATH_TRAVERSAL', 'Archive entry name is empty.');
417
+ }
418
+ if (normalizedSlashes.startsWith('/') || /^[a-zA-Z]:\//u.test(normalizedSlashes)) {
419
+ throw new DirArchiverError('DIRARCHIVER_PATH_TRAVERSAL', `Archive entry "${entryName}" is absolute and cannot be extracted safely.`);
420
+ }
421
+ const parts = normalizedSlashes.split('/').filter((part) => part.length > 0 && part !== '.');
422
+ if (parts.some((part) => part === '..')) {
423
+ throw new DirArchiverError('DIRARCHIVER_PATH_TRAVERSAL', `Archive entry "${entryName}" escapes extraction root.`);
424
+ }
425
+ return parts.join('/');
426
+ };
427
+ const resolveEntryDestination = (destinationRoot, entryName) => {
428
+ const safeRelative = normalizeArchiveEntryName(entryName);
429
+ const resolved = path.resolve(destinationRoot, safeRelative);
430
+ if (resolved !== destinationRoot && !resolved.startsWith(`${destinationRoot}${path.sep}`)) {
431
+ throw new DirArchiverError('DIRARCHIVER_PATH_TRAVERSAL', `Archive entry "${entryName}" resolves outside destination root.`);
432
+ }
433
+ return resolved;
434
+ };
435
+ const normalizeSymlinkTarget = (linkName) => {
436
+ const normalized = linkName.replace(/\\/g, '/');
437
+ if (normalized.startsWith('/') || /^[a-zA-Z]:\//u.test(normalized)) {
438
+ throw new DirArchiverError('DIRARCHIVER_PATH_TRAVERSAL', `Symlink target "${linkName}" is absolute and not allowed.`);
439
+ }
440
+ const parts = normalized.split('/').filter((part) => part.length > 0 && part !== '.');
441
+ if (parts.some((part) => part === '..')) {
442
+ throw new DirArchiverError('DIRARCHIVER_PATH_TRAVERSAL', `Symlink target "${linkName}" escapes extraction root.`);
443
+ }
444
+ return parts.join('/');
445
+ };
446
+ const readAllBytes = async (stream, maxEntryBytes, maxTotalBytes, currentTotalBytes = 0) => {
447
+ const reader = stream.getReader();
448
+ const chunks = [];
449
+ let entryTotal = 0;
450
+ try {
451
+ for (;;) {
452
+ const chunk = await reader.read();
453
+ if (chunk.done) {
454
+ break;
455
+ }
456
+ if (!(chunk.value instanceof Uint8Array)) {
457
+ continue;
458
+ }
459
+ entryTotal += chunk.value.length;
460
+ if (typeof maxEntryBytes === 'number' && entryTotal > maxEntryBytes) {
461
+ throw new DirArchiverError('DIRARCHIVER_RESOURCE_LIMIT', `Entry exceeds maxEntryBytes (${maxEntryBytes}).`, {
462
+ context: {
463
+ maxEntryBytes,
464
+ actualEntryBytes: entryTotal
465
+ }
466
+ });
467
+ }
468
+ const projectedTotal = currentTotalBytes + entryTotal;
469
+ if (typeof maxTotalBytes === 'number' && projectedTotal > maxTotalBytes) {
470
+ throw new DirArchiverError('DIRARCHIVER_RESOURCE_LIMIT', `Extraction exceeds maxTotalExtractedBytes (${maxTotalBytes}).`, {
471
+ context: {
472
+ maxTotalExtractedBytes: maxTotalBytes,
473
+ projectedTotalBytes: projectedTotal
474
+ }
475
+ });
476
+ }
477
+ chunks.push(chunk.value);
478
+ }
479
+ }
480
+ finally {
481
+ reader.releaseLock();
482
+ }
483
+ const output = new Uint8Array(entryTotal);
484
+ let offset = 0;
485
+ for (const chunk of chunks) {
486
+ output.set(chunk, offset);
487
+ offset += chunk.length;
488
+ }
489
+ return output;
490
+ };
491
+ const ensureParentDirectory = async (targetPath) => {
492
+ await fsPromises.mkdir(path.dirname(targetPath), { recursive: true });
493
+ };
494
+ const createFileWritable = (targetPath) => {
495
+ const writable = createWriteStream(targetPath);
496
+ return Writable.toWeb(writable);
497
+ };
498
+ const inferFormatFromDestination = (destinationPath) => {
499
+ const lower = destinationPath.toLowerCase();
500
+ if (lower.endsWith('.tar.gz') || lower.endsWith('.tgz'))
501
+ return 'tar.gz';
502
+ if (lower.endsWith('.tar.bz2'))
503
+ return 'tar.bz2';
504
+ if (lower.endsWith('.tar.xz'))
505
+ return 'tar.xz';
506
+ if (lower.endsWith('.tar.zst'))
507
+ return 'tar.zst';
508
+ if (lower.endsWith('.tar.br'))
509
+ return 'tar.br';
510
+ if (lower.endsWith('.tar'))
511
+ return 'tar';
512
+ if (lower.endsWith('.zip'))
513
+ return 'zip';
514
+ if (lower.endsWith('.gz'))
515
+ return 'gz';
516
+ if (lower.endsWith('.bz2'))
517
+ return 'bz2';
518
+ if (lower.endsWith('.xz'))
519
+ return 'xz';
520
+ if (lower.endsWith('.zst'))
521
+ return 'zst';
522
+ if (lower.endsWith('.br'))
523
+ return 'br';
524
+ return undefined;
525
+ };
526
+ export const copyStreamToFile = async (source, destination) => {
527
+ await ensureParentDirectory(destination);
528
+ const nodeReadable = createReadStream(source);
529
+ const webReadable = Readable.toWeb(nodeReadable);
530
+ const bytes = await readAllBytes(webReadable);
531
+ await fsPromises.writeFile(destination, bytes);
532
+ };
533
+ export const pathExists = (value) => existsSync(value);
534
+ export const fileSize = (value) => statSync(value).size;
535
+ const disposeArchiveReader = async (reader) => {
536
+ const withAsyncDispose = reader;
537
+ const asyncDispose = withAsyncDispose[Symbol.asyncDispose];
538
+ if (typeof asyncDispose === 'function') {
539
+ await asyncDispose.call(withAsyncDispose);
540
+ return;
541
+ }
542
+ if (typeof withAsyncDispose.dispose === 'function') {
543
+ await withAsyncDispose.dispose();
544
+ return;
545
+ }
546
+ if (typeof withAsyncDispose.close === 'function') {
547
+ await withAsyncDispose.close();
548
+ }
549
+ };
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Stable dir-archiver error codes.
3
+ */
4
+ export type DirArchiverErrorCode = 'DIRARCHIVER_INVALID_SOURCE' | 'DIRARCHIVER_INVALID_DESTINATION' | 'DIRARCHIVER_PATH_TRAVERSAL' | 'DIRARCHIVER_UNSUPPORTED_ENTRY' | 'DIRARCHIVER_RESOURCE_LIMIT' | 'DIRARCHIVER_RUNTIME_UNSUPPORTED' | 'DIRARCHIVER_NORMALIZE_UNSUPPORTED' | 'DIRARCHIVER_USAGE';
5
+ /**
6
+ * Structured error contract for dir-archiver v3.
7
+ */
8
+ export declare class DirArchiverError extends Error {
9
+ readonly code: DirArchiverErrorCode;
10
+ readonly hint: string | undefined;
11
+ readonly context: Record<string, unknown> | undefined;
12
+ constructor(code: DirArchiverErrorCode, message: string, options?: {
13
+ hint?: string | undefined;
14
+ context?: Record<string, unknown> | undefined;
15
+ cause?: unknown;
16
+ });
17
+ toJSON(): {
18
+ schemaVersion: '1';
19
+ name: 'DirArchiverError';
20
+ code: DirArchiverErrorCode;
21
+ message: string;
22
+ hint?: string;
23
+ context?: Record<string, unknown>;
24
+ };
25
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Structured error contract for dir-archiver v3.
3
+ */
4
+ export class DirArchiverError extends Error {
5
+ constructor(code, message, options = {}) {
6
+ super(message);
7
+ this.name = 'DirArchiverError';
8
+ this.code = code;
9
+ this.hint = options.hint;
10
+ this.context = options.context;
11
+ if (options.cause !== undefined) {
12
+ this.cause = options.cause;
13
+ }
14
+ }
15
+ toJSON() {
16
+ return {
17
+ schemaVersion: '1',
18
+ name: 'DirArchiverError',
19
+ code: this.code,
20
+ message: this.message,
21
+ ...(this.hint ? { hint: this.hint } : {}),
22
+ ...(this.context ? { context: this.context } : {})
23
+ };
24
+ }
25
+ }
package/dist/index.d.ts CHANGED
@@ -1,28 +1,19 @@
1
- declare class DirArchiver {
2
- private excludedPaths;
3
- private excludedNames;
4
- private caseInsensitiveExcludes;
5
- private directoryPath;
6
- private zipPath;
7
- private includeBaseDirectory;
8
- private followSymlinks;
9
- private baseDirectory;
10
- private visitedDirectories;
11
- /**
12
- * The constructor.
13
- * @param directoryPath - the path of the folder to archive.
14
- * @param zipPath - The path of the zip file to create.
15
- * @param includeBaseDirectory - Includes a base directory at the root of the archive. For example, if the root folder of your project is named "your-project", setting includeBaseDirectory to true will create an archive that includes this base directory. If this option is set to false the archive created will unzip its content to the current directory.
16
- * @param excludes - The name of the files and foldes to exclude.
17
- */
18
- constructor(directoryPath: string, zipPath: string, includeBaseDirectory?: boolean, excludes?: string[], followSymlinks?: boolean);
19
- /**
20
- * Recursively traverse the directory tree and append the files to the archive.
21
- * @param directoryPath - The path of the directory being looped through.
22
- */
23
- private traverseDirectoryTree;
24
- private prettyBytes;
25
- private normalizeExcludeValue;
26
- createZip(): Promise<string>;
27
- }
28
- export = DirArchiver;
1
+ /**
2
+ * dir-archiver v3 API surface.
3
+ *
4
+ * v3 is a bytefold-backed orchestration layer that supports Node.js, Deno, and Bun.
5
+ */
6
+ import { audit, detect, extract, list, normalize, open, write } from './core.js';
7
+ export { audit, detect, extract, list, normalize, open, write };
8
+ export { DirArchiverError } from './errors.js';
9
+ export type { ArchiveFormat, ArchiveLimits, ArchiveProfile, CliUsageError, DetectResult, DirArchiverInput, ExtractOptions, ExtractResult, ListEntry, ListResult, NormalizeOptions, NormalizeResult, OpenOptions, SupportedCommandMap, WriteOptions, WriteResult } from './types.js';
10
+ declare const api: {
11
+ open: (input: import("./types.js").DirArchiverInput, options?: import("./types.js").OpenOptions) => Promise<import("@ismail-elkorchi/bytefold").ArchiveReader>;
12
+ detect: (input: import("./types.js").DirArchiverInput, options?: import("./types.js").OpenOptions) => Promise<import("./types.js").DetectResult>;
13
+ list: (input: import("./types.js").DirArchiverInput, options?: import("./types.js").OpenOptions) => Promise<import("./types.js").ListResult>;
14
+ audit: (input: import("./types.js").DirArchiverInput, options?: import("./types.js").OpenOptions) => Promise<import("@ismail-elkorchi/bytefold").ArchiveAuditReport>;
15
+ normalize: (input: import("./types.js").DirArchiverInput, destination: string, options?: import("./types.js").NormalizeOptions) => Promise<import("./types.js").NormalizeResult>;
16
+ extract: (input: import("./types.js").DirArchiverInput, destination: string, options?: import("./types.js").ExtractOptions) => Promise<import("./types.js").ExtractResult>;
17
+ write: (source: string, destination: string, options?: import("./types.js").WriteOptions) => Promise<import("./types.js").WriteResult>;
18
+ };
19
+ export default api;