core.js 23 KB

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