utils.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. 'use strict';
  2. const fs = require('fs');
  3. const path = require('path');
  4. const mkdirp = require('mkdirp');
  5. const pump = require('pump');
  6. // file/fileBuffer/stream
  7. exports.sourceType = source => {
  8. if (!source) return undefined;
  9. if (source instanceof Buffer) return 'buffer';
  10. if (typeof source._read === 'function' || typeof source._transform === 'function') return 'stream';
  11. if (typeof source !== 'string') {
  12. const err = new Error('Type is not supported, must be a file path, file buffer, or a readable stream');
  13. err.name = 'IlligalSourceError';
  14. throw err;
  15. }
  16. return 'file';
  17. };
  18. function destType(dest) {
  19. if (typeof dest._write === 'function' || typeof dest._transform === 'function') return 'stream';
  20. if (typeof dest !== 'string') {
  21. const err = new Error('Type is not supported, must be a file path, or a writable stream');
  22. err.name = 'IlligalDestinationError';
  23. throw err;
  24. }
  25. return 'path';
  26. }
  27. exports.destType = destType;
  28. const illigalEntryError = new Error('Type is not supported, must be a file path, directory path, file buffer, or a readable stream');
  29. illigalEntryError.name = 'IlligalEntryError';
  30. // fileOrDir/fileBuffer/stream
  31. exports.entryType = entry => {
  32. if (!entry) return;
  33. if (entry instanceof Buffer) return 'buffer';
  34. if (typeof entry._read === 'function' || typeof entry._transform === 'function') return 'stream';
  35. if (typeof entry !== 'string') throw illigalEntryError;
  36. return 'fileOrDir';
  37. };
  38. exports.clone = obj => {
  39. const newObj = {};
  40. for (const i in obj) {
  41. newObj[i] = obj[i];
  42. }
  43. return newObj;
  44. };
  45. exports.makeFileProcessFn = StreamClass => {
  46. return (source, dest, opts) => {
  47. opts = opts || {};
  48. opts.source = source;
  49. const destStream = destType(dest) === 'path' ? fs.createWriteStream(dest) : dest;
  50. const compressStream = new StreamClass(opts);
  51. return safePipe([ compressStream, destStream ]);
  52. };
  53. };
  54. exports.makeCompressDirFn = StreamClass => {
  55. return (dir, dest, opts) => {
  56. const destStream = destType(dest) === 'path' ? fs.createWriteStream(dest) : dest;
  57. const compressStream = new StreamClass();
  58. compressStream.addEntry(dir, opts);
  59. return safePipe([ compressStream, destStream ]);
  60. };
  61. };
  62. exports.makeUncompressFn = StreamClass => {
  63. return (source, destDir, opts) => {
  64. opts = opts || {};
  65. opts.source = source;
  66. if (destType(destDir) !== 'path') {
  67. const error = new Error('uncompress destination must be a directory');
  68. error.name = 'IlligalDestError';
  69. throw error;
  70. }
  71. return new Promise((resolve, reject) => {
  72. mkdirp(destDir, err => {
  73. if (err) return reject(err);
  74. let entryCount = 0;
  75. let successCount = 0;
  76. let isFinish = false;
  77. function done() {
  78. // resolve when both stream finish and file write finish
  79. if (isFinish && entryCount === successCount) resolve();
  80. }
  81. new StreamClass(opts)
  82. .on('finish', () => {
  83. isFinish = true;
  84. done();
  85. })
  86. .on('error', reject)
  87. .on('entry', (header, stream, next) => {
  88. stream.on('end', next);
  89. if (header.type === 'file') {
  90. const fullpath = path.join(destDir, header.name);
  91. mkdirp(path.dirname(fullpath), err => {
  92. if (err) return reject(err);
  93. entryCount++;
  94. pump(stream, fs.createWriteStream(fullpath, { mode: header.mode }), err => {
  95. if (err) return reject(err);
  96. successCount++;
  97. done();
  98. });
  99. });
  100. } else { // directory
  101. mkdirp(path.join(destDir, header.name), err => {
  102. if (err) return reject(err);
  103. stream.resume();
  104. });
  105. }
  106. });
  107. });
  108. });
  109. };
  110. };
  111. exports.streamToBuffer = stream => {
  112. return new Promise((resolve, reject) => {
  113. const chunks = [];
  114. stream
  115. .on('readable', () => {
  116. let chunk;
  117. while ((chunk = stream.read())) chunks.push(chunk);
  118. })
  119. .on('end', () => resolve(Buffer.concat(chunks)))
  120. .on('error', err => reject(err));
  121. });
  122. };
  123. function safePipe(streams) {
  124. return new Promise((resolve, reject) => {
  125. pump(streams[0], streams[1], err => {
  126. if (err) return reject(err);
  127. resolve();
  128. });
  129. });
  130. }
  131. exports.safePipe = safePipe;
  132. exports.stripFileName = (strip, fileName, type) => {
  133. // before
  134. // node/package.json
  135. // node/lib/index.js
  136. //
  137. // when strip 1
  138. // package.json
  139. // lib/index.js
  140. //
  141. // when strip 2
  142. // package.json
  143. // index.js
  144. if (Buffer.isBuffer(fileName)) fileName = fileName.toString();
  145. // use / instead of \\
  146. if (fileName.indexOf('\\') !== -1) fileName = fileName.replace(/\\+/g, '/');
  147. // fix absolute path
  148. // /foo => foo
  149. if (fileName[0] === '/') fileName = fileName.replace(/^\/+/, '');
  150. let s = fileName.split('/');
  151. // fix relative path
  152. // foo/../bar/../../asdf/
  153. // => asdf/
  154. if (s.indexOf('..') !== -1) {
  155. fileName = path.normalize(fileName);
  156. // https://npm.taobao.org/mirrors/node/latest/docs/api/path.html#path_path_normalize_path
  157. if (process.platform === 'win32') fileName = fileName.replace(/\\+/g, '/');
  158. // replace '../' on ../../foo/bar
  159. fileName = fileName.replace(/(\.\.\/)+/, '');
  160. if (type === 'directory' && fileName && fileName[fileName.length - 1] !== '/') {
  161. fileName += '/';
  162. }
  163. s = fileName.split('/');
  164. }
  165. strip = Math.min(strip, s.length - 1);
  166. return s.slice(strip).join('/') || '/';
  167. };