gzipper
Version:
CLI for compressing files.
166 lines • 15.6 kB
JavaScript
import { parentPort, workerData } from 'node:worker_threads';
import { createReadStream, createWriteStream } from 'node:fs';
import { lstat, unlink } from 'node:fs/promises';
import { pipeline } from 'node:stream/promises';
import path from 'node:path';
import crypto from 'node:crypto';
import { OUTPUT_FILE_FORMAT_REGEXP } from './constants.js';
import { createFolders, checkFileExists, readableSize, readableHrtime, } from './helpers.js';
import { Logger } from './logger/Logger.js';
import { CompressService } from './Compress.service.js';
import { Incremental } from './Incremental.js';
class CompressWorker {
options = workerData.options;
chunk = workerData.chunk;
target = workerData.target;
outputPath = workerData.outputPath;
incrementalFilePaths = workerData.incrementalFilePaths;
incremental;
service;
compressionInstances;
logger;
constructor() {
if (this.options.incremental) {
this.incremental = new Incremental();
this.incremental.filePaths = this.incrementalFilePaths;
}
this.logger = new Logger();
this.logger.initialize({
verbose: this.options.verbose,
color: this.options.color,
});
this.service = new CompressService(this.options);
this.compressionInstances = this.service.getCompressionInstances();
}
/**
* Compress files list and returns files and incremental data.
*/
async compressFiles() {
const filesList = [];
for (const compressionInstance of this.compressionInstances) {
for (const filePath of this.chunk) {
const hrtimeStart = process.hrtime();
const fileInfo = await this.compressFile(path.basename(filePath), path.dirname(filePath), compressionInstance);
if (!fileInfo.removeCompressed && !fileInfo.isSkipped) {
filesList.push(filePath);
}
if (this.options.verbose) {
const hrTimeEnd = process.hrtime(hrtimeStart);
this.logger.log(this.getCompressedFileMsg(compressionInstance, filePath, fileInfo, hrTimeEnd));
}
}
}
return {
files: filesList,
filePaths: this.incremental?.filePaths,
};
}
/**
* File compression.
*/
async compressFile(filename, target, compressionInstance) {
const createCompression = await compressionInstance.getCompression();
let isCached = false;
let isSkipped = false;
const inputPath = path.join(target, filename);
if (this.outputPath) {
const isFileTarget = (await lstat(this.target)).isFile();
target = isFileTarget
? this.outputPath
: path.join(this.outputPath, path.relative(this.target, target));
await createFolders(target);
}
const outputPath = this.getOutputPath(target, filename, compressionInstance.ext);
if (this.options.skipCompressed) {
if (await checkFileExists(outputPath)) {
isSkipped = true;
return { isCached, isSkipped };
}
}
if (this.options.incremental) {
const checksum = await this.incremental.getFileChecksum(inputPath);
const { isChanged, fileId } = this.incremental.setFile(inputPath, checksum, compressionInstance.compressionName, compressionInstance.compressionOptions);
const cachedFile = path.resolve(this.incremental.cacheFolder, fileId);
if (isChanged) {
await pipeline(createReadStream(inputPath), createCompression, createWriteStream(outputPath));
await pipeline(createReadStream(outputPath), createWriteStream(cachedFile));
}
else {
await pipeline(createReadStream(cachedFile), createWriteStream(outputPath));
isCached = true;
}
}
else {
await pipeline(createReadStream(inputPath), createCompression, createWriteStream(outputPath));
}
if (this.options.verbose || this.options.removeLarger) {
const beforeSize = (await lstat(inputPath)).size;
const afterSize = (await lstat(outputPath)).size;
const removeCompressed = this.options.removeLarger && beforeSize < afterSize;
if (removeCompressed) {
await unlink(outputPath);
}
return {
beforeSize,
afterSize,
isCached,
isSkipped,
removeCompressed,
};
}
return { isCached, isSkipped };
}
/**
* Get output path which is based on [outputFileFormat].
*/
getOutputPath(target, file, ext) {
const artifactsMap = new Map([
['[filename]', path.parse(file).name],
['[ext]', path.extname(file).slice(1)],
['[compressExt]', ext],
]);
let filename = `${artifactsMap.get('[filename]')}.${artifactsMap.get('[ext]')}.${artifactsMap.get('[compressExt]')}`;
if (this.options.outputFileFormat) {
artifactsMap.set('[hash]', null);
filename = this.options.outputFileFormat.replace(OUTPUT_FILE_FORMAT_REGEXP, (artifact) => {
if (artifactsMap.has(artifact)) {
// Need to generate hash only if we have appropriate param
if (artifact === '[hash]') {
artifactsMap.set('[hash]', crypto.randomUUID());
}
return artifactsMap.get(artifact);
}
else {
return artifact;
}
});
}
filename = filename.replaceAll(/\.+/g, (match, offset, value) => match.length + offset === value.length ? '' : '.');
return path.join(target, filename);
}
/**
* Returns information message about compressed file (size, time, cache, etc.)
*/
getCompressedFileMsg(compressionInstance, file, fileInfo, hrtime) {
const fileRelative = path.relative(this.target, file);
const compressionName = compressionInstance.compressionName;
if (fileInfo.isSkipped) {
return `File ${fileRelative} has been skipped.`;
}
const getSize = `${readableSize(fileInfo.beforeSize)} -> ${readableSize(fileInfo.afterSize)}`;
const getTime = readableHrtime(hrtime);
const fileMessage = fileInfo.isCached
? `File ${fileRelative} has been retrieved from the cache.`
: `File ${fileRelative} has been compressed.`;
return `${fileMessage} \n
Algorithm: ${compressionName} \n
Size: ${getSize} \n
Time: ${getTime}`;
}
}
const compressWorker = new CompressWorker();
(async function () {
const { files, filePaths } = await compressWorker.compressFiles();
parentPort?.postMessage({ files, filePaths });
})();
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQ29tcHJlc3Mud29ya2VyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL0NvbXByZXNzLndvcmtlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsVUFBVSxFQUFFLFVBQVUsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBQzdELE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxpQkFBaUIsRUFBRSxNQUFNLFNBQVMsQ0FBQztBQUM5RCxPQUFPLEVBQUUsS0FBSyxFQUFFLE1BQU0sRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBQ2pELE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUNoRCxPQUFPLElBQUksTUFBTSxXQUFXLENBQUM7QUFDN0IsT0FBTyxNQUFNLE1BQU0sYUFBYSxDQUFDO0FBU2pDLE9BQU8sRUFBRSx5QkFBeUIsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBQzNELE9BQU8sRUFDTCxhQUFhLEVBQ2IsZUFBZSxFQUNmLFlBQVksRUFDWixjQUFjLEdBQ2YsTUFBTSxjQUFjLENBQUM7QUFDdEIsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQzVDLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUN4RCxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sa0JBQWtCLENBQUM7QUFFL0MsTUFBTSxjQUFjO0lBQ0QsT0FBTyxHQUFvQixVQUFVLENBQUMsT0FBTyxDQUFDO0lBQzlDLEtBQUssR0FBYSxVQUFVLENBQUMsS0FBSyxDQUFDO0lBQ25DLE1BQU0sR0FBVyxVQUFVLENBQUMsTUFBTSxDQUFDO0lBQ25DLFVBQVUsR0FBVyxVQUFVLENBQUMsVUFBVSxDQUFDO0lBQzNDLG9CQUFvQixHQUNuQyxVQUFVLENBQUMsb0JBQW9CLENBQUM7SUFDakIsV0FBVyxDQUFlO0lBQzFCLE9BQU8sQ0FBa0I7SUFDekIsb0JBQW9CLENBQW9CO0lBQ3hDLE1BQU0sQ0FB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