webpocalypse 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +138 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +66 -0
- package/dist/cli.js.map +1 -0
- package/dist/convert.d.ts +7 -0
- package/dist/convert.d.ts.map +1 -0
- package/dist/convert.js +126 -0
- package/dist/convert.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +101 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +8 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +149 -0
- package/dist/logger.js.map +1 -0
- package/dist/scan.d.ts +11 -0
- package/dist/scan.d.ts.map +1 -0
- package/dist/scan.js +53 -0
- package/dist/scan.js.map +1 -0
- package/dist/types.d.ts +34 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/writer.d.ts +25 -0
- package/dist/writer.d.ts.map +1 -0
- package/dist/writer.js +76 -0
- package/dist/writer.js.map +1 -0
- package/package.json +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# webpocalypse
|
|
2
|
+
|
|
3
|
+
> Batch convert images to WebP/AVIF — fully local, no server, no API calls.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
npx webpocalypse ./public --format webp --quality 80 --max-width 1920
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Recursively scans a directory, converts every `.jpg`, `.jpeg`, `.png` to WebP
|
|
10
|
+
and/or AVIF using [sharp](https://sharp.pixelplumbing.com/), preserves the full
|
|
11
|
+
folder structure, and reports per-file savings.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install -g webpocalypse
|
|
19
|
+
# or run without installing:
|
|
20
|
+
npx webpocalypse <input> [options]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
webpocalypse <input> [options]
|
|
29
|
+
|
|
30
|
+
Arguments:
|
|
31
|
+
input Directory of images to convert
|
|
32
|
+
|
|
33
|
+
Options:
|
|
34
|
+
-f, --format <format> Output format: webp | avif | both (default: webp)
|
|
35
|
+
-q, --quality <number> Compression quality 1–100 (default: 80)
|
|
36
|
+
--lossless Lossless compression
|
|
37
|
+
--max-width <px> Maximum output width (no upscaling)
|
|
38
|
+
--max-height <px> Maximum output height (no upscaling)
|
|
39
|
+
-o, --out <path> Output directory (default: <input>-optimized)
|
|
40
|
+
--in-place Replace source directory safely via temp dir
|
|
41
|
+
-h, --help Show help
|
|
42
|
+
-V, --version Show version
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Examples
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# WebP at quality 80 (default)
|
|
51
|
+
webpocalypse ./images
|
|
52
|
+
|
|
53
|
+
# Both WebP + AVIF
|
|
54
|
+
webpocalypse ./images --format both --quality 75
|
|
55
|
+
|
|
56
|
+
# Resize + convert
|
|
57
|
+
webpocalypse ./public/photos --format webp --max-width 1920 --quality 85
|
|
58
|
+
|
|
59
|
+
# Lossless WebP
|
|
60
|
+
webpocalypse ./assets --format webp --lossless
|
|
61
|
+
|
|
62
|
+
# Custom output directory
|
|
63
|
+
webpocalypse ./src/images --format avif --out ./dist/images
|
|
64
|
+
|
|
65
|
+
# Overwrite source in-place (safe: uses temp dir, rolls back on failure)
|
|
66
|
+
webpocalypse ./public --format webp --in-place
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Output
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
webpocalypse v1.0.0
|
|
75
|
+
Input: /home/user/project/public/images
|
|
76
|
+
Output: /home/user/project/public/images-optimized
|
|
77
|
+
Format: both Quality: 80
|
|
78
|
+
Files: 42 images found → 84 outputs
|
|
79
|
+
|
|
80
|
+
File Original Converted Savings
|
|
81
|
+
─────────────────────────────────────────────────────────────────────────────
|
|
82
|
+
hero.webp 1.2 MB 149 KB 88%
|
|
83
|
+
hero.avif 1.2 MB 218 KB 82%
|
|
84
|
+
icons/logo.webp 45 KB 12 KB 73%
|
|
85
|
+
icons/logo.avif 45 KB 9 KB 80%
|
|
86
|
+
...
|
|
87
|
+
─────────────────────────────────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
✔ 84 files converted
|
|
90
|
+
✔ 56.2 MB → 9.1 MB (84% saved)
|
|
91
|
+
|
|
92
|
+
Output: /home/user/project/public/images-optimized
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Behavior
|
|
98
|
+
|
|
99
|
+
| Extension | Action |
|
|
100
|
+
|------------------|-------------------------------|
|
|
101
|
+
| `.jpg` / `.jpeg` | Re-encoded via sharp |
|
|
102
|
+
| `.png` | Re-encoded via sharp |
|
|
103
|
+
| `.webp` / `.avif`| Copied as-is (no re-encoding) |
|
|
104
|
+
|
|
105
|
+
### `--in-place` safety
|
|
106
|
+
|
|
107
|
+
1. All files are written to a temporary directory first.
|
|
108
|
+
2. **Only if every conversion succeeds**, the source directory is replaced atomically.
|
|
109
|
+
3. On any failure the source is left completely untouched and the temp dir is cleaned up.
|
|
110
|
+
|
|
111
|
+
### Exit codes
|
|
112
|
+
|
|
113
|
+
| Code | Meaning |
|
|
114
|
+
|------|--------------------------|
|
|
115
|
+
| `0` | All files processed OK |
|
|
116
|
+
| `1` | One or more files failed |
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Project structure
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
src/
|
|
124
|
+
index.ts Entry point & orchestration
|
|
125
|
+
cli.ts Argument parsing (commander)
|
|
126
|
+
scan.ts Recursive directory traversal
|
|
127
|
+
convert.ts sharp encoding logic + p-limit concurrency
|
|
128
|
+
writer.ts Output & in-place replacement logic
|
|
129
|
+
logger.ts Table, summary, formatting helpers
|
|
130
|
+
types.ts Shared TypeScript types
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Requirements
|
|
136
|
+
|
|
137
|
+
- Node.js ≥ 18
|
|
138
|
+
- Runs on Linux, macOS, Windows (sharp ships pre-built binaries)
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAgB,MAAM,YAAY,CAAC;AAI3D,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,UAAU,CAAA;CAAE,CAiEnF"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.parseArgs = parseArgs;
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const VALID_FORMATS = ['webp', 'avif', 'both'];
|
|
10
|
+
function parseArgs(argv) {
|
|
11
|
+
const program = new commander_1.Command();
|
|
12
|
+
program
|
|
13
|
+
.name('webpocalypse')
|
|
14
|
+
.description('Batch convert images to WebP/AVIF with quality control and directory structure preservation')
|
|
15
|
+
.version('1.0.0')
|
|
16
|
+
.argument('<input>', 'Input directory containing images to convert')
|
|
17
|
+
.option('-f, --format <format>', 'Output format: webp, avif, or both', 'webp')
|
|
18
|
+
.option('-q, --quality <number>', 'Compression quality (50–100)', '80')
|
|
19
|
+
.option('--lossless', 'Use lossless compression', false)
|
|
20
|
+
.option('--max-width <number>', 'Maximum output width in pixels (no upscaling)')
|
|
21
|
+
.option('--max-height <number>', 'Maximum output height in pixels (no upscaling)')
|
|
22
|
+
.option('-o, --out <path>', 'Output directory (default: <input>-optimized)')
|
|
23
|
+
.option('--in-place', 'Overwrite source directory safely via temp dir', false);
|
|
24
|
+
program.parse(argv);
|
|
25
|
+
const opts = program.opts();
|
|
26
|
+
const [inputArg] = program.args;
|
|
27
|
+
if (!inputArg) {
|
|
28
|
+
program.error('Missing required argument: <input>');
|
|
29
|
+
}
|
|
30
|
+
const format = opts.format;
|
|
31
|
+
if (!VALID_FORMATS.includes(format)) {
|
|
32
|
+
program.error(`Invalid format "${format}". Must be one of: ${VALID_FORMATS.join(', ')}`);
|
|
33
|
+
}
|
|
34
|
+
const quality = parseInt(opts.quality, 10);
|
|
35
|
+
if (isNaN(quality) || quality < 1 || quality > 100) {
|
|
36
|
+
program.error(`Invalid quality "${opts.quality}". Must be a number between 1 and 100`);
|
|
37
|
+
}
|
|
38
|
+
let maxWidth;
|
|
39
|
+
if (opts.maxWidth !== undefined) {
|
|
40
|
+
maxWidth = parseInt(opts.maxWidth, 10);
|
|
41
|
+
if (isNaN(maxWidth) || maxWidth <= 0) {
|
|
42
|
+
program.error(`Invalid --max-width "${opts.maxWidth}". Must be a positive integer`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
let maxHeight;
|
|
46
|
+
if (opts.maxHeight !== undefined) {
|
|
47
|
+
maxHeight = parseInt(opts.maxHeight, 10);
|
|
48
|
+
if (isNaN(maxHeight) || maxHeight <= 0) {
|
|
49
|
+
program.error(`Invalid --max-height "${opts.maxHeight}". Must be a positive integer`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const inputDir = path_1.default.resolve(inputArg);
|
|
53
|
+
return {
|
|
54
|
+
inputDir,
|
|
55
|
+
options: {
|
|
56
|
+
format: format,
|
|
57
|
+
quality,
|
|
58
|
+
lossless: opts.lossless,
|
|
59
|
+
maxWidth,
|
|
60
|
+
maxHeight,
|
|
61
|
+
out: opts.out ? path_1.default.resolve(opts.out) : undefined,
|
|
62
|
+
inPlace: opts.inPlace,
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { FileEntry, CliOptions, ConversionResult } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Convert all files and write directly to outputDir.
|
|
4
|
+
* Calls onProgress after each file finishes (success or failure).
|
|
5
|
+
*/
|
|
6
|
+
export declare function convertFiles(files: FileEntry[], outputDir: string, options: CliOptions, onProgress: (result: ConversionResult) => void): Promise<ConversionResult[]>;
|
|
7
|
+
//# sourceMappingURL=convert.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"convert.d.ts","sourceRoot":"","sources":["../src/convert.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAgF1E;;;GAGG;AACH,wBAAsB,YAAY,CAChC,KAAK,EAAE,SAAS,EAAE,EAClB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,UAAU,EACnB,UAAU,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,GAC7C,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAuD7B"}
|
package/dist/convert.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.convertFiles = convertFiles;
|
|
7
|
+
const sharp_1 = __importDefault(require("sharp"));
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
11
|
+
const p_limit_1 = __importDefault(require("p-limit"));
|
|
12
|
+
const scan_js_1 = require("./scan.js");
|
|
13
|
+
function applyResize(image, options) {
|
|
14
|
+
if (!options.maxWidth && !options.maxHeight)
|
|
15
|
+
return image;
|
|
16
|
+
return image.resize({
|
|
17
|
+
width: options.maxWidth,
|
|
18
|
+
height: options.maxHeight,
|
|
19
|
+
fit: 'inside',
|
|
20
|
+
withoutEnlargement: true,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
async function encodeToFile(file, format, outputDir, options) {
|
|
24
|
+
const inputBuffer = fs_1.default.readFileSync(file.inputPath);
|
|
25
|
+
const originalSize = inputBuffer.length;
|
|
26
|
+
const outputRelativePath = (0, scan_js_1.replaceExtension)(file.relativePath, format);
|
|
27
|
+
const outputPath = path_1.default.join(outputDir, outputRelativePath);
|
|
28
|
+
await fs_extra_1.default.ensureDir(path_1.default.dirname(outputPath));
|
|
29
|
+
let image = (0, sharp_1.default)(inputBuffer);
|
|
30
|
+
image = applyResize(image, options);
|
|
31
|
+
if (format === 'webp') {
|
|
32
|
+
await (options.lossless
|
|
33
|
+
? image.webp({ lossless: true })
|
|
34
|
+
: image.webp({ quality: options.quality })).toFile(outputPath);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
// effort 2 (scale 0–9): ~10× faster than default 4 with negligible quality
|
|
38
|
+
// loss for web use. The default effort level locks the CPU.
|
|
39
|
+
await (options.lossless
|
|
40
|
+
? image.avif({ lossless: true, effort: 2 })
|
|
41
|
+
: image.avif({ quality: options.quality, effort: 2 })).toFile(outputPath);
|
|
42
|
+
}
|
|
43
|
+
const convertedSize = fs_1.default.statSync(outputPath).size;
|
|
44
|
+
return {
|
|
45
|
+
inputPath: file.inputPath,
|
|
46
|
+
relativePath: file.relativePath,
|
|
47
|
+
outputPath,
|
|
48
|
+
outputRelativePath,
|
|
49
|
+
originalSize,
|
|
50
|
+
convertedSize,
|
|
51
|
+
success: true,
|
|
52
|
+
skipped: false,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
async function passthroughFile(file, outputDir) {
|
|
56
|
+
const outputPath = path_1.default.join(outputDir, file.relativePath);
|
|
57
|
+
await fs_extra_1.default.ensureDir(path_1.default.dirname(outputPath));
|
|
58
|
+
await fs_extra_1.default.copy(file.inputPath, outputPath);
|
|
59
|
+
const size = fs_1.default.statSync(file.inputPath).size;
|
|
60
|
+
return {
|
|
61
|
+
inputPath: file.inputPath,
|
|
62
|
+
relativePath: file.relativePath,
|
|
63
|
+
outputPath,
|
|
64
|
+
outputRelativePath: file.relativePath,
|
|
65
|
+
originalSize: size,
|
|
66
|
+
convertedSize: size,
|
|
67
|
+
success: true,
|
|
68
|
+
skipped: true,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Convert all files and write directly to outputDir.
|
|
73
|
+
* Calls onProgress after each file finishes (success or failure).
|
|
74
|
+
*/
|
|
75
|
+
async function convertFiles(files, outputDir, options, onProgress) {
|
|
76
|
+
// AVIF is much heavier to encode — reduce concurrency to avoid CPU saturation
|
|
77
|
+
const concurrency = options.format === 'avif' ? 3
|
|
78
|
+
: options.format === 'both' ? 4
|
|
79
|
+
: 6;
|
|
80
|
+
const limit = (0, p_limit_1.default)(concurrency);
|
|
81
|
+
const allResults = [];
|
|
82
|
+
const tasks = files.flatMap((file) => {
|
|
83
|
+
if ((0, scan_js_1.isPassthrough)(file.inputPath)) {
|
|
84
|
+
return [
|
|
85
|
+
limit(async () => {
|
|
86
|
+
const result = await passthroughFile(file, outputDir);
|
|
87
|
+
allResults.push(result);
|
|
88
|
+
onProgress(result);
|
|
89
|
+
}),
|
|
90
|
+
];
|
|
91
|
+
}
|
|
92
|
+
const formats = options.format === 'both' ? ['webp', 'avif'] : [options.format];
|
|
93
|
+
return formats.map((fmt) => limit(async () => {
|
|
94
|
+
let result;
|
|
95
|
+
try {
|
|
96
|
+
result = await encodeToFile(file, fmt, outputDir, options);
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
const stat = (() => {
|
|
100
|
+
try {
|
|
101
|
+
return fs_1.default.statSync(file.inputPath);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
})();
|
|
107
|
+
result = {
|
|
108
|
+
inputPath: file.inputPath,
|
|
109
|
+
relativePath: file.relativePath,
|
|
110
|
+
outputPath: '',
|
|
111
|
+
outputRelativePath: (0, scan_js_1.replaceExtension)(file.relativePath, fmt),
|
|
112
|
+
originalSize: stat?.size ?? 0,
|
|
113
|
+
convertedSize: 0,
|
|
114
|
+
success: false,
|
|
115
|
+
skipped: false,
|
|
116
|
+
error: err instanceof Error ? err.message : String(err),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
allResults.push(result);
|
|
120
|
+
onProgress(result);
|
|
121
|
+
}));
|
|
122
|
+
});
|
|
123
|
+
await Promise.all(tasks);
|
|
124
|
+
return allResults;
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=convert.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"convert.js","sourceRoot":"","sources":["../src/convert.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const ora_1 = __importDefault(require("ora"));
|
|
9
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
10
|
+
const cli_js_1 = require("./cli.js");
|
|
11
|
+
const scan_js_1 = require("./scan.js");
|
|
12
|
+
const writer_js_1 = require("./writer.js");
|
|
13
|
+
const logger_js_1 = require("./logger.js");
|
|
14
|
+
async function main() {
|
|
15
|
+
const { inputDir, options } = (0, cli_js_1.parseArgs)(process.argv);
|
|
16
|
+
// ── Validate input directory ──────────────────────────────────────────────
|
|
17
|
+
if (!fs_1.default.existsSync(inputDir)) {
|
|
18
|
+
console.error(chalk_1.default.red(`✖ Input directory not found: ${inputDir}`));
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
const stat = fs_1.default.statSync(inputDir);
|
|
22
|
+
if (!stat.isDirectory()) {
|
|
23
|
+
console.error(chalk_1.default.red(`✖ Input path is not a directory: ${inputDir}`));
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
// ── Scan ──────────────────────────────────────────────────────────────────
|
|
27
|
+
const spinner = (0, ora_1.default)({ text: 'Scanning for images…', stream: process.stderr }).start();
|
|
28
|
+
const files = (0, scan_js_1.scanDirectory)(inputDir);
|
|
29
|
+
spinner.stop();
|
|
30
|
+
if (files.length === 0) {
|
|
31
|
+
console.log(chalk_1.default.yellow('⚠ No supported images found in: ') + inputDir);
|
|
32
|
+
console.log(chalk_1.default.dim(' Supported formats: .jpg, .jpeg, .png, .webp, .avif'));
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
35
|
+
// Each file may produce 1 or 2 output files (when --format both)
|
|
36
|
+
const totalOutputs = files.reduce((sum, f) => {
|
|
37
|
+
const isPassthrough = /\.(webp|avif)$/i.test(f.inputPath);
|
|
38
|
+
return sum + (isPassthrough ? 1 : options.format === 'both' ? 2 : 1);
|
|
39
|
+
}, 0);
|
|
40
|
+
const outputDir = options.inPlace
|
|
41
|
+
? inputDir
|
|
42
|
+
: (options.out ?? `${inputDir}-optimized`);
|
|
43
|
+
console.log();
|
|
44
|
+
console.log(chalk_1.default.bold('webpocalypse') +
|
|
45
|
+
chalk_1.default.dim(` v${getVersion()}`));
|
|
46
|
+
console.log(chalk_1.default.dim(' Input: ') + inputDir);
|
|
47
|
+
console.log(chalk_1.default.dim(' Output: ') + (options.inPlace ? chalk_1.default.yellow(outputDir + ' (in-place)') : outputDir));
|
|
48
|
+
console.log(chalk_1.default.dim(' Format: ') + options.format +
|
|
49
|
+
chalk_1.default.dim(' Quality: ') + (options.lossless ? 'lossless' : String(options.quality)) +
|
|
50
|
+
(options.maxWidth ? chalk_1.default.dim(' Max-W: ') + options.maxWidth : '') +
|
|
51
|
+
(options.maxHeight ? chalk_1.default.dim(' Max-H: ') + options.maxHeight : ''));
|
|
52
|
+
console.log(chalk_1.default.dim(` Files: ${files.length} image${files.length !== 1 ? 's' : ''} found → ${totalOutputs} output${totalOutputs !== 1 ? 's' : ''}`));
|
|
53
|
+
// ── Convert ───────────────────────────────────────────────────────────────
|
|
54
|
+
(0, logger_js_1.printTableHeader)();
|
|
55
|
+
let doneCount = 0;
|
|
56
|
+
const isTTY = Boolean(process.stderr.isTTY);
|
|
57
|
+
// Only spin in interactive terminals; in CI/piped output just print rows.
|
|
58
|
+
const progressSpinner = isTTY
|
|
59
|
+
? (0, ora_1.default)({ text: (0, logger_js_1.progressText)(0, totalOutputs, ''), stream: process.stderr }).start()
|
|
60
|
+
: null;
|
|
61
|
+
const allResults = [];
|
|
62
|
+
function onProgress(result) {
|
|
63
|
+
doneCount++;
|
|
64
|
+
if (progressSpinner) {
|
|
65
|
+
progressSpinner.clear();
|
|
66
|
+
}
|
|
67
|
+
(0, logger_js_1.printResultRow)(result);
|
|
68
|
+
allResults.push(result);
|
|
69
|
+
if (progressSpinner && doneCount < totalOutputs) {
|
|
70
|
+
progressSpinner.text = (0, logger_js_1.progressText)(doneCount, totalOutputs, result.outputRelativePath);
|
|
71
|
+
progressSpinner.render();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
const { results, outputDir: finalOutputDir } = await (0, writer_js_1.writeOutput)(inputDir, files, options, onProgress);
|
|
76
|
+
progressSpinner?.stop();
|
|
77
|
+
// Sort results by path for a stable display order
|
|
78
|
+
results.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
79
|
+
const summary = (0, logger_js_1.buildSummary)(results);
|
|
80
|
+
(0, logger_js_1.printSummary)(summary, finalOutputDir);
|
|
81
|
+
process.exit(summary.failureCount > 0 ? 1 : 0);
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
progressSpinner?.stop();
|
|
85
|
+
console.error();
|
|
86
|
+
console.error(chalk_1.default.red('✖ Fatal error: ') + (err instanceof Error ? err.message : String(err)));
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function getVersion() {
|
|
91
|
+
try {
|
|
92
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
93
|
+
const pkg = require('../package.json');
|
|
94
|
+
return pkg.version;
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return '?';
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
main();
|
|
101
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ConversionResult, ConversionSummary } from './types.js';
|
|
2
|
+
export declare function formatBytes(bytes: number): string;
|
|
3
|
+
export declare function printTableHeader(): void;
|
|
4
|
+
export declare function printResultRow(result: ConversionResult): void;
|
|
5
|
+
export declare function buildSummary(results: ConversionResult[]): ConversionSummary;
|
|
6
|
+
export declare function printSummary(summary: ConversionSummary, outputDir: string): void;
|
|
7
|
+
export declare function progressText(done: number, total: number, currentFile: string): string;
|
|
8
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAItE,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMjD;AAgCD,wBAAgB,gBAAgB,IAAI,IAAI,CAIvC;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAwC7D;AAID,wBAAgB,YAAY,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,iBAAiB,CA8B3E;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAwChF;AAID,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAIrF"}
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.formatBytes = formatBytes;
|
|
7
|
+
exports.printTableHeader = printTableHeader;
|
|
8
|
+
exports.printResultRow = printResultRow;
|
|
9
|
+
exports.buildSummary = buildSummary;
|
|
10
|
+
exports.printSummary = printSummary;
|
|
11
|
+
exports.progressText = progressText;
|
|
12
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
13
|
+
// ─── Formatting helpers ──────────────────────────────────────────────────────
|
|
14
|
+
function formatBytes(bytes) {
|
|
15
|
+
if (bytes === 0)
|
|
16
|
+
return '0 B';
|
|
17
|
+
const units = ['B', 'KB', 'MB', 'GB'];
|
|
18
|
+
const exp = Math.min(Math.floor(Math.log2(bytes) / 10), units.length - 1);
|
|
19
|
+
const value = bytes / Math.pow(1024, exp);
|
|
20
|
+
return `${value.toFixed(value < 10 ? 1 : 0)} ${units[exp]}`;
|
|
21
|
+
}
|
|
22
|
+
function pct(original, converted) {
|
|
23
|
+
if (original === 0)
|
|
24
|
+
return '—';
|
|
25
|
+
const saved = ((original - converted) / original) * 100;
|
|
26
|
+
return `${saved >= 0 ? saved.toFixed(0) : '+' + Math.abs(saved).toFixed(0)}%`;
|
|
27
|
+
}
|
|
28
|
+
function truncatePath(p, maxLen) {
|
|
29
|
+
if (p.length <= maxLen)
|
|
30
|
+
return p.padEnd(maxLen);
|
|
31
|
+
return '…' + p.slice(-(maxLen - 1));
|
|
32
|
+
}
|
|
33
|
+
// ─── Results table ───────────────────────────────────────────────────────────
|
|
34
|
+
const COL = {
|
|
35
|
+
file: 40,
|
|
36
|
+
original: 10,
|
|
37
|
+
converted: 11,
|
|
38
|
+
savings: 10,
|
|
39
|
+
};
|
|
40
|
+
const HEADER = chalk_1.default.bold(truncatePath('File', COL.file) + ' ' +
|
|
41
|
+
'Original'.padStart(COL.original) + ' ' +
|
|
42
|
+
'Converted'.padStart(COL.converted) + ' ' +
|
|
43
|
+
'Savings'.padStart(COL.savings));
|
|
44
|
+
const DIVIDER = '─'.repeat(COL.file + COL.original + COL.converted + COL.savings + 6);
|
|
45
|
+
function printTableHeader() {
|
|
46
|
+
console.log();
|
|
47
|
+
console.log(HEADER);
|
|
48
|
+
console.log(chalk_1.default.dim(DIVIDER));
|
|
49
|
+
}
|
|
50
|
+
function printResultRow(result) {
|
|
51
|
+
if (!result.success) {
|
|
52
|
+
console.log(chalk_1.default.red('✖ ') +
|
|
53
|
+
chalk_1.default.red(truncatePath(result.relativePath, COL.file - 2)) +
|
|
54
|
+
' ' +
|
|
55
|
+
chalk_1.default.dim(`Failed: ${result.error ?? 'unknown error'}`));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const displayPath = result.skipped
|
|
59
|
+
? result.relativePath + chalk_1.default.dim(' (copy)')
|
|
60
|
+
: result.outputRelativePath;
|
|
61
|
+
const fileCol = truncatePath(displayPath, COL.file);
|
|
62
|
+
const savings = pct(result.originalSize, result.convertedSize);
|
|
63
|
+
const savingsNum = result.originalSize
|
|
64
|
+
? ((result.originalSize - result.convertedSize) / result.originalSize) * 100
|
|
65
|
+
: 0;
|
|
66
|
+
let savingsStr;
|
|
67
|
+
if (result.skipped) {
|
|
68
|
+
savingsStr = chalk_1.default.dim('—');
|
|
69
|
+
}
|
|
70
|
+
else if (savingsNum >= 20) {
|
|
71
|
+
savingsStr = chalk_1.default.green(savings.padStart(COL.savings));
|
|
72
|
+
}
|
|
73
|
+
else if (savingsNum >= 0) {
|
|
74
|
+
savingsStr = chalk_1.default.yellow(savings.padStart(COL.savings));
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
savingsStr = chalk_1.default.red(savings.padStart(COL.savings));
|
|
78
|
+
}
|
|
79
|
+
console.log(chalk_1.default.dim(' ') +
|
|
80
|
+
fileCol.padEnd(COL.file) + ' ' +
|
|
81
|
+
chalk_1.default.dim(formatBytes(result.originalSize).padStart(COL.original)) + ' ' +
|
|
82
|
+
chalk_1.default.cyan(formatBytes(result.convertedSize).padStart(COL.converted)) + ' ' +
|
|
83
|
+
savingsStr);
|
|
84
|
+
}
|
|
85
|
+
// ─── Summary ─────────────────────────────────────────────────────────────────
|
|
86
|
+
function buildSummary(results) {
|
|
87
|
+
let totalOriginalBytes = 0;
|
|
88
|
+
let totalConvertedBytes = 0;
|
|
89
|
+
let successCount = 0;
|
|
90
|
+
let failureCount = 0;
|
|
91
|
+
let skippedCount = 0;
|
|
92
|
+
for (const r of results) {
|
|
93
|
+
if (!r.success) {
|
|
94
|
+
failureCount++;
|
|
95
|
+
totalOriginalBytes += r.originalSize;
|
|
96
|
+
}
|
|
97
|
+
else if (r.skipped) {
|
|
98
|
+
skippedCount++;
|
|
99
|
+
totalOriginalBytes += r.originalSize;
|
|
100
|
+
totalConvertedBytes += r.convertedSize;
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
successCount++;
|
|
104
|
+
totalOriginalBytes += r.originalSize;
|
|
105
|
+
totalConvertedBytes += r.convertedSize;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
totalFiles: results.length,
|
|
110
|
+
successCount,
|
|
111
|
+
failureCount,
|
|
112
|
+
skippedCount,
|
|
113
|
+
totalOriginalBytes,
|
|
114
|
+
totalConvertedBytes,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function printSummary(summary, outputDir) {
|
|
118
|
+
console.log(chalk_1.default.dim(DIVIDER));
|
|
119
|
+
console.log();
|
|
120
|
+
const { successCount, failureCount, skippedCount, totalOriginalBytes, totalConvertedBytes } = summary;
|
|
121
|
+
if (successCount > 0) {
|
|
122
|
+
const savings = totalOriginalBytes > 0
|
|
123
|
+
? Math.round(((totalOriginalBytes - totalConvertedBytes) / totalOriginalBytes) * 100)
|
|
124
|
+
: 0;
|
|
125
|
+
console.log(chalk_1.default.green('✔') + ' ' +
|
|
126
|
+
chalk_1.default.bold(`${successCount} file${successCount !== 1 ? 's' : ''} converted`));
|
|
127
|
+
console.log(chalk_1.default.green('✔') + ' ' +
|
|
128
|
+
`${formatBytes(totalOriginalBytes)} → ${formatBytes(totalConvertedBytes)} ` +
|
|
129
|
+
chalk_1.default.bold.green(`(${savings}% saved)`));
|
|
130
|
+
}
|
|
131
|
+
if (skippedCount > 0) {
|
|
132
|
+
console.log(chalk_1.default.dim('·') + ' ' +
|
|
133
|
+
chalk_1.default.dim(`${skippedCount} file${skippedCount !== 1 ? 's' : ''} copied as-is`));
|
|
134
|
+
}
|
|
135
|
+
if (failureCount > 0) {
|
|
136
|
+
console.log(chalk_1.default.red('✖') + ' ' +
|
|
137
|
+
chalk_1.default.red.bold(`${failureCount} file${failureCount !== 1 ? 's' : ''} failed`));
|
|
138
|
+
}
|
|
139
|
+
console.log();
|
|
140
|
+
console.log(chalk_1.default.dim('Output: ') + chalk_1.default.cyan(outputDir));
|
|
141
|
+
console.log();
|
|
142
|
+
}
|
|
143
|
+
// ─── Spinner helpers ─────────────────────────────────────────────────────────
|
|
144
|
+
function progressText(done, total, currentFile) {
|
|
145
|
+
const pctDone = total > 0 ? Math.round((done / total) * 100) : 0;
|
|
146
|
+
const truncated = currentFile.length > 40 ? '…' + currentFile.slice(-39) : currentFile;
|
|
147
|
+
return `[${done}/${total}] ${pctDone}% — ${truncated}`;
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"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"}
|
package/dist/scan.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { FileEntry } from './types.js';
|
|
2
|
+
export declare function isSupportedImage(filename: string): boolean;
|
|
3
|
+
export declare function isPassthrough(filename: string): boolean;
|
|
4
|
+
export declare function replaceExtension(filePath: string, newExt: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Recursively walk a directory and collect all supported image files.
|
|
7
|
+
* Returns paths relative to the given root so that directory structure
|
|
8
|
+
* can be replicated in the output.
|
|
9
|
+
*/
|
|
10
|
+
export declare function scanDirectory(rootDir: string): FileEntry[];
|
|
11
|
+
//# sourceMappingURL=scan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAU5C,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAG1D;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAGvD;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAIzE;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,CAoB1D"}
|
package/dist/scan.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.isSupportedImage = isSupportedImage;
|
|
7
|
+
exports.isPassthrough = isPassthrough;
|
|
8
|
+
exports.replaceExtension = replaceExtension;
|
|
9
|
+
exports.scanDirectory = scanDirectory;
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
// Extensions that will be re-encoded by sharp
|
|
13
|
+
const CONVERTIBLE_EXTENSIONS = new Set(['.jpg', '.jpeg', '.png']);
|
|
14
|
+
// Extensions that are already in a target format — copy as-is
|
|
15
|
+
const PASSTHROUGH_EXTENSIONS = new Set(['.webp', '.avif']);
|
|
16
|
+
const ALL_SUPPORTED = new Set([...CONVERTIBLE_EXTENSIONS, ...PASSTHROUGH_EXTENSIONS]);
|
|
17
|
+
function isSupportedImage(filename) {
|
|
18
|
+
const ext = filename.toLowerCase().slice(filename.lastIndexOf('.'));
|
|
19
|
+
return ALL_SUPPORTED.has(ext);
|
|
20
|
+
}
|
|
21
|
+
function isPassthrough(filename) {
|
|
22
|
+
const ext = filename.toLowerCase().slice(filename.lastIndexOf('.'));
|
|
23
|
+
return PASSTHROUGH_EXTENSIONS.has(ext);
|
|
24
|
+
}
|
|
25
|
+
function replaceExtension(filePath, newExt) {
|
|
26
|
+
const dotIndex = filePath.lastIndexOf('.');
|
|
27
|
+
const base = dotIndex !== -1 ? filePath.slice(0, dotIndex) : filePath;
|
|
28
|
+
return `${base}.${newExt}`;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Recursively walk a directory and collect all supported image files.
|
|
32
|
+
* Returns paths relative to the given root so that directory structure
|
|
33
|
+
* can be replicated in the output.
|
|
34
|
+
*/
|
|
35
|
+
function scanDirectory(rootDir) {
|
|
36
|
+
const entries = [];
|
|
37
|
+
function walk(dir) {
|
|
38
|
+
const items = fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
39
|
+
for (const item of items) {
|
|
40
|
+
const fullPath = path_1.default.join(dir, item.name);
|
|
41
|
+
if (item.isDirectory()) {
|
|
42
|
+
walk(fullPath);
|
|
43
|
+
}
|
|
44
|
+
else if (item.isFile() && isSupportedImage(item.name)) {
|
|
45
|
+
const relativePath = path_1.default.relative(rootDir, fullPath);
|
|
46
|
+
entries.push({ inputPath: fullPath, relativePath });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
walk(rootDir);
|
|
51
|
+
return entries;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=scan.js.map
|
package/dist/scan.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scan.js","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":";;;;;AAYA,4CAGC;AAED,sCAGC;AAED,4CAIC;AAOD,sCAoBC;AArDD,4CAAoB;AACpB,gDAAwB;AAGxB,8CAA8C;AAC9C,MAAM,sBAAsB,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;AAElE,8DAA8D;AAC9D,MAAM,sBAAsB,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;AAE3D,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,sBAAsB,EAAE,GAAG,sBAAsB,CAAC,CAAC,CAAC;AAEtF,SAAgB,gBAAgB,CAAC,QAAgB;IAC/C,MAAM,GAAG,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;IACpE,OAAO,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAChC,CAAC;AAED,SAAgB,aAAa,CAAC,QAAgB;IAC5C,MAAM,GAAG,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;IACpE,OAAO,sBAAsB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AACzC,CAAC;AAED,SAAgB,gBAAgB,CAAC,QAAgB,EAAE,MAAc;IAC/D,MAAM,QAAQ,GAAG,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAC3C,MAAM,IAAI,GAAG,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IACtE,OAAO,GAAG,IAAI,IAAI,MAAM,EAAE,CAAC;AAC7B,CAAC;AAED;;;;GAIG;AACH,SAAgB,aAAa,CAAC,OAAe;IAC3C,MAAM,OAAO,GAAgB,EAAE,CAAC;IAEhC,SAAS,IAAI,CAAC,GAAW;QACvB,MAAM,KAAK,GAAG,YAAE,CAAC,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAE3D,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,QAAQ,GAAG,cAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;YAE3C,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;gBACvB,IAAI,CAAC,QAAQ,CAAC,CAAC;YACjB,CAAC;iBAAM,IAAI,IAAI,CAAC,MAAM,EAAE,IAAI,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBACxD,MAAM,YAAY,GAAG,cAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;gBACtD,OAAO,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC,CAAC;YACtD,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,CAAC,OAAO,CAAC,CAAC;IACd,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export type OutputFormat = 'webp' | 'avif' | 'both';
|
|
2
|
+
export interface CliOptions {
|
|
3
|
+
format: OutputFormat;
|
|
4
|
+
quality: number;
|
|
5
|
+
lossless: boolean;
|
|
6
|
+
maxWidth?: number;
|
|
7
|
+
maxHeight?: number;
|
|
8
|
+
out?: string;
|
|
9
|
+
inPlace: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface FileEntry {
|
|
12
|
+
inputPath: string;
|
|
13
|
+
relativePath: string;
|
|
14
|
+
}
|
|
15
|
+
export interface ConversionResult {
|
|
16
|
+
inputPath: string;
|
|
17
|
+
relativePath: string;
|
|
18
|
+
outputPath: string;
|
|
19
|
+
outputRelativePath: string;
|
|
20
|
+
originalSize: number;
|
|
21
|
+
convertedSize: number;
|
|
22
|
+
success: boolean;
|
|
23
|
+
skipped: boolean;
|
|
24
|
+
error?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface ConversionSummary {
|
|
27
|
+
totalFiles: number;
|
|
28
|
+
successCount: number;
|
|
29
|
+
failureCount: number;
|
|
30
|
+
skippedCount: number;
|
|
31
|
+
totalOriginalBytes: number;
|
|
32
|
+
totalConvertedBytes: number;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAEpD,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,YAAY,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,mBAAmB,EAAE,MAAM,CAAC;CAC7B"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/dist/writer.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { CliOptions, ConversionResult } from './types.js';
|
|
2
|
+
import type { FileEntry } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Determine the final output directory from CLI options.
|
|
5
|
+
* - `--out <path>` wins if provided
|
|
6
|
+
* - `--in-place` uses a temp dir (caller must finalize after success)
|
|
7
|
+
* - Default: `<inputDir>-optimized`
|
|
8
|
+
*/
|
|
9
|
+
export declare function resolveOutputDir(inputDir: string, options: CliOptions): string;
|
|
10
|
+
/**
|
|
11
|
+
* Run conversion and write output.
|
|
12
|
+
*
|
|
13
|
+
* For --in-place:
|
|
14
|
+
* 1. Write everything to a temporary directory.
|
|
15
|
+
* 2. Only if ALL conversions succeed, atomically replace the source directory.
|
|
16
|
+
* 3. On any failure, leave the source untouched and clean up the temp dir.
|
|
17
|
+
*
|
|
18
|
+
* For normal output:
|
|
19
|
+
* Write directly to the resolved output directory.
|
|
20
|
+
*/
|
|
21
|
+
export declare function writeOutput(inputDir: string, files: FileEntry[], options: CliOptions, onProgress: (result: ConversionResult) => void): Promise<{
|
|
22
|
+
results: ConversionResult[];
|
|
23
|
+
outputDir: string;
|
|
24
|
+
}>;
|
|
25
|
+
//# sourceMappingURL=writer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"writer.d.ts","sourceRoot":"","sources":["../src/writer.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE/D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,MAAM,CAI9E;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,SAAS,EAAE,EAClB,OAAO,EAAE,UAAU,EACnB,UAAU,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,GAC7C,OAAO,CAAC;IAAE,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAS7D"}
|
package/dist/writer.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.resolveOutputDir = resolveOutputDir;
|
|
7
|
+
exports.writeOutput = writeOutput;
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const os_1 = __importDefault(require("os"));
|
|
10
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
11
|
+
const convert_js_1 = require("./convert.js");
|
|
12
|
+
/**
|
|
13
|
+
* Determine the final output directory from CLI options.
|
|
14
|
+
* - `--out <path>` wins if provided
|
|
15
|
+
* - `--in-place` uses a temp dir (caller must finalize after success)
|
|
16
|
+
* - Default: `<inputDir>-optimized`
|
|
17
|
+
*/
|
|
18
|
+
function resolveOutputDir(inputDir, options) {
|
|
19
|
+
if (options.out)
|
|
20
|
+
return options.out;
|
|
21
|
+
if (options.inPlace)
|
|
22
|
+
return ''; // placeholder; writer uses a temp dir internally
|
|
23
|
+
return `${inputDir}-optimized`;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Run conversion and write output.
|
|
27
|
+
*
|
|
28
|
+
* For --in-place:
|
|
29
|
+
* 1. Write everything to a temporary directory.
|
|
30
|
+
* 2. Only if ALL conversions succeed, atomically replace the source directory.
|
|
31
|
+
* 3. On any failure, leave the source untouched and clean up the temp dir.
|
|
32
|
+
*
|
|
33
|
+
* For normal output:
|
|
34
|
+
* Write directly to the resolved output directory.
|
|
35
|
+
*/
|
|
36
|
+
async function writeOutput(inputDir, files, options, onProgress) {
|
|
37
|
+
if (options.inPlace) {
|
|
38
|
+
return runInPlace(inputDir, files, options, onProgress);
|
|
39
|
+
}
|
|
40
|
+
const outputDir = resolveOutputDir(inputDir, options);
|
|
41
|
+
await fs_extra_1.default.ensureDir(outputDir);
|
|
42
|
+
const results = await (0, convert_js_1.convertFiles)(files, outputDir, options, onProgress);
|
|
43
|
+
return { results, outputDir };
|
|
44
|
+
}
|
|
45
|
+
async function runInPlace(inputDir, files, options, onProgress) {
|
|
46
|
+
const tempDir = path_1.default.join(os_1.default.tmpdir(), `webpocalypse-${process.pid}-${Date.now()}`);
|
|
47
|
+
try {
|
|
48
|
+
await fs_extra_1.default.ensureDir(tempDir);
|
|
49
|
+
const results = await (0, convert_js_1.convertFiles)(files, tempDir, options, onProgress);
|
|
50
|
+
const hasFailures = results.some((r) => !r.success);
|
|
51
|
+
if (hasFailures) {
|
|
52
|
+
// Leave source untouched; clean up temp
|
|
53
|
+
await fs_extra_1.default.remove(tempDir);
|
|
54
|
+
return { results, outputDir: inputDir };
|
|
55
|
+
}
|
|
56
|
+
// All succeeded — replace source directory atomically
|
|
57
|
+
const backupDir = `${inputDir}.bak-${Date.now()}`;
|
|
58
|
+
await fs_extra_1.default.move(inputDir, backupDir);
|
|
59
|
+
try {
|
|
60
|
+
await fs_extra_1.default.move(tempDir, inputDir);
|
|
61
|
+
await fs_extra_1.default.remove(backupDir);
|
|
62
|
+
}
|
|
63
|
+
catch (moveErr) {
|
|
64
|
+
// Restore from backup on failure
|
|
65
|
+
await fs_extra_1.default.move(backupDir, inputDir);
|
|
66
|
+
await fs_extra_1.default.remove(tempDir).catch(() => undefined);
|
|
67
|
+
throw moveErr;
|
|
68
|
+
}
|
|
69
|
+
return { results, outputDir: inputDir };
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
await fs_extra_1.default.remove(tempDir).catch(() => undefined);
|
|
73
|
+
throw err;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=writer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"writer.js","sourceRoot":"","sources":["../src/writer.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "webpocalypse",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI tool for batch image conversion to WebP/AVIF with quality control and directory structure preservation",
|
|
5
|
+
"keywords": ["image", "conversion", "webp", "avif", "optimization", "cli"],
|
|
6
|
+
"author": "",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"bin": {
|
|
10
|
+
"webpocalypse": "dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"files": ["dist"],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=18.0.0"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"dev": "ts-node src/index.ts",
|
|
19
|
+
"prepublishOnly": "npm run build"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"chalk": "^5.3.0",
|
|
23
|
+
"commander": "^12.1.0",
|
|
24
|
+
"fs-extra": "^11.2.0",
|
|
25
|
+
"ora": "^8.1.0",
|
|
26
|
+
"p-limit": "^6.1.0",
|
|
27
|
+
"sharp": "^0.33.4"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/fs-extra": "^11.0.4",
|
|
31
|
+
"@types/node": "^20.14.0",
|
|
32
|
+
"typescript": "^5.4.5"
|
|
33
|
+
}
|
|
34
|
+
}
|