Skip to content

Commit a068a93

Browse files
authored
Merge pull request #44 from funbox/feature/output
Add “output” flag
2 parents 2ce53fc + fa42285 commit a068a93

File tree

11 files changed

+100
-45
lines changed

11 files changed

+100
-45
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## 3.1.0 (04.02.2022)
4+
5+
Added `--output` flag, which allows output to be written to provided directory.
6+
7+
38
## 3.0.0 (31.01.2022)
49

510
Removed [pngquant-bin](https://github.com/imagemin/pngquant-bin) due to license issues.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ optimizt path/to/picture.jpg
3939
- `--force` — force create AVIF and WebP even if output file size increased or file already exists.
4040
- `-l, --lossless` — optimize losslessly instead of lossily (WebP and AVIF only).
4141
- `-v, --verbose` — show additional info, e.g. skipped files.
42+
- `-o, --output` — write result to provided directory.
4243
- `-V, --version` — show tool version.
4344
- `-h, --help` — show help.
4445

README.ru.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ optimizt path/to/picture.jpg
3737
- `--force` — принудительно создавать AVIF и WebP-версии игнорируя размер итогового файла и его существование.
3838
- `-l, --lossless` — оптимизировать без потерь, а не с потерями (только для WebP и AVIF).
3939
- `-v, --verbose` — выводить дополнительную информацию в ходе работы (например, проигнорированные файлы).
40+
- `-o, --output` — сохранять результат в указанную директорию.
4041
- `-V, --version` — вывести версию изображения.
4142
- `-h, --help` — вывести справочную информацию.
4243

cli.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ program
88
.option('--webp', 'create WebP and exit')
99
.option('--force', 'force create AVIF and WebP')
1010
.option('-l, --lossless', 'perform lossless optimizations (WebP and AVIF only)')
11-
.option('-v, --verbose', 'be verbose');
11+
.option('-v, --verbose', 'be verbose')
12+
.option('-o, --output <path>', 'write output to directory');
1213

1314
program
1415
.usage('[options] <dir> <file ...>')

index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ const convert = require('./lib/convert');
22
const { enableVerbose } = require('./lib/log');
33
const optimize = require('./lib/optimize');
44
const prepareFilePaths = require('./lib/prepareFilePaths');
5+
const prepareOutputPath = require('./lib/prepareOutputPath');
56

6-
async function optimizt({ paths, avif, webp, force, lossless, verbose }) {
7+
async function optimizt({ paths, avif, webp, force, lossless, verbose, output }) {
78
if (verbose) enableVerbose();
89

910
if (avif || webp) {
@@ -13,11 +14,13 @@ async function optimizt({ paths, avif, webp, force, lossless, verbose }) {
1314
avif,
1415
webp,
1516
force,
17+
output: prepareOutputPath(output),
1618
});
1719
} else {
1820
await optimize({
1921
paths: prepareFilePaths(paths, ['gif', 'jpeg', 'jpg', 'png', 'svg']),
2022
lossless,
23+
output: prepareOutputPath(output),
2124
});
2225
}
2326
}

lib/convert.js

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ const getImageFormat = require('./getImageFormat');
1313
const formatBytes = require('./formatBytes');
1414
const getPlural = require('./getPlural');
1515
const { log } = require('./log');
16+
const prepareWriteFilePath = require('./prepareWriteFilePath');
1617
const showTotal = require('./showTotal');
1718

18-
async function convert({ paths, lossless, avif, webp, force }) {
19+
async function convert({ paths, lossless, avif, webp, force, output: outputDir }) {
1920
const totalPaths = paths.length;
2021

2122
if (!totalPaths) return;
@@ -45,6 +46,7 @@ async function convert({ paths, lossless, avif, webp, force }) {
4546
lossless,
4647
force,
4748
tasksErrors,
49+
outputDir,
4850
})));
4951
}
5052

@@ -57,6 +59,7 @@ async function convert({ paths, lossless, avif, webp, force }) {
5759
lossless,
5860
force,
5961
tasksErrors,
62+
outputDir,
6063
})));
6164
}
6265

@@ -77,7 +80,7 @@ async function convert({ paths, lossless, avif, webp, force }) {
7780
totalSize.before += fileSize.before;
7881
totalSize.after += fileSize.after;
7982

80-
checkResult({ fileBuffer, filePath, fileSize, outputFormat, force });
83+
checkResult({ fileBuffer, filePath, fileSize, outputFormat, force, outputDir });
8184
});
8285

8386
tasksErrors.forEach(error => log(...error));
@@ -91,53 +94,51 @@ function getOutputFilePath(filePath, outputFormat) {
9194
return path.join(dir, `${name}.${outputFormat.toLowerCase()}`);
9295
}
9396

94-
function checkResult({ fileBuffer, filePath, fileSize, outputFormat, force }) {
97+
function checkResult({ fileBuffer, filePath, fileSize, outputFormat, force, outputDir }) {
9598
if (!Buffer.isBuffer(fileBuffer) || typeof filePath !== 'string') return;
9699

97-
const destPath = getOutputFilePath(filePath, outputFormat);
100+
const writeFilePath = prepareWriteFilePath(getOutputFilePath(filePath, outputFormat), outputDir);
101+
const before = formatBytes(fileSize.before);
102+
const after = formatBytes(fileSize.after);
98103
const ratio = calcRatio(fileSize.before, fileSize.after);
104+
const successMessage = `${before}${after}. Ratio: ${ratio}%`;
99105

100-
if (ratio > 0 || force) {
101-
try {
102-
const before = formatBytes(fileSize.before);
103-
const after = formatBytes(fileSize.after);
106+
const isChanged = !fs.readFileSync(filePath).equals(fileBuffer);
107+
const isOptimised = ratio > 0;
104108

105-
fs.writeFileSync(destPath, fileBuffer, { flag: force ? 'w' : 'wx' });
109+
if (isOptimised || force) {
110+
try {
111+
fs.writeFileSync(writeFilePath, fileBuffer, { flag: force ? 'w' : 'wx' });
106112

107-
log(destPath, {
113+
log(filePath, {
108114
type: 'success',
109-
description: `${before}${after}. Ratio: ${ratio}%`,
115+
description: successMessage,
110116
});
111117
} catch (error) {
112118
if (error.message) {
113-
log(destPath, {
119+
log(filePath, {
114120
type: 'error',
115121
description: error.message,
116122
});
117123
} else {
118124
console.error(error);
119125
}
120126
}
121-
} else if (!fs.readFileSync(filePath).equals(fileBuffer)) {
122-
log(filePath, {
123-
description: `File size increased. Conversion to ${outputFormat} skipped`,
124-
verboseOnly: true,
125-
});
126127
} else {
127128
log(filePath, {
128-
description: `Nothing changed. Conversion to ${outputFormat} skipped`,
129+
description: `${isChanged ? 'File size increased' : 'Nothing changed'}. Conversion to ${outputFormat} skipped`,
129130
verboseOnly: true,
130131
});
131132
}
132133
}
133134

134-
function processImage({ filePath, convertFunction, lossless, outputFormat, force, progressBar, tasksErrors }) {
135-
const destPath = getOutputFilePath(filePath, outputFormat);
135+
function processImage({ filePath, convertFunction, lossless, outputFormat, force, progressBar, tasksErrors, outputDir }) {
136+
const writeFilePath = prepareWriteFilePath(getOutputFilePath(filePath, outputFormat), outputDir);
136137

137138
return fs.promises.readFile(filePath)
138139
.then(fileBuffer => {
139-
if (!force && fs.existsSync(destPath)) {
140-
throw new Error(`File already exists, '${destPath}'`);
140+
if (!force && fs.existsSync(writeFilePath)) {
141+
throw new Error(`File already exists, '${writeFilePath}'`);
141142
}
142143

143144
return convertFunction({

lib/optimize.js

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@ const calcRatio = require('./calcRatio');
1616
const formatBytes = require('./formatBytes');
1717
const getImageFormat = require('./getImageFormat');
1818
const getPlural = require('./getPlural');
19+
const prepareWriteFilePath = require('./prepareWriteFilePath');
1920
const setSrgbColorspace = require('./setSrgbColorspace');
2021
const { log } = require('./log');
2122
const showTotal = require('./showTotal');
2223

2324
const svgoConfig = require('../svgo/config');
2425

25-
async function optimize({ paths, lossless: isLossless }) {
26+
async function optimize({ paths, lossless: isLossless, output: outputDir }) {
2627
const totalPaths = paths.length;
2728

2829
if (!totalPaths) return;
@@ -81,7 +82,7 @@ async function optimize({ paths, lossless: isLossless }) {
8182
totalSize.before += fileSize.before;
8283
totalSize.after += fileSize.after;
8384

84-
checkResult(fileBuffer, filePath, fileSize);
85+
checkResult(fileBuffer, filePath, fileSize, outputDir);
8586
});
8687

8788
tasksErrors.forEach(error => log(...error));
@@ -90,7 +91,7 @@ async function optimize({ paths, lossless: isLossless }) {
9091
showTotal(totalSize.before, totalSize.after);
9192
}
9293

93-
function checkResult(fileBuffer, filePath, fileSize) {
94+
function checkResult(fileBuffer, filePath, fileSize, outputDir) {
9495
if (!Buffer.isBuffer(fileBuffer) || typeof filePath !== 'string') return;
9596

9697
const fileExt = path.extname(filePath).toLowerCase();
@@ -99,30 +100,33 @@ function checkResult(fileBuffer, filePath, fileSize) {
99100
const ratio = calcRatio(fileSize.before, fileSize.after);
100101
const successMessage = `${before}${after}. Ratio: ${ratio}%`;
101102

102-
if (ratio > 0) {
103-
fs.writeFileSync(filePath, fileBuffer);
103+
const writeFilePath = prepareWriteFilePath(filePath, outputDir);
104104

105-
log(filePath, {
106-
type: 'success',
107-
description: successMessage,
108-
});
109-
} else if (!fs.readFileSync(filePath).equals(fileBuffer)) {
110-
if (fileExt === '.svg') {
111-
fs.writeFileSync(filePath, fileBuffer);
105+
const isChanged = !fs.readFileSync(filePath).equals(fileBuffer);
106+
const isOptimized = ratio > 0;
107+
const isSvg = fileExt === '.svg';
108+
109+
if (isOptimized || (isChanged && isSvg)) {
110+
try {
111+
fs.writeFileSync(writeFilePath, fileBuffer);
112112

113113
log(filePath, {
114-
type: 'warning',
114+
type: !isOptimized ? 'warning' : 'success',
115115
description: successMessage,
116116
});
117-
} else {
118-
log(filePath, {
119-
description: 'File size increased. Skipped',
120-
verboseOnly: true,
121-
});
117+
} catch (error) {
118+
if (error.message) {
119+
log(filePath, {
120+
type: 'error',
121+
description: error.message,
122+
});
123+
} else {
124+
console.error(error);
125+
}
122126
}
123127
} else {
124128
log(filePath, {
125-
description: 'Nothing changed. Skipped',
129+
description: `${(isChanged ? 'File size increased' : 'Nothing changed')}. Skipped`,
126130
verboseOnly: true,
127131
});
128132
}

lib/prepareOutputPath.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
4+
const { log } = require('./log');
5+
6+
module.exports = function prepareOutputPath(outputPath) {
7+
if (!outputPath) return '';
8+
9+
const resolvedPath = path.resolve(outputPath);
10+
11+
if (!fs.existsSync(resolvedPath)) {
12+
logErrorAndExit('Output path does not exist');
13+
}
14+
15+
if (!fs.lstatSync(resolvedPath).isDirectory()) {
16+
logErrorAndExit('Output path must be a directory');
17+
}
18+
19+
return resolvedPath;
20+
};
21+
22+
function logErrorAndExit(title) {
23+
log(title, { type: 'error' });
24+
process.exit(1);
25+
}

lib/prepareWriteFilePath.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
4+
module.exports = function prepareWriteFilePath(filePath, outputDir) {
5+
if (!outputDir) return filePath;
6+
7+
const replacePath = `${process.cwd()}${path.sep}`;
8+
const { base, dir } = path.parse(filePath);
9+
const [, ...subDirs] = dir.split(path.sep);
10+
11+
fs.mkdirSync(path.join(outputDir, ...subDirs), { recursive: true });
12+
13+
return path.join(outputDir, ...subDirs, base).replace(replacePath, '');
14+
};

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)