@alienkarma/archiver 7.0.1-fork.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/core.js ADDED
@@ -0,0 +1,851 @@
1
+ import { createReadStream, lstat, readlinkSync, Stats } from "fs";
2
+ import { isStream } from "is-stream";
3
+ import readdirGlob from "readdir-glob";
4
+ import { Readable } from "lazystream";
5
+ import { queue } from "async";
6
+ import {
7
+ dirname,
8
+ relative as relativePath,
9
+ resolve as resolvePath,
10
+ } from "path";
11
+ import { ArchiverError } from "./error.js";
12
+ import { Transform } from "readable-stream";
13
+ import {
14
+ dateify,
15
+ normalizeInputSource,
16
+ sanitizePath,
17
+ trailingSlashIt,
18
+ } from "./utils.js";
19
+ const { ReaddirGlob } = readdirGlob;
20
+ const win32 = process.platform === "win32";
21
+
22
+ export default class Archiver extends Transform {
23
+ _supportsDirectory = false;
24
+ _supportsSymlink = false;
25
+
26
+ /**
27
+ * @constructor
28
+ * @param {String} format The archive format to use.
29
+ * @param {(CoreOptions|TransformOptions)} options See also {@link ZipOptions} and {@link TarOptions}.
30
+ */
31
+ constructor(options) {
32
+ options = {
33
+ highWaterMark: 1024 * 1024,
34
+ statConcurrency: 4,
35
+ ...options,
36
+ };
37
+ super(options);
38
+ this.options = options;
39
+ this._format = false;
40
+ this._module = false;
41
+ this._pending = 0;
42
+ this._pointer = 0;
43
+ this._entriesCount = 0;
44
+ this._entriesProcessedCount = 0;
45
+ this._fsEntriesTotalBytes = 0;
46
+ this._fsEntriesProcessedBytes = 0;
47
+ this._queue = queue(this._onQueueTask.bind(this), 1);
48
+ this._queue.drain(this._onQueueDrain.bind(this));
49
+ this._statQueue = queue(
50
+ this._onStatQueueTask.bind(this),
51
+ options.statConcurrency,
52
+ );
53
+ this._statQueue.drain(this._onQueueDrain.bind(this));
54
+ this._state = {
55
+ aborted: false,
56
+ finalize: false,
57
+ finalizing: false,
58
+ finalized: false,
59
+ modulePiped: false,
60
+ };
61
+ this._streams = [];
62
+ }
63
+
64
+ /**
65
+ * Internal logic for `abort`.
66
+ *
67
+ * @private
68
+ * @return void
69
+ */
70
+ _abort() {
71
+ this._state.aborted = true;
72
+ this._queue.kill();
73
+ this._statQueue.kill();
74
+ if (this._queue.idle()) {
75
+ this._shutdown();
76
+ }
77
+ }
78
+ /**
79
+ * Internal helper for appending files.
80
+ *
81
+ * @private
82
+ * @param {String} filepath The source filepath.
83
+ * @param {EntryData} data The entry data.
84
+ * @return void
85
+ */
86
+ _append(filepath, data) {
87
+ data = data || {};
88
+ let task = {
89
+ source: null,
90
+ filepath: filepath,
91
+ };
92
+ if (!data.name) {
93
+ data.name = filepath;
94
+ }
95
+ data.sourcePath = filepath;
96
+ task.data = data;
97
+ this._entriesCount++;
98
+ if (data.stats && data.stats instanceof Stats) {
99
+ task = this._updateQueueTaskWithStats(task, data.stats);
100
+ if (task) {
101
+ if (data.stats.size) {
102
+ this._fsEntriesTotalBytes += data.stats.size;
103
+ }
104
+ this._queue.push(task);
105
+ }
106
+ } else {
107
+ this._statQueue.push(task);
108
+ }
109
+ }
110
+ /**
111
+ * Internal logic for `finalize`.
112
+ *
113
+ * @private
114
+ * @return void
115
+ */
116
+ _finalize() {
117
+ if (
118
+ this._state.finalizing ||
119
+ this._state.finalized ||
120
+ this._state.aborted
121
+ ) {
122
+ return;
123
+ }
124
+ this._state.finalizing = true;
125
+ this._moduleFinalize();
126
+ this._state.finalizing = false;
127
+ this._state.finalized = true;
128
+ }
129
+ /**
130
+ * Checks the various state variables to determine if we can `finalize`.
131
+ *
132
+ * @private
133
+ * @return {Boolean}
134
+ */
135
+ _maybeFinalize() {
136
+ if (
137
+ this._state.finalizing ||
138
+ this._state.finalized ||
139
+ this._state.aborted
140
+ ) {
141
+ return false;
142
+ }
143
+ if (
144
+ this._state.finalize &&
145
+ this._pending === 0 &&
146
+ this._queue.idle() &&
147
+ this._statQueue.idle()
148
+ ) {
149
+ this._finalize();
150
+ return true;
151
+ }
152
+ return false;
153
+ }
154
+ /**
155
+ * Appends an entry to the module.
156
+ *
157
+ * @private
158
+ * @fires Archiver#entry
159
+ * @param {(Buffer|Stream)} source
160
+ * @param {EntryData} data
161
+ * @param {Function} callback
162
+ * @return void
163
+ */
164
+ _moduleAppend(source, data, callback) {
165
+ if (this._state.aborted) {
166
+ callback();
167
+ return;
168
+ }
169
+ this._module.append(
170
+ source,
171
+ data,
172
+ function (err) {
173
+ this._task = null;
174
+ if (this._state.aborted) {
175
+ this._shutdown();
176
+ return;
177
+ }
178
+ if (err) {
179
+ this.emit("error", err);
180
+ setImmediate(callback);
181
+ return;
182
+ }
183
+ /**
184
+ * Fires when the entry's input has been processed and appended to the archive.
185
+ *
186
+ * @event Archiver#entry
187
+ * @type {EntryData}
188
+ */
189
+ this.emit("entry", data);
190
+ this._entriesProcessedCount++;
191
+ if (data.stats && data.stats.size) {
192
+ this._fsEntriesProcessedBytes += data.stats.size;
193
+ }
194
+ /**
195
+ * @event Archiver#progress
196
+ * @type {ProgressData}
197
+ */
198
+ this.emit("progress", {
199
+ entries: {
200
+ total: this._entriesCount,
201
+ processed: this._entriesProcessedCount,
202
+ },
203
+ fs: {
204
+ totalBytes: this._fsEntriesTotalBytes,
205
+ processedBytes: this._fsEntriesProcessedBytes,
206
+ },
207
+ });
208
+ setImmediate(callback);
209
+ }.bind(this),
210
+ );
211
+ }
212
+ /**
213
+ * Finalizes the module.
214
+ *
215
+ * @private
216
+ * @return void
217
+ */
218
+ _moduleFinalize() {
219
+ if (typeof this._module.finalize === "function") {
220
+ this._module.finalize();
221
+ } else if (typeof this._module.end === "function") {
222
+ this._module.end();
223
+ } else {
224
+ this.emit("error", new ArchiverError("NOENDMETHOD"));
225
+ }
226
+ }
227
+ /**
228
+ * Pipes the module to our internal stream with error bubbling.
229
+ *
230
+ * @private
231
+ * @return void
232
+ */
233
+ _modulePipe() {
234
+ this._module.on("error", this._onModuleError.bind(this));
235
+ this._module.pipe(this);
236
+ this._state.modulePiped = true;
237
+ }
238
+ /**
239
+ * Unpipes the module from our internal stream.
240
+ *
241
+ * @private
242
+ * @return void
243
+ */
244
+ _moduleUnpipe() {
245
+ this._module.unpipe(this);
246
+ this._state.modulePiped = false;
247
+ }
248
+ /**
249
+ * Normalizes entry data with fallbacks for key properties.
250
+ *
251
+ * @private
252
+ * @param {Object} data
253
+ * @param {fs.Stats} stats
254
+ * @return {Object}
255
+ */
256
+ _normalizeEntryData(data, stats) {
257
+ data = {
258
+ type: "file",
259
+ name: null,
260
+ date: null,
261
+ mode: null,
262
+ prefix: null,
263
+ sourcePath: null,
264
+ stats: false,
265
+ ...data,
266
+ };
267
+ if (stats && data.stats === false) {
268
+ data.stats = stats;
269
+ }
270
+ let isDir = data.type === "directory";
271
+ if (data.name) {
272
+ if (typeof data.prefix === "string" && "" !== data.prefix) {
273
+ data.name = data.prefix + "/" + data.name;
274
+ data.prefix = null;
275
+ }
276
+ data.name = sanitizePath(data.name);
277
+ if (data.type !== "symlink" && data.name.slice(-1) === "/") {
278
+ isDir = true;
279
+ data.type = "directory";
280
+ } else if (isDir) {
281
+ data.name += "/";
282
+ }
283
+ }
284
+ // 511 === 0777; 493 === 0755; 438 === 0666; 420 === 0644
285
+ if (typeof data.mode === "number") {
286
+ if (win32) {
287
+ data.mode &= 511;
288
+ } else {
289
+ data.mode &= 4095;
290
+ }
291
+ } else if (data.stats && data.mode === null) {
292
+ if (win32) {
293
+ data.mode = data.stats.mode & 511;
294
+ } else {
295
+ data.mode = data.stats.mode & 4095;
296
+ }
297
+ // stat isn't reliable on windows; force 0755 for dir
298
+ if (win32 && isDir) {
299
+ data.mode = 493;
300
+ }
301
+ } else if (data.mode === null) {
302
+ data.mode = isDir ? 493 : 420;
303
+ }
304
+ if (data.stats && data.date === null) {
305
+ data.date = data.stats.mtime;
306
+ } else {
307
+ data.date = dateify(data.date);
308
+ }
309
+ return data;
310
+ }
311
+ /**
312
+ * Error listener that re-emits error on to our internal stream.
313
+ *
314
+ * @private
315
+ * @param {Error} err
316
+ * @return void
317
+ */
318
+ _onModuleError(err) {
319
+ /**
320
+ * @event Archiver#error
321
+ * @type {ErrorData}
322
+ */
323
+ this.emit("error", err);
324
+ }
325
+ /**
326
+ * Checks the various state variables after queue has drained to determine if
327
+ * we need to `finalize`.
328
+ *
329
+ * @private
330
+ * @return void
331
+ */
332
+ _onQueueDrain() {
333
+ if (
334
+ this._state.finalizing ||
335
+ this._state.finalized ||
336
+ this._state.aborted
337
+ ) {
338
+ return;
339
+ }
340
+ if (
341
+ this._state.finalize &&
342
+ this._pending === 0 &&
343
+ this._queue.idle() &&
344
+ this._statQueue.idle()
345
+ ) {
346
+ this._finalize();
347
+ }
348
+ }
349
+ /**
350
+ * Appends each queue task to the module.
351
+ *
352
+ * @private
353
+ * @param {Object} task
354
+ * @param {Function} callback
355
+ * @return void
356
+ */
357
+ _onQueueTask(task, callback) {
358
+ const fullCallback = () => {
359
+ if (task.data.callback) {
360
+ task.data.callback();
361
+ }
362
+ callback();
363
+ };
364
+ if (
365
+ this._state.finalizing ||
366
+ this._state.finalized ||
367
+ this._state.aborted
368
+ ) {
369
+ fullCallback();
370
+ return;
371
+ }
372
+ this._task = task;
373
+ this._moduleAppend(task.source, task.data, fullCallback);
374
+ }
375
+ /**
376
+ * Performs a file stat and reinjects the task back into the queue.
377
+ *
378
+ * @private
379
+ * @param {Object} task
380
+ * @param {Function} callback
381
+ * @return void
382
+ */
383
+ _onStatQueueTask(task, callback) {
384
+ if (
385
+ this._state.finalizing ||
386
+ this._state.finalized ||
387
+ this._state.aborted
388
+ ) {
389
+ callback();
390
+ return;
391
+ }
392
+ lstat(
393
+ task.filepath,
394
+ function (err, stats) {
395
+ if (this._state.aborted) {
396
+ setImmediate(callback);
397
+ return;
398
+ }
399
+ if (err) {
400
+ this._entriesCount--;
401
+ /**
402
+ * @event Archiver#warning
403
+ * @type {ErrorData}
404
+ */
405
+ this.emit("warning", err);
406
+ setImmediate(callback);
407
+ return;
408
+ }
409
+ task = this._updateQueueTaskWithStats(task, stats);
410
+ if (task) {
411
+ if (stats.size) {
412
+ this._fsEntriesTotalBytes += stats.size;
413
+ }
414
+ this._queue.push(task);
415
+ }
416
+ setImmediate(callback);
417
+ }.bind(this),
418
+ );
419
+ }
420
+ /**
421
+ * Unpipes the module and ends our internal stream.
422
+ *
423
+ * @private
424
+ * @return void
425
+ */
426
+ _shutdown() {
427
+ this._moduleUnpipe();
428
+ this.end();
429
+ }
430
+ /**
431
+ * Tracks the bytes emitted by our internal stream.
432
+ *
433
+ * @private
434
+ * @param {Buffer} chunk
435
+ * @param {String} encoding
436
+ * @param {Function} callback
437
+ * @return void
438
+ */
439
+ _transform(chunk, encoding, callback) {
440
+ if (chunk) {
441
+ this._pointer += chunk.length;
442
+ }
443
+ callback(null, chunk);
444
+ }
445
+ /**
446
+ * Updates and normalizes a queue task using stats data.
447
+ *
448
+ * @private
449
+ * @param {Object} task
450
+ * @param {Stats} stats
451
+ * @return {Object}
452
+ */
453
+ _updateQueueTaskWithStats(task, stats) {
454
+ if (stats.isFile()) {
455
+ task.data.type = "file";
456
+ task.data.sourceType = "stream";
457
+ task.source = new Readable(function () {
458
+ return createReadStream(task.filepath);
459
+ });
460
+ } else if (stats.isDirectory() && this._supportsDirectory) {
461
+ task.data.name = trailingSlashIt(task.data.name);
462
+ task.data.type = "directory";
463
+ task.data.sourcePath = trailingSlashIt(task.filepath);
464
+ task.data.sourceType = "buffer";
465
+ task.source = Buffer.concat([]);
466
+ } else if (stats.isSymbolicLink() && this._supportsSymlink) {
467
+ const linkPath = readlinkSync(task.filepath);
468
+ const dirName = dirname(task.filepath);
469
+ task.data.type = "symlink";
470
+ task.data.linkname = relativePath(
471
+ dirName,
472
+ resolvePath(dirName, linkPath),
473
+ );
474
+ task.data.sourceType = "buffer";
475
+ task.source = Buffer.concat([]);
476
+ } else {
477
+ if (stats.isDirectory()) {
478
+ this.emit(
479
+ "warning",
480
+ new ArchiverError("DIRECTORYNOTSUPPORTED", task.data),
481
+ );
482
+ } else if (stats.isSymbolicLink()) {
483
+ this.emit(
484
+ "warning",
485
+ new ArchiverError("SYMLINKNOTSUPPORTED", task.data),
486
+ );
487
+ } else {
488
+ this.emit("warning", new ArchiverError("ENTRYNOTSUPPORTED", task.data));
489
+ }
490
+ return null;
491
+ }
492
+ task.data = this._normalizeEntryData(task.data, stats);
493
+ return task;
494
+ }
495
+ /**
496
+ * Aborts the archiving process, taking a best-effort approach, by:
497
+ *
498
+ * - removing any pending queue tasks
499
+ * - allowing any active queue workers to finish
500
+ * - detaching internal module pipes
501
+ * - ending both sides of the Transform stream
502
+ *
503
+ * It will NOT drain any remaining sources.
504
+ *
505
+ * @return {this}
506
+ */
507
+ abort() {
508
+ if (this._state.aborted || this._state.finalized) {
509
+ return this;
510
+ }
511
+ this._abort();
512
+ return this;
513
+ }
514
+ /**
515
+ * Appends an input source (text string, buffer, or stream) to the instance.
516
+ *
517
+ * When the instance has received, processed, and emitted the input, the `entry`
518
+ * event is fired.
519
+ *
520
+ * @fires Archiver#entry
521
+ * @param {(Buffer|Stream|String)} source The input source.
522
+ * @param {EntryData} data See also {@link ZipEntryData} and {@link TarEntryData}.
523
+ * @return {this}
524
+ */
525
+ append(source, data) {
526
+ if (this._state.finalize || this._state.aborted) {
527
+ this.emit("error", new ArchiverError("QUEUECLOSED"));
528
+ return this;
529
+ }
530
+ data = this._normalizeEntryData(data);
531
+ if (typeof data.name !== "string" || data.name.length === 0) {
532
+ this.emit("error", new ArchiverError("ENTRYNAMEREQUIRED"));
533
+ return this;
534
+ }
535
+ if (data.type === "directory" && !this._supportsDirectory) {
536
+ this.emit(
537
+ "error",
538
+ new ArchiverError("DIRECTORYNOTSUPPORTED", { name: data.name }),
539
+ );
540
+ return this;
541
+ }
542
+ source = normalizeInputSource(source);
543
+ if (Buffer.isBuffer(source)) {
544
+ data.sourceType = "buffer";
545
+ } else if (isStream(source)) {
546
+ data.sourceType = "stream";
547
+ } else {
548
+ this.emit(
549
+ "error",
550
+ new ArchiverError("INPUTSTEAMBUFFERREQUIRED", { name: data.name }),
551
+ );
552
+ return this;
553
+ }
554
+ this._entriesCount++;
555
+ this._queue.push({
556
+ data: data,
557
+ source: source,
558
+ });
559
+ return this;
560
+ }
561
+ /**
562
+ * Appends a directory and its files, recursively, given its dirpath.
563
+ *
564
+ * @param {String} dirpath The source directory path.
565
+ * @param {String} destpath The destination path within the archive.
566
+ * @param {(EntryData|Function)} data See also [ZipEntryData]{@link ZipEntryData} and
567
+ * [TarEntryData]{@link TarEntryData}.
568
+ * @return {this}
569
+ */
570
+ directory(dirpath, destpath, data) {
571
+ if (this._state.finalize || this._state.aborted) {
572
+ this.emit("error", new ArchiverError("QUEUECLOSED"));
573
+ return this;
574
+ }
575
+ if (typeof dirpath !== "string" || dirpath.length === 0) {
576
+ this.emit("error", new ArchiverError("DIRECTORYDIRPATHREQUIRED"));
577
+ return this;
578
+ }
579
+ this._pending++;
580
+ if (destpath === false) {
581
+ destpath = "";
582
+ } else if (typeof destpath !== "string") {
583
+ destpath = dirpath;
584
+ }
585
+ var dataFunction = false;
586
+ if (typeof data === "function") {
587
+ dataFunction = data;
588
+ data = {};
589
+ } else if (typeof data !== "object") {
590
+ data = {};
591
+ }
592
+ var globOptions = {
593
+ stat: true,
594
+ dot: true,
595
+ };
596
+ function onGlobEnd() {
597
+ this._pending--;
598
+ this._maybeFinalize();
599
+ }
600
+ function onGlobError(err) {
601
+ this.emit("error", err);
602
+ }
603
+ function onGlobMatch(match) {
604
+ globber.pause();
605
+ let ignoreMatch = false;
606
+ let entryData = Object.assign({}, data);
607
+ entryData.name = match.relative;
608
+ entryData.prefix = destpath;
609
+ entryData.stats = match.stat;
610
+ entryData.callback = globber.resume.bind(globber);
611
+ try {
612
+ if (dataFunction) {
613
+ entryData = dataFunction(entryData);
614
+ if (entryData === false) {
615
+ ignoreMatch = true;
616
+ } else if (typeof entryData !== "object") {
617
+ throw new ArchiverError("DIRECTORYFUNCTIONINVALIDDATA", {
618
+ dirpath: dirpath,
619
+ });
620
+ }
621
+ }
622
+ } catch (e) {
623
+ this.emit("error", e);
624
+ return;
625
+ }
626
+ if (ignoreMatch) {
627
+ globber.resume();
628
+ return;
629
+ }
630
+ this._append(match.absolute, entryData);
631
+ }
632
+ const globber = readdirGlob(dirpath, globOptions);
633
+ globber.on("error", onGlobError.bind(this));
634
+ globber.on("match", onGlobMatch.bind(this));
635
+ globber.on("end", onGlobEnd.bind(this));
636
+ return this;
637
+ }
638
+ /**
639
+ * Appends a file given its filepath using a
640
+ * [lazystream]{@link https://github.com/jpommerening/node-lazystream} wrapper to
641
+ * prevent issues with open file limits.
642
+ *
643
+ * When the instance has received, processed, and emitted the file, the `entry`
644
+ * event is fired.
645
+ *
646
+ * @param {String} filepath The source filepath.
647
+ * @param {EntryData} data See also [ZipEntryData]{@link ZipEntryData} and
648
+ * [TarEntryData]{@link TarEntryData}.
649
+ * @return {this}
650
+ */
651
+ file(filepath, data) {
652
+ if (this._state.finalize || this._state.aborted) {
653
+ this.emit("error", new ArchiverError("QUEUECLOSED"));
654
+ return this;
655
+ }
656
+ if (typeof filepath !== "string" || filepath.length === 0) {
657
+ this.emit("error", new ArchiverError("FILEFILEPATHREQUIRED"));
658
+ return this;
659
+ }
660
+ this._append(filepath, data);
661
+ return this;
662
+ }
663
+ /**
664
+ * Appends multiple files that match a glob pattern.
665
+ *
666
+ * @param {String} pattern The [glob pattern]{@link https://github.com/isaacs/minimatch} to match.
667
+ * @param {Object} options See [node-readdir-glob]{@link https://github.com/yqnn/node-readdir-glob#options}.
668
+ * @param {EntryData} data See also [ZipEntryData]{@link ZipEntryData} and
669
+ * [TarEntryData]{@link TarEntryData}.
670
+ * @return {this}
671
+ */
672
+ glob(pattern, options, data) {
673
+ this._pending++;
674
+ options = {
675
+ stat: true,
676
+ pattern: pattern,
677
+ ...options,
678
+ };
679
+ function onGlobEnd() {
680
+ this._pending--;
681
+ this._maybeFinalize();
682
+ }
683
+ function onGlobError(err) {
684
+ this.emit("error", err);
685
+ }
686
+ function onGlobMatch(match) {
687
+ globber.pause();
688
+ const entryData = Object.assign({}, data);
689
+ entryData.callback = globber.resume.bind(globber);
690
+ entryData.stats = match.stat;
691
+ entryData.name = match.relative;
692
+ this._append(match.absolute, entryData);
693
+ }
694
+ const globber = new ReaddirGlob(options.cwd || ".", options);
695
+ globber.on("error", onGlobError.bind(this));
696
+ globber.on("match", onGlobMatch.bind(this));
697
+ globber.on("end", onGlobEnd.bind(this));
698
+ return this;
699
+ }
700
+ /**
701
+ * Finalizes the instance and prevents further appending to the archive
702
+ * structure (queue will continue til drained).
703
+ *
704
+ * The `end`, `close` or `finish` events on the destination stream may fire
705
+ * right after calling this method so you should set listeners beforehand to
706
+ * properly detect stream completion.
707
+ *
708
+ * @return {Promise}
709
+ */
710
+ finalize() {
711
+ if (this._state.aborted) {
712
+ var abortedError = new ArchiverError("ABORTED");
713
+ this.emit("error", abortedError);
714
+ return Promise.reject(abortedError);
715
+ }
716
+ if (this._state.finalize) {
717
+ var finalizingError = new ArchiverError("FINALIZING");
718
+ this.emit("error", finalizingError);
719
+ return Promise.reject(finalizingError);
720
+ }
721
+ this._state.finalize = true;
722
+ if (this._pending === 0 && this._queue.idle() && this._statQueue.idle()) {
723
+ this._finalize();
724
+ }
725
+ var self = this;
726
+ return new Promise(function (resolve, reject) {
727
+ var errored;
728
+ self._module.on("end", function () {
729
+ if (!errored) {
730
+ resolve();
731
+ }
732
+ });
733
+ self._module.on("error", function (err) {
734
+ errored = true;
735
+ reject(err);
736
+ });
737
+ });
738
+ }
739
+ /**
740
+ * Appends a symlink to the instance.
741
+ *
742
+ * This does NOT interact with filesystem and is used for programmatically creating symlinks.
743
+ *
744
+ * @param {String} filepath The symlink path (within archive).
745
+ * @param {String} target The target path (within archive).
746
+ * @param {Number} mode Sets the entry permissions.
747
+ * @return {this}
748
+ */
749
+ symlink(filepath, target, mode) {
750
+ if (this._state.finalize || this._state.aborted) {
751
+ this.emit("error", new ArchiverError("QUEUECLOSED"));
752
+ return this;
753
+ }
754
+ if (typeof filepath !== "string" || filepath.length === 0) {
755
+ this.emit("error", new ArchiverError("SYMLINKFILEPATHREQUIRED"));
756
+ return this;
757
+ }
758
+ if (typeof target !== "string" || target.length === 0) {
759
+ this.emit(
760
+ "error",
761
+ new ArchiverError("SYMLINKTARGETREQUIRED", { filepath: filepath }),
762
+ );
763
+ return this;
764
+ }
765
+ if (!this._supportsSymlink) {
766
+ this.emit(
767
+ "error",
768
+ new ArchiverError("SYMLINKNOTSUPPORTED", { filepath: filepath }),
769
+ );
770
+ return this;
771
+ }
772
+ var data = {};
773
+ data.type = "symlink";
774
+ data.name = filepath.replace(/\\/g, "/");
775
+ data.linkname = target.replace(/\\/g, "/");
776
+ data.sourceType = "buffer";
777
+ if (typeof mode === "number") {
778
+ data.mode = mode;
779
+ }
780
+ this._entriesCount++;
781
+ this._queue.push({
782
+ data: data,
783
+ source: Buffer.concat([]),
784
+ });
785
+ return this;
786
+ }
787
+ /**
788
+ * Returns the current length (in bytes) that has been emitted.
789
+ *
790
+ * @return {Number}
791
+ */
792
+ pointer() {
793
+ return this._pointer;
794
+ }
795
+ }
796
+
797
+ /**
798
+ * @typedef {Object} CoreOptions
799
+ * @global
800
+ * @property {Number} [statConcurrency=4] Sets the number of workers used to
801
+ * process the internal fs stat queue.
802
+ */
803
+
804
+ /**
805
+ * @typedef {Object} TransformOptions
806
+ * @property {Boolean} [allowHalfOpen=true] If set to false, then the stream
807
+ * will automatically end the readable side when the writable side ends and vice
808
+ * versa.
809
+ * @property {Boolean} [readableObjectMode=false] Sets objectMode for readable
810
+ * side of the stream. Has no effect if objectMode is true.
811
+ * @property {Boolean} [writableObjectMode=false] Sets objectMode for writable
812
+ * side of the stream. Has no effect if objectMode is true.
813
+ * @property {Boolean} [decodeStrings=true] Whether or not to decode strings
814
+ * into Buffers before passing them to _write(). `Writable`
815
+ * @property {String} [encoding=NULL] If specified, then buffers will be decoded
816
+ * to strings using the specified encoding. `Readable`
817
+ * @property {Number} [highWaterMark=16kb] The maximum number of bytes to store
818
+ * in the internal buffer before ceasing to read from the underlying resource.
819
+ * `Readable` `Writable`
820
+ * @property {Boolean} [objectMode=false] Whether this stream should behave as a
821
+ * stream of objects. Meaning that stream.read(n) returns a single value instead
822
+ * of a Buffer of size n. `Readable` `Writable`
823
+ */
824
+
825
+ /**
826
+ * @typedef {Object} EntryData
827
+ * @property {String} name Sets the entry name including internal path.
828
+ * @property {(String|Date)} [date=NOW()] Sets the entry date.
829
+ * @property {Number} [mode=D:0755/F:0644] Sets the entry permissions.
830
+ * @property {String} [prefix] Sets a path prefix for the entry name. Useful
831
+ * when working with methods like `directory` or `glob`.
832
+ * @property {fs.Stats} [stats] Sets the fs stat data for this entry allowing
833
+ * for reduction of fs stat calls when stat data is already known.
834
+ */
835
+
836
+ /**
837
+ * @typedef {Object} ErrorData
838
+ * @property {String} message The message of the error.
839
+ * @property {String} code The error code assigned to this error.
840
+ * @property {String} data Additional data provided for reporting or debugging (where available).
841
+ */
842
+
843
+ /**
844
+ * @typedef {Object} ProgressData
845
+ * @property {Object} entries
846
+ * @property {Number} entries.total Number of entries that have been appended.
847
+ * @property {Number} entries.processed Number of entries that have been processed.
848
+ * @property {Object} fs
849
+ * @property {Number} fs.totalBytes Number of bytes that have been appended. Calculated asynchronously and might not be accurate: it growth while entries are added. (based on fs.Stats)
850
+ * @property {Number} fs.processedBytes Number of bytes that have been processed. (based on fs.Stats)
851
+ */