gzipper
Version:
CLI for compressing files.
186 lines • 15.5 kB
JavaScript
import { lstat, readdir } from 'node:fs/promises';
import path from 'node:path';
import { Worker } from 'node:worker_threads';
import { createFolders, readableHrtime, getCPUs, chunkArray, filterObject, } from './helpers.js';
import { Logger } from './logger/Logger.js';
import { NO_FILES_MESSAGE, NO_PATH_MESSAGE, DEFAULT_OUTPUT_FORMAT_MESSAGE, INCREMENTAL_ENABLE_MESSAGE, WORKER_STARTED, } from './constants.js';
import { Incremental } from './Incremental.js';
import { Config } from './Config.js';
import { LogLevel } from './logger/LogLevel.enum.js';
import { CompressService } from './Compress.service.js';
/**
* Compressing files.
*/
export class Compress {
incremental;
config;
outputPath;
target;
service;
logger;
options;
compressionInstances;
/**
* Creates an instance of Compress.
*/
constructor(target, outputPath, options = {}) {
this.logger = new Logger();
this.logger.initialize({
verbose: options.verbose,
color: options.color,
});
this.config = new Config();
if (!target) {
const message = NO_PATH_MESSAGE;
this.logger.log(message, LogLevel.ERROR);
throw new Error(message);
}
if (outputPath) {
this.outputPath = path.resolve(process.cwd(), outputPath);
}
if (options.incremental) {
this.incremental = new Incremental(this.config);
}
this.target = path.resolve(process.cwd(), target);
this.options = options;
this.service = new CompressService(this.options);
this.compressionInstances = this.service.getCompressionInstances();
}
/**
* Start compressing files.
*/
async run() {
let files;
let hrtime;
try {
if (this.outputPath) {
await createFolders(this.outputPath);
}
if (this.options.incremental) {
await this.config.readConfig();
this.logger.log(INCREMENTAL_ENABLE_MESSAGE, LogLevel.INFO);
await this.incremental.initCacheFolder();
await this.incremental.readConfig();
}
this.compressionLog();
const hrtimeStart = process.hrtime();
const workersResponse = await this.createWorkers();
files = workersResponse.files;
hrtime = process.hrtime(hrtimeStart);
if (this.options.incremental) {
this.incremental.filePaths = workersResponse.filePaths;
await this.incremental.updateConfig();
await this.config.writeConfig();
}
}
catch (error) {
this.logger.log(error, LogLevel.ERROR);
throw new Error(error.message);
}
const filesCount = files.length;
if (filesCount) {
this.logger.log(`${filesCount} ${filesCount > 1 ? 'files have' : 'file has'} been compressed. (${readableHrtime(hrtime)})`, LogLevel.SUCCESS);
}
else {
this.logger.log(NO_FILES_MESSAGE, LogLevel.WARNING);
}
return files;
}
/**
* Returns available files to compress.
*/
async getFilesToCompress(target = this.target) {
const compressedFiles = [];
const isFileTarget = (await lstat(target)).isFile();
let filesList;
if (isFileTarget) {
const targetParsed = path.parse(target);
target = targetParsed.dir;
filesList = [targetParsed.base];
}
else {
filesList = await readdir(target);
}
for (const file of filesList) {
const filePath = path.resolve(target, file);
const fileStat = await lstat(filePath);
if (fileStat.isDirectory()) {
compressedFiles.push(...(await this.getFilesToCompress(filePath)));
}
else if (fileStat.isFile() &&
this.service.isValidFileExtensions(path.extname(filePath).slice(1))) {
if (fileStat.size < (this.options.threshold ?? 0)) {
continue;
}
compressedFiles.push(filePath);
}
}
return compressedFiles;
}
/**
* Create workers for parallel compression.
*/
async createWorkers() {
const files = await this.getFilesToCompress();
const cpus = process.env.NODE_ENV !== 'test'
? this.options.workers || getCPUs() - 1
: 1;
const size = Math.ceil(files.length / cpus);
const chunks = chunkArray(files, size);
const workers = chunks.map((chunk) => this.runCompressWorker(chunk));
const results = await Promise.all(workers);
return results.reduce((accumulator, value) => {
return {
files: [...accumulator.files, ...value.files],
filePaths: { ...accumulator.filePaths, ...value.filePaths },
};
}, {
files: [],
filePaths: {},
});
}
/**
* Run compress worker
*/
async runCompressWorker(chunk) {
return new Promise((resolve, reject) => {
const worker = new Worker(path.resolve(import.meta.dirname, process.env.NODE_ENV !== 'test'
? './Compress.worker.js'
: '../test/__mocks__/Compress.worker.import.js'), {
workerData: {
cwd: process.cwd(),
chunk,
target: this.target,
outputPath: this.outputPath,
options: this.options,
incrementalFilePaths: this.options.incremental &&
filterObject(this.incremental.filePaths, (key) => chunk.includes(key)),
},
execArgv: [...process.execArgv, '--unhandled-rejections=strict'],
});
worker.on('online', () => {
this.logger.log(`[${worker.threadId}] ${WORKER_STARTED}`, LogLevel.INFO);
});
worker.once('message', (result) => {
worker.terminate();
resolve(result);
});
worker.on('error', (error) => {
worker.terminate();
reject(error);
});
});
}
/**
* Show message with compression params.
*/
compressionLog() {
for (const instance of this.compressionInstances) {
this.logger.log(`Compression ${instance.readableOptions()}`, LogLevel.INFO);
}
if (!this.options.outputFileFormat) {
this.logger.log(DEFAULT_OUTPUT_FORMAT_MESSAGE, LogLevel.INFO);
}
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQ29tcHJlc3MuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvQ29tcHJlc3MudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLEtBQUssRUFBRSxPQUFPLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUNsRCxPQUFPLElBQUksTUFBTSxXQUFXLENBQUM7QUFDN0IsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBRTdDLE9BQU8sRUFDTCxhQUFhLEVBQ2IsY0FBYyxFQUNkLE9BQU8sRUFDUCxVQUFVLEVBQ1YsWUFBWSxHQUNiLE1BQU0sY0FBYyxDQUFDO0FBQ3RCLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUM1QyxPQUFPLEVBQ0wsZ0JBQWdCLEVBQ2hCLGVBQWUsRUFDZiw2QkFBNkIsRUFDN0IsMEJBQTBCLEVBQzFCLGNBQWMsR0FDZixNQUFNLGdCQUFnQixDQUFDO0FBTXhCLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUMvQyxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sYUFBYSxDQUFDO0FBQ3JDLE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUNyRCxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFHeEQ7O0dBRUc7QUFDSCxNQUFNLE9BQU8sUUFBUTtJQUNGLFdBQVcsQ0FBZTtJQUMxQixNQUFNLENBQVM7SUFDZixVQUFVLENBQXFCO0lBQy9CLE1BQU0sQ0FBUztJQUNmLE9BQU8sQ0FBa0I7SUFDakMsTUFBTSxDQUFTO0lBQ2YsT0FBTyxDQUFrQjtJQUN6QixvQkFBb0IsQ0FBb0I7SUFFakQ7O09BRUc7SUFDSCxZQUNFLE1BQWMsRUFDZCxVQUEwQixFQUMxQixVQUEyQixFQUFFO1FBRTdCLElBQUksQ0FBQyxNQUFNLEdBQUcsSUFBSSxNQUFNLEVBQUUsQ0FBQztRQUMzQixJQUFJLENBQUMs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