230 lines
6.5 KiB
Plaintext
230 lines
6.5 KiB
Plaintext
'use strict';
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const mkdirp = require('mkdirp');
|
|
const pump = require('pump');
|
|
|
|
// file/fileBuffer/stream
|
|
exports.sourceType = source => {
|
|
if (!source) return undefined;
|
|
|
|
if (source instanceof Buffer) return 'buffer';
|
|
if (typeof source._read === 'function' || typeof source._transform === 'function') return 'stream';
|
|
if (typeof source !== 'string') {
|
|
const err = new Error('Type is not supported, must be a file path, file buffer, or a readable stream');
|
|
err.name = 'IlligalSourceError';
|
|
throw err;
|
|
}
|
|
|
|
return 'file';
|
|
};
|
|
|
|
function destType(dest) {
|
|
if (typeof dest._write === 'function' || typeof dest._transform === 'function') return 'stream';
|
|
if (typeof dest !== 'string') {
|
|
const err = new Error('Type is not supported, must be a file path, or a writable stream');
|
|
err.name = 'IlligalDestinationError';
|
|
throw err;
|
|
}
|
|
return 'path';
|
|
}
|
|
|
|
exports.destType = destType;
|
|
|
|
const illigalEntryError = new Error('Type is not supported, must be a file path, directory path, file buffer, or a readable stream');
|
|
illigalEntryError.name = 'IlligalEntryError';
|
|
|
|
// fileOrDir/fileBuffer/stream
|
|
exports.entryType = entry => {
|
|
if (!entry) return;
|
|
|
|
if (entry instanceof Buffer) return 'buffer';
|
|
if (typeof entry._read === 'function' || typeof entry._transform === 'function') return 'stream';
|
|
if (typeof entry !== 'string') throw illigalEntryError;
|
|
|
|
return 'fileOrDir';
|
|
};
|
|
|
|
|
|
exports.clone = obj => {
|
|
const newObj = {};
|
|
for (const i in obj) {
|
|
newObj[i] = obj[i];
|
|
}
|
|
return newObj;
|
|
};
|
|
|
|
exports.makeFileProcessFn = StreamClass => {
|
|
return (source, dest, opts) => {
|
|
opts = opts || {};
|
|
opts.source = source;
|
|
const destStream = destType(dest) === 'path' ? fs.createWriteStream(dest) : dest;
|
|
const compressStream = new StreamClass(opts);
|
|
return safePipe([ compressStream, destStream ]);
|
|
};
|
|
};
|
|
|
|
exports.makeCompressDirFn = StreamClass => {
|
|
return (dir, dest, opts) => {
|
|
const destStream = destType(dest) === 'path' ? fs.createWriteStream(dest) : dest;
|
|
const compressStream = new StreamClass();
|
|
compressStream.addEntry(dir, opts);
|
|
return safePipe([ compressStream, destStream ]);
|
|
};
|
|
};
|
|
|
|
exports.makeUncompressFn = StreamClass => {
|
|
return (source, destDir, opts) => {
|
|
opts = opts || {};
|
|
opts.source = source;
|
|
if (!source) { // !source 和 sourceType中返回undeined对应
|
|
const error = new Error('Type is not supported, must be a file path, file buffer, or a readable stream');
|
|
error.name = 'IlligalSourceError';
|
|
throw error;
|
|
}
|
|
if (destType(destDir) !== 'path') {
|
|
const error = new Error('uncompress destination must be a directory');
|
|
error.name = 'IlligalDestError';
|
|
throw error;
|
|
}
|
|
|
|
return new Promise((resolve, reject) => {
|
|
mkdirp(destDir, err => {
|
|
if (err) return reject(err);
|
|
|
|
let entryCount = 0;
|
|
let successCount = 0;
|
|
let isFinish = false;
|
|
function done() {
|
|
// resolve when both stream finish and file write finish
|
|
if (isFinish && entryCount === successCount) resolve();
|
|
}
|
|
|
|
new StreamClass(opts)
|
|
.on('finish', () => {
|
|
isFinish = true;
|
|
done();
|
|
})
|
|
.on('error', reject)
|
|
.on('entry', (header, stream, next) => {
|
|
stream.on('end', next);
|
|
const destFilePath = path.join(destDir, header.name);
|
|
|
|
if (header.type === 'file') {
|
|
const dir = path.dirname(destFilePath);
|
|
mkdirp(dir, err => {
|
|
if (err) return reject(err);
|
|
|
|
entryCount++;
|
|
pump(stream, fs.createWriteStream(destFilePath, { mode: opts.mode || header.mode }), err => {
|
|
if (err) return reject(err);
|
|
successCount++;
|
|
done();
|
|
});
|
|
});
|
|
} else if (header.type === 'symlink') {
|
|
const dir = path.dirname(destFilePath);
|
|
const target = path.resolve(dir, header.linkname);
|
|
entryCount++;
|
|
|
|
mkdirp(dir, err => {
|
|
if (err) return reject(err);
|
|
|
|
const relativeTarget = path.relative(dir, target);
|
|
fs.symlink(relativeTarget, destFilePath, err => {
|
|
if (err) return reject(err);
|
|
successCount++;
|
|
stream.resume();
|
|
});
|
|
});
|
|
} else { // directory
|
|
mkdirp(destFilePath, err => {
|
|
if (err) return reject(err);
|
|
stream.resume();
|
|
});
|
|
}
|
|
});
|
|
});
|
|
});
|
|
};
|
|
};
|
|
|
|
exports.streamToBuffer = stream => {
|
|
return new Promise((resolve, reject) => {
|
|
const chunks = [];
|
|
stream
|
|
.on('readable', () => {
|
|
let chunk;
|
|
while ((chunk = stream.read())) chunks.push(chunk);
|
|
})
|
|
.on('end', () => resolve(Buffer.concat(chunks)))
|
|
.on('error', err => reject(err));
|
|
});
|
|
};
|
|
|
|
function safePipe(streams) {
|
|
return new Promise((resolve, reject) => {
|
|
pump(streams[0], streams[1], err => {
|
|
if (err) return reject(err);
|
|
resolve();
|
|
});
|
|
});
|
|
}
|
|
|
|
exports.safePipe = safePipe;
|
|
|
|
function normalizePath(fileName) {
|
|
fileName = path.normalize(fileName);
|
|
// https://nodejs.org/api/path.html#path_path_normalize_path
|
|
if (process.platform === 'win32') fileName = fileName.replace(/\\+/g, '/');
|
|
return fileName;
|
|
}
|
|
|
|
function stripFileName(strip, fileName, type) {
|
|
// before
|
|
// node/package.json
|
|
// node/lib/index.js
|
|
//
|
|
// when strip 1
|
|
// package.json
|
|
// lib/index.js
|
|
//
|
|
// when strip 2
|
|
// package.json
|
|
// index.js
|
|
if (Buffer.isBuffer(fileName)) fileName = fileName.toString();
|
|
|
|
// use / instead of \\
|
|
if (fileName.indexOf('\\') !== -1) fileName = fileName.replace(/\\+/g, '/');
|
|
|
|
// fix absolute path
|
|
// /foo => foo
|
|
if (fileName[0] === '/') fileName = fileName.replace(/^\/+/, '');
|
|
|
|
// fix case
|
|
// ./foo/bar => foo/bar
|
|
if (fileName) {
|
|
fileName = normalizePath(fileName);
|
|
}
|
|
|
|
let s = fileName.split('/');
|
|
|
|
// fix relative path
|
|
// foo/../bar/../../asdf/
|
|
// => asdf/
|
|
if (s.indexOf('..') !== -1) {
|
|
// replace '../' on ../../foo/bar
|
|
fileName = fileName.replace(/(\.\.\/)+/, '');
|
|
if (type === 'directory' && fileName && fileName[fileName.length - 1] !== '/') {
|
|
fileName += '/';
|
|
}
|
|
s = fileName.split('/');
|
|
}
|
|
|
|
strip = Math.min(strip, s.length - 1);
|
|
return s.slice(strip).join('/') || '/';
|
|
}
|
|
|
|
exports.stripFileName = stripFileName;
|