easy-tinypng-cli
Version:
A cli can be used to compress images with tinypng.com
178 lines (177 loc) • 6.23 kB
JavaScript
import path from 'node:path';
import { cwd } from 'node:process';
import fs from 'node:fs';
import tinify from 'tinify';
import chokidar from 'chokidar';
import fse from 'fs-extra';
import figlet from 'figlet';
import chalk from 'chalk';
import ora from 'ora';
import { Command } from 'commander';
import fg from 'fast-glob';
const program = new Command();
program
.option('-o, --once', 'compress just once');
program.parse(process.argv);
let compressedImagesNumber = 0;
let needCompressImagesNumber = 0;
const options = program.opts();
// eslint-disable-next-line no-console
const log = console.log;
const RecordFilePath = path.resolve(cwd(), 'record.json');
function defineTinyConfig(config) {
return config;
}
export { defineTinyConfig, };
export function getAllConfigs(config) {
return config.configs;
}
export async function startOptimize(configs, APIKey) {
tinify.key = APIKey;
await ConsoleFigFont('tinypng cli is running !!!');
for (let i = 0; i < configs.length; i++) {
const { targetDir } = configs[i];
const count = await getTargetFileImagesCount(targetDir);
needCompressImagesNumber += count;
}
if (!needCompressImagesNumber) {
console.warn('There is no images need to compress!');
process.exit(0);
}
for (let i = 0; i < configs.length; i++) {
const { targetDir } = configs[i];
log(chalk.bgBlue.bold(`${targetDir} watching~~~\n`));
chokidar.watch(path.resolve(cwd(), targetDir), {
atomic: true,
followSymlinks: true,
}).on('all', async (event, pathDir) => {
switch (event) {
case 'add':
await reduceImage(targetDir, pathDir, pathDir);
break;
case 'unlink':
autoRecord(targetDir, event, pathDir);
break;
case 'change':
default:
break;
}
});
}
}
export function isPathDirectory(path) {
return fs.statSync(path)?.isDirectory();
}
export function isFileExist(pathDir) {
return new Promise((resolve) => {
fs.access(pathDir, fs.constants.F_OK, (err) => {
resolve(!err);
});
});
}
export async function isRecord(watchFileDir, pathDir) {
const isExist = await isFileExist(RecordFilePath);
if (isExist) {
const json = fse.readJSONSync(RecordFilePath);
const isRecord = Object.prototype.hasOwnProperty.call(json, path.relative(watchFileDir, pathDir));
return isRecord;
}
else {
return false;
}
}
export async function record(watchFileDir, pathDir) {
const isExist = await isFileExist(RecordFilePath);
if (isExist) {
const json = fse.readJSONSync(RecordFilePath);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
json[path.relative(watchFileDir, pathDir)] = path.relative(watchFileDir, pathDir);
fse.writeJSONSync(RecordFilePath, json, { spaces: 2 });
}
else {
fse.writeJSONSync(RecordFilePath, { [path.relative(watchFileDir, pathDir)]: path.relative(watchFileDir, pathDir) }, { spaces: 2 });
}
}
export async function removeRecord(watchFileDir, pathDir) {
const RecordFilePath = path.resolve(cwd(), 'record.json');
const isExist = await isFileExist(RecordFilePath);
if (isExist) {
const json = fse.readJSONSync(RecordFilePath);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
delete json[path.relative(watchFileDir, pathDir)];
fse.writeJSONSync(RecordFilePath, json, { spaces: 2 });
}
}
export function getFileName(pathDir) {
return path.basename(pathDir);
}
export function getExtName(pathDir) {
return path.extname(pathDir).slice(1);
}
export async function autoRecord(watchFileDir, action, pathDir) {
if (!isImageFile(pathDir))
return;
if (action === 'add')
await record(watchFileDir, pathDir);
if (action === 'unlink')
await removeRecord(watchFileDir, pathDir);
}
export function isImageFile(pathDir) {
const fileExtname = getExtName(pathDir);
const supportFiles = ['webp', 'jpeg', 'png', 'jpg'];
return supportFiles.includes(fileExtname);
}
export async function reduceImage(watchFileDir, fileDir, targetDir) {
const recorded = await isRecord(watchFileDir, fileDir);
if (recorded) {
compressedImagesNumber++;
if (compressedImagesNumber === needCompressImagesNumber && options.once) {
console.warn(chalk.bgGreen.bold('\nAll images have been compressed!'));
process.exit(0);
}
return;
}
if (!isImageFile(fileDir))
return;
const spinner = ora('Loading').start();
try {
spinner.color = 'blue';
spinner.text = chalk.bold.greenBright(`compressing ${fileDir}`);
tinify.fromFile(fileDir).toFile(targetDir).then(async () => {
await autoRecord(watchFileDir, 'add', fileDir);
compressedImagesNumber++;
spinner.succeed();
if (compressedImagesNumber === needCompressImagesNumber && options.once) {
console.warn(chalk.bgGreen.bold('\nAll images have been compressed!'));
process.exit(0);
}
});
}
catch (err) {
spinner.fail();
}
}
export function ConsoleFigFont(str) {
return new Promise((resolve, reject) => {
figlet(str, (err, data) => {
if (err) {
console.warn('Something went wrong...');
console.warn(err);
reject(err);
}
console.warn(data);
resolve('');
});
});
}
export async function getTargetFileImagesCount(targetDir) {
const patternArray = [
`${path.join(process.cwd(), targetDir, '/**/*.png')}`.replace(/\\/g, '/'),
`${path.join(process.cwd(), targetDir, '/**/*.jpeg')}`.replace(/\\/g, '/'),
`${path.join(process.cwd(), targetDir, '/**/*.jpg')}`.replace(/\\/g, '/'),
];
const entries = await fg(patternArray, { dot: true });
return entries.length;
}