@valdiszz53/image-optimizer-cli 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vladislav Tregubov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,415 @@
1
+ # Image Optimizer CLI
2
+
3
+ A fast and flexible CLI tool for optimizing images, converting formats and resizing images based on filename instructions or project configuration.
4
+
5
+ - [Features](#features)
6
+ - [Installation](#installation)
7
+ - [Usage](#usage)
8
+ - [Configuration](#configuration)
9
+ - [CLI Options](#cli-options)
10
+ - [Supported Formats](#supported-formats)
11
+ - [Filename Instructions](#filename-instructions)
12
+ - [SVG Support](#svg-support)
13
+ - [Configuration Priority](#configuration-priority)
14
+ - [Folder Structure](#folder-structure)
15
+ - [Processing Statistics](#processing-statistics)
16
+ - [Example Workflow](#example-workflow)
17
+ - [Requirements](#requirements)
18
+ - [License](#license)
19
+
20
+ ## Features
21
+
22
+ - ๐Ÿš€ Optimize JPEG, PNG, WebP, AVIF and SVG images
23
+ - ๐Ÿ“ Recursive directory scanning
24
+ - ๐Ÿ“‚ Preserve folder structure
25
+ - ๐Ÿ–ผ Convert images to multiple formats
26
+ - ๐Ÿ“ Resize images directly from filename
27
+ - ๐Ÿ“‰ Configurable compression quality
28
+ - โš™๏ธ Project configuration file
29
+ - ๐Ÿ’ป CLI arguments
30
+ - ๐Ÿ” Dry-run mode
31
+ - ๐Ÿงน Clean output directory
32
+ - ๐Ÿ“Š Processing statistics
33
+ - โšก Powered by Sharp
34
+
35
+ ## Installation
36
+
37
+ ```bash
38
+ # Global
39
+ npm install -g image-optimizer-cli
40
+
41
+ # Using npx
42
+ npx image-optimizer-cli ./images
43
+
44
+ # Local
45
+ npm install image-optimizer-cli
46
+ # or
47
+ npm install
48
+ npm link
49
+ ```
50
+
51
+ ## Usage
52
+
53
+ ```bash
54
+ image-optimizer [input-directory] [options]
55
+ ```
56
+
57
+ The input directory can be provided either:
58
+
59
+ - as the first CLI argument;
60
+ - in the configuration file (`inputDir`).
61
+
62
+ ### Examples
63
+
64
+ Optimize images:
65
+
66
+ ```bash
67
+ image-optimizer ./images
68
+ ```
69
+
70
+ Save into another directory:
71
+
72
+ ```bash
73
+ image-optimizer ./images \
74
+ --output ./dist
75
+ ```
76
+
77
+ Convert every image to WebP:
78
+
79
+ ```bash
80
+ image-optimizer ./images \
81
+ --format webp
82
+ ```
83
+
84
+ Change quality:
85
+
86
+ ```bash
87
+ image-optimizer ./images \
88
+ --quality 70
89
+ ```
90
+
91
+ Dry run:
92
+
93
+ ```bash
94
+ image-optimizer ./images \
95
+ --dry-run
96
+ ```
97
+
98
+ Use custom configuration:
99
+
100
+ ```bash
101
+ image-optimizer \
102
+ --config ./configs/image-optimizer.config.ts
103
+ ```
104
+
105
+ ## Configuration
106
+
107
+ Create a file:
108
+
109
+ ```text
110
+ image-optimizer.config.ts
111
+ ```
112
+
113
+ Example:
114
+
115
+ ```ts
116
+ import { defineConfig } from 'image-optimizer'
117
+
118
+ export default defineConfig({
119
+ inputDir: './assets',
120
+ outputDir: './public/assets',
121
+ quality: 80,
122
+ clean: true,
123
+ outputs: {
124
+ jpg: ['jpeg', 'webp'],
125
+ jpeg: ['jpeg', 'webp'],
126
+ png: ['png', 'webp'],
127
+ webp: ['webp'],
128
+ avif: ['avif'],
129
+ svg: ['svg'],
130
+ },
131
+ })
132
+ ```
133
+
134
+ With this configuration:
135
+
136
+ ```text
137
+ logo.png
138
+ ```
139
+
140
+ becomes
141
+
142
+ ```text
143
+ logo.png
144
+ logo.webp
145
+ ```
146
+
147
+ without adding any filename instructions.
148
+
149
+ ## CLI Options
150
+
151
+ | Option | Description |
152
+ | --------------- | ---------------------------------------- |
153
+ | `-c, --config` | Path to configuration file |
154
+ | `-o, --output` | Output directory |
155
+ | `-q, --quality` | Compression quality (1โ€“100) |
156
+ | `-f, --format` | Force output format |
157
+ | `--dry-run` | Show processing without writing files |
158
+ | `--clean` | Clean output directory before processing |
159
+
160
+ ## Supported Formats
161
+
162
+ - JPG
163
+ - JPEG
164
+ - PNG
165
+ - WebP
166
+ - AVIF
167
+ - SVG
168
+ - TIFF
169
+
170
+ ## Filename Instructions
171
+
172
+ Filename instructions always override both CLI arguments and configuration.
173
+
174
+ Example:
175
+
176
+ ```text
177
+ logo[120].png
178
+ โ†“
179
+ logo.png
180
+ ```
181
+
182
+ ### Resize
183
+
184
+ | Instruction | Result |
185
+ | ------------------ | ---------------------------- |
186
+ | `logo[120].png` | width = 120px |
187
+ | `logo[120x80].png` | width = 120px, height = 80px |
188
+ | `logo[w=120].png` | width = 120px |
189
+ | `logo[h=80].png` | height = 80px |
190
+ | `logo[50%].png` | resize to 50% |
191
+ | `logo[2x].png` | scale ร—2 |
192
+
193
+ ### Format Conversion
194
+
195
+ Convert:
196
+
197
+ ```text
198
+ photo[webp].png
199
+ โ†“
200
+ photo.webp
201
+ ```
202
+
203
+ Keep original:
204
+
205
+ ```text
206
+ photo[+webp].png
207
+ โ†“
208
+ photo.png
209
+ photo.webp
210
+ ```
211
+
212
+ Generate multiple formats:
213
+
214
+ ```text
215
+ photo[+webp,+avif].png
216
+ โ†“
217
+ photo.png
218
+ photo.webp
219
+ photo.avif
220
+ ```
221
+
222
+ Resize + convert:
223
+
224
+ ```text
225
+ photo[320.webp].png
226
+ โ†“
227
+ photo.webp # width 320px
228
+ ```
229
+
230
+ Resize + keep original:
231
+
232
+ ```text
233
+ photo[320.+webp].png
234
+ โ†“
235
+ photo.png
236
+ photo.webp
237
+ ```
238
+
239
+ Both images will be resized.
240
+
241
+ Per-image quality:
242
+
243
+ ```text
244
+ photo[q=60].jpg
245
+ or
246
+ photo[quality=60].jpg
247
+ ```
248
+
249
+ ## SVG Support
250
+
251
+ SVG files are never rasterized.
252
+
253
+ Supported:
254
+
255
+ ```text
256
+ logo[24].svg
257
+ โ†“
258
+ <svg width="24px"/>
259
+ ```
260
+
261
+ ```text
262
+ logo[24x24].svg
263
+ โ†“
264
+ <svg
265
+ width="24px"
266
+ height="24px"/>
267
+ ```
268
+
269
+ ## Configuration Priority
270
+
271
+ Image Optimizer resolves settings using the following priority:
272
+
273
+ 1. **Filename instructions**
274
+ 2. **CLI arguments**
275
+ 3. **Configuration file**
276
+ 4. **Default values**
277
+
278
+ Example:
279
+
280
+ Configuration:
281
+
282
+ ```ts
283
+ outputs: {
284
+ png: ['png', 'webp']
285
+ }
286
+ ```
287
+
288
+ Image:
289
+
290
+ ```text
291
+ logo.png
292
+ โ†“
293
+ logo.png
294
+ logo.webp
295
+ ```
296
+
297
+ But:
298
+
299
+ ```text
300
+ logo[avif].png
301
+ โ†“
302
+ logo.avif
303
+ ```
304
+
305
+ because filename instructions always have the highest priority.
306
+
307
+ ## Folder Structure
308
+
309
+ Input:
310
+
311
+ ```text
312
+ images
313
+ โ”œโ”€โ”€ logo[120].png
314
+ โ”œโ”€โ”€ hero.jpg
315
+ โ””โ”€โ”€ icons
316
+ โ”œโ”€โ”€ menu[32].svg
317
+ โ””โ”€โ”€ close.png
318
+ ```
319
+
320
+ Configuration:
321
+
322
+ ```ts
323
+ export default defineConfig({
324
+ outputDir: './dist',
325
+ outputs: {
326
+ png: ['png', 'webp'],
327
+ jpg: ['jpeg'],
328
+ },
329
+ })
330
+ ```
331
+
332
+ Output:
333
+
334
+ ```text
335
+ dist
336
+ โ”œโ”€โ”€ logo.png
337
+ โ”œโ”€โ”€ logo.webp
338
+ โ”œโ”€โ”€ hero.jpeg
339
+ โ””โ”€โ”€ icons
340
+ โ”œโ”€โ”€ menu.svg
341
+ โ”œโ”€โ”€ close.png
342
+ โ””โ”€โ”€ close.webp
343
+ ```
344
+
345
+ The directory structure is always preserved.
346
+
347
+ ## Processing Statistics
348
+
349
+ Example output:
350
+
351
+ ```text
352
+ Found 42 image(s)
353
+
354
+ โœ“ logo.png 420 KB โ†’ 95 KB (-77%)
355
+ โœ“ logo.webp 420 KB โ†’ 62 KB (-85%)
356
+ โœ“ hero.jpeg 1.8 MB โ†’ 640 KB (-64%)
357
+
358
+ Summary
359
+ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
360
+ Processed: 63
361
+ Skipped: 0
362
+ Before: 24.7 MB
363
+ After: 10.2 MB
364
+ Saved: 14.5 MB
365
+ ```
366
+
367
+ ## Example Workflow
368
+
369
+ Configuration:
370
+
371
+ ```ts
372
+ export default defineConfig({
373
+ inputDir: './assets',
374
+ outputDir: './public/assets',
375
+ outputs: {
376
+ png: ['png', 'webp'],
377
+ jpg: ['jpeg', 'webp'],
378
+ },
379
+ })
380
+ ```
381
+
382
+ Assets:
383
+
384
+ ```text
385
+ assets
386
+ โ”œโ”€โ”€ logo.png
387
+ โ”œโ”€โ”€ hero.jpg
388
+ โ”œโ”€โ”€ avatar[50%].png
389
+ โ”œโ”€โ”€ icon[32].svg
390
+ โ”œโ”€โ”€ banner[+avif].jpg
391
+ ```
392
+
393
+ Result:
394
+
395
+ ```text
396
+ public/assets
397
+ โ”œโ”€โ”€ logo.png
398
+ โ”œโ”€โ”€ logo.webp
399
+ โ”œโ”€โ”€ hero.jpeg
400
+ โ”œโ”€โ”€ hero.webp
401
+ โ”œโ”€โ”€ avatar.png
402
+ โ”œโ”€โ”€ avatar.webp
403
+ โ”œโ”€โ”€ icon.svg
404
+ โ”œโ”€โ”€ banner.jpg
405
+ โ””โ”€โ”€ banner.avif
406
+ ```
407
+
408
+ ## Requirements
409
+
410
+ - Node.js 18+
411
+ - Sharp 0.33+
412
+
413
+ ## License
414
+
415
+ [MIT](LICENSE)
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+ import chalk from 'chalk';
3
+ import { Command } from 'commander';
4
+ import { loadConfig, mergeConfigWithCliOptions } from './config.js';
5
+ import { optimizeDirectory } from './index.js';
6
+ const program = new Command();
7
+ program
8
+ .name('image-optimizer')
9
+ .description('Optimize images from a directory')
10
+ .argument('[input]', 'Input directory with images')
11
+ .option('-c, --config <path>', 'Path to config file')
12
+ .option('-o, --output <directory>', 'Output directory')
13
+ .option('-q, --quality <number>', 'Image quality from 1 to 100')
14
+ .option('-f, --format <format>', 'Convert all images to format: jpeg, png, webp, avif')
15
+ .option('--dry-run', 'Show what will be processed without writing files')
16
+ .option('--clean', 'Clean output directory before optimization')
17
+ .parse(process.argv);
18
+ const options = program.opts();
19
+ const input = program.args[0];
20
+ try {
21
+ const config = await loadConfig(options.config);
22
+ const finalOptions = mergeConfigWithCliOptions({
23
+ config,
24
+ cliOptions: {
25
+ inputDir: input,
26
+ outputDir: options.output,
27
+ quality: options.quality ? Number(options.quality) : undefined,
28
+ format: options.format,
29
+ dryRun: options.dryRun,
30
+ clean: options.clean,
31
+ },
32
+ });
33
+ if (!finalOptions.inputDir) {
34
+ throw new Error('Input directory is required. Pass it as CLI argument or define inputDir in config.');
35
+ }
36
+ await optimizeDirectory(finalOptions);
37
+ }
38
+ catch (error) {
39
+ const message = error instanceof Error ? error.message : String(error);
40
+ console.error(chalk.red('Error:'), message);
41
+ process.exit(1);
42
+ }
43
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,UAAU,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAA;AACnE,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAG9C,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAA;AAE7B,OAAO;KACF,IAAI,CAAC,iBAAiB,CAAC;KACvB,WAAW,CAAC,kCAAkC,CAAC;KAC/C,QAAQ,CAAC,SAAS,EAAE,6BAA6B,CAAC;KAClD,MAAM,CAAC,qBAAqB,EAAE,qBAAqB,CAAC;KACpD,MAAM,CAAC,0BAA0B,EAAE,kBAAkB,CAAC;KACtD,MAAM,CAAC,wBAAwB,EAAE,6BAA6B,CAAC;KAC/D,MAAM,CACH,uBAAuB,EACvB,qDAAqD,CACxD;KACA,MAAM,CAAC,WAAW,EAAE,mDAAmD,CAAC;KACxE,MAAM,CAAC,SAAS,EAAE,4CAA4C,CAAC;KAC/D,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;AAExB,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAOxB,CAAA;AAEJ,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AAE7B,IAAI,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;IAE/C,MAAM,YAAY,GAAG,yBAAyB,CAAC;QAC3C,MAAM;QACN,UAAU,EAAE;YACR,QAAQ,EAAE,KAAK;YACf,SAAS,EAAE,OAAO,CAAC,MAAM;YACzB,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS;YAC9D,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,KAAK,EAAE,OAAO,CAAC,KAAK;SACvB;KACJ,CAAC,CAAA;IAEF,IAAI,CAAC,YAAY,CAAC,QAAQ,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CACX,oFAAoF,CACvF,CAAA;IACL,CAAC;IAED,MAAM,iBAAiB,CAAC,YAAY,CAAC,CAAA;AACzC,CAAC;AAAC,OAAO,KAAK,EAAE,CAAC;IACb,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IAEtE,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAA;IAC3C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACnB,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { ImageOptimizerConfig } from './types.js';
2
+ export declare function defineConfig(config: ImageOptimizerConfig): ImageOptimizerConfig;
3
+ export declare function loadConfig(configPath?: string): Promise<ImageOptimizerConfig>;
4
+ export declare function mergeConfigWithCliOptions({ config, cliOptions, }: {
5
+ config: ImageOptimizerConfig;
6
+ cliOptions: ImageOptimizerConfig;
7
+ }): ImageOptimizerConfig;
package/dist/config.js ADDED
@@ -0,0 +1,46 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { pathToFileURL } from 'node:url';
4
+ const DEFAULT_CONFIG_FILES = [
5
+ 'image-optimizer.config.ts',
6
+ 'image-optimizer.config.js',
7
+ 'image-optimizer.config.mjs',
8
+ ];
9
+ export function defineConfig(config) {
10
+ return config;
11
+ }
12
+ export async function loadConfig(configPath) {
13
+ const resolvedConfigPath = configPath
14
+ ? path.resolve(configPath)
15
+ : await findConfigFile();
16
+ if (!resolvedConfigPath) {
17
+ return {};
18
+ }
19
+ const moduleUrl = pathToFileURL(resolvedConfigPath).href;
20
+ const configModule = await import(moduleUrl);
21
+ return configModule.default ?? configModule;
22
+ }
23
+ async function findConfigFile() {
24
+ for (const file of DEFAULT_CONFIG_FILES) {
25
+ const fullPath = path.resolve(file);
26
+ try {
27
+ await fs.access(fullPath);
28
+ return fullPath;
29
+ }
30
+ catch {
31
+ // ignore
32
+ }
33
+ }
34
+ return null;
35
+ }
36
+ export function mergeConfigWithCliOptions({ config, cliOptions, }) {
37
+ return {
38
+ ...config,
39
+ ...removeUndefined(cliOptions),
40
+ outputs: cliOptions.outputs ?? config.outputs,
41
+ };
42
+ }
43
+ function removeUndefined(object) {
44
+ return Object.fromEntries(Object.entries(object).filter(([, value]) => value !== undefined));
45
+ }
46
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAA;AACjC,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAGxC,MAAM,oBAAoB,GAAG;IACzB,2BAA2B;IAC3B,2BAA2B;IAC3B,4BAA4B;CAC/B,CAAA;AAED,MAAM,UAAU,YAAY,CACxB,MAA4B;IAE5B,OAAO,MAAM,CAAA;AACjB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAC5B,UAAmB;IAEnB,MAAM,kBAAkB,GAAG,UAAU;QACjC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC;QAC1B,CAAC,CAAC,MAAM,cAAc,EAAE,CAAA;IAE5B,IAAI,CAAC,kBAAkB,EAAE,CAAC;QACtB,OAAO,EAAE,CAAA;IACb,CAAC;IAED,MAAM,SAAS,GAAG,aAAa,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAA;IACxD,MAAM,YAAY,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,CAAA;IAE5C,OAAO,YAAY,CAAC,OAAO,IAAI,YAAY,CAAA;AAC/C,CAAC;AAED,KAAK,UAAU,cAAc;IACzB,KAAK,MAAM,IAAI,IAAI,oBAAoB,EAAE,CAAC;QACtC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QAEnC,IAAI,CAAC;YACD,MAAM,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;YACzB,OAAO,QAAQ,CAAA;QACnB,CAAC;QAAC,MAAM,CAAC;YACL,SAAS;QACb,CAAC;IACL,CAAC;IAED,OAAO,IAAI,CAAA;AACf,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,EACtC,MAAM,EACN,UAAU,GAIb;IACG,OAAO;QACH,GAAG,MAAM;QACT,GAAG,eAAe,CAAC,UAAU,CAAC;QAC9B,OAAO,EAAE,UAAU,CAAC,OAAO,IAAI,MAAM,CAAC,OAAO;KAChD,CAAA;AACL,CAAC;AAED,SAAS,eAAe,CAAmB,MAAS;IAChD,OAAO,MAAM,CAAC,WAAW,CACrB,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC,CACtD,CAAA;AACnB,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { defineConfig } from './config.js';
2
+ export { optimizeDirectory } from './optimizer.js';
3
+ export type { ImageFormat, ImageInstructions, ImageOptimizerConfig, ImageOutput, InputImageFormat, OptimizeOptions, OutputImageFormat, OutputsConfig, OutputTask, } from './types.js';
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { defineConfig } from './config.js';
2
+ export { optimizeDirectory } from './optimizer.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA"}
@@ -0,0 +1,2 @@
1
+ import type { OptimizeOptions } from './types.js';
2
+ export declare function optimizeDirectory({ inputDir, outputDir, quality, format, outputs, dryRun, clean, }: OptimizeOptions): Promise<void>;
@@ -0,0 +1,247 @@
1
+ import chalk from 'chalk';
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import sharp from 'sharp';
5
+ import { scanImages } from './scanner.js';
6
+ import { cleanDirectory, ensureDirectory, formatBytes, getFileSize, getInputFormat, getOutputPath, normalizeFormat, normalizeOutputFormat, parseImageInstructions, pathExists, validateFormat, validateQuality, } from './utils.js';
7
+ export async function optimizeDirectory({ inputDir, outputDir, quality = 80, format, outputs, dryRun = false, clean = false, }) {
8
+ if (!inputDir) {
9
+ throw new Error('inputDir is required');
10
+ }
11
+ validateQuality(quality);
12
+ validateFormat(format);
13
+ const absoluteInputDir = path.resolve(inputDir);
14
+ const absoluteOutputDir = outputDir
15
+ ? path.resolve(outputDir)
16
+ : absoluteInputDir;
17
+ if (!(await pathExists(absoluteInputDir))) {
18
+ throw new Error(`Input directory does not exist: ${absoluteInputDir}`);
19
+ }
20
+ if (clean && outputDir && !dryRun) {
21
+ await cleanDirectory(absoluteOutputDir);
22
+ }
23
+ const files = await scanImages(absoluteInputDir);
24
+ if (files.length === 0) {
25
+ console.log(chalk.yellow('No images found'));
26
+ return;
27
+ }
28
+ let totalBefore = 0;
29
+ let totalAfter = 0;
30
+ let processed = 0;
31
+ let skipped = 0;
32
+ console.log(chalk.cyan(`Found ${files.length} image(s)`));
33
+ for (const file of files) {
34
+ try {
35
+ const instructions = parseImageInstructions(file);
36
+ const beforeSize = await getFileSize(file);
37
+ const outputTasks = createOutputTasks({
38
+ inputDir: absoluteInputDir,
39
+ outputDir: absoluteOutputDir,
40
+ file,
41
+ cliFormat: format,
42
+ configOutputs: outputs,
43
+ instructions,
44
+ });
45
+ if (dryRun) {
46
+ for (const task of outputTasks) {
47
+ console.log(chalk.gray(`[dry-run] ${file} -> ${task.outputPath}`));
48
+ }
49
+ continue;
50
+ }
51
+ for (const task of outputTasks) {
52
+ await ensureDirectory(path.dirname(task.outputPath));
53
+ await processImage({
54
+ file,
55
+ outputPath: task.outputPath,
56
+ outputFormat: task.format,
57
+ instructions,
58
+ quality: instructions.quality || quality,
59
+ });
60
+ const afterSize = await getFileSize(task.outputPath);
61
+ totalBefore += beforeSize;
62
+ totalAfter += afterSize;
63
+ processed++;
64
+ const percent = Math.round((1 - afterSize / beforeSize) * 100);
65
+ console.log(chalk.green('โœ“'), path.relative(process.cwd(), task.outputPath), chalk.gray(`${formatBytes(beforeSize)} โ†’ ${formatBytes(afterSize)}`), percent >= 0
66
+ ? chalk.green(`-${percent}%`)
67
+ : chalk.yellow(`+${Math.abs(percent)}%`));
68
+ }
69
+ }
70
+ catch (error) {
71
+ skipped++;
72
+ const message = error instanceof Error ? error.message : String(error);
73
+ console.log(chalk.red('โœ—'), path.relative(process.cwd(), file), chalk.gray(message));
74
+ }
75
+ }
76
+ console.log('');
77
+ console.log(chalk.cyan('Summary'));
78
+ console.log(chalk.gray('โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€'));
79
+ console.log(`Processed: ${processed}`);
80
+ console.log(`Skipped: ${skipped}`);
81
+ console.log(`Before: ${formatBytes(totalBefore)}`);
82
+ console.log(`After: ${formatBytes(totalAfter)}`);
83
+ console.log(`Saved: ${formatBytes(totalBefore - totalAfter)}`);
84
+ }
85
+ function createOutputTasks({ inputDir, outputDir, file, cliFormat, configOutputs, instructions, }) {
86
+ const tasks = [];
87
+ const hasKeepOriginalOutput = instructions.outputs.some(output => output.keepOriginal);
88
+ if (hasKeepOriginalOutput) {
89
+ tasks.push({
90
+ format: null,
91
+ outputPath: getOutputPath({
92
+ inputDir,
93
+ outputDir,
94
+ filePath: file,
95
+ format: null,
96
+ }),
97
+ });
98
+ }
99
+ if (instructions.outputs.length > 0) {
100
+ for (const output of instructions.outputs) {
101
+ tasks.push({
102
+ format: output.format,
103
+ outputPath: getOutputPath({
104
+ inputDir,
105
+ outputDir,
106
+ filePath: file,
107
+ format: output.format,
108
+ }),
109
+ });
110
+ }
111
+ return removeDuplicateTasks(tasks);
112
+ }
113
+ if (cliFormat) {
114
+ const normalizedFormat = normalizeFormat(cliFormat);
115
+ tasks.push({
116
+ format: normalizedFormat ?? null,
117
+ outputPath: getOutputPath({
118
+ inputDir,
119
+ outputDir,
120
+ filePath: file,
121
+ format: normalizedFormat,
122
+ }),
123
+ });
124
+ return removeDuplicateTasks(tasks);
125
+ }
126
+ const inputFormat = getInputFormat(file);
127
+ const configuredOutputs = configOutputs?.[inputFormat];
128
+ if (configuredOutputs?.length) {
129
+ for (const outputFormat of configuredOutputs) {
130
+ const normalizedOutputFormat = normalizeOutputFormat(outputFormat);
131
+ tasks.push({
132
+ format: normalizedOutputFormat ?? null,
133
+ outputPath: getOutputPath({
134
+ inputDir,
135
+ outputDir,
136
+ filePath: file,
137
+ format: normalizedOutputFormat,
138
+ }),
139
+ });
140
+ }
141
+ return removeDuplicateTasks(tasks);
142
+ }
143
+ tasks.push({
144
+ format: null,
145
+ outputPath: getOutputPath({
146
+ inputDir,
147
+ outputDir,
148
+ filePath: file,
149
+ format: null,
150
+ }),
151
+ });
152
+ return tasks;
153
+ }
154
+ function removeDuplicateTasks(tasks) {
155
+ const map = new Map();
156
+ for (const task of tasks) {
157
+ map.set(task.outputPath, task);
158
+ }
159
+ return [...map.values()];
160
+ }
161
+ async function processImage({ file, outputPath, outputFormat, instructions, quality, }) {
162
+ const inputExt = path.extname(file).toLowerCase();
163
+ if (inputExt === '.svg' && (!outputFormat || outputFormat === 'svg')) {
164
+ await processSvgFile({
165
+ file,
166
+ outputPath,
167
+ instructions,
168
+ });
169
+ return;
170
+ }
171
+ await processRasterFile({
172
+ file,
173
+ outputPath,
174
+ instructions,
175
+ outputFormat,
176
+ quality,
177
+ });
178
+ }
179
+ async function processRasterFile({ file, outputPath, instructions, outputFormat, quality, }) {
180
+ const image = sharp(file, { failOn: 'none' });
181
+ const metadata = await image.metadata();
182
+ let width = instructions.width;
183
+ let height = instructions.height;
184
+ if (instructions.percent && metadata.width) {
185
+ width = Math.round((metadata.width * instructions.percent) / 100);
186
+ }
187
+ if (instructions.scale && metadata.width) {
188
+ width = Math.round(metadata.width * instructions.scale);
189
+ }
190
+ let pipeline = image.rotate();
191
+ if (width || height) {
192
+ pipeline = pipeline.resize({
193
+ width: width ?? undefined,
194
+ height: height ?? undefined,
195
+ fit: width && height ? 'cover' : 'inside',
196
+ withoutEnlargement: !instructions.scale,
197
+ });
198
+ }
199
+ const finalFormat = normalizeOutputFormat(outputFormat || metadata.format);
200
+ switch (finalFormat) {
201
+ case 'jpeg':
202
+ pipeline = pipeline.jpeg({
203
+ quality,
204
+ mozjpeg: true,
205
+ });
206
+ break;
207
+ case 'png':
208
+ pipeline = pipeline.png({
209
+ quality,
210
+ compressionLevel: 9,
211
+ });
212
+ break;
213
+ case 'webp':
214
+ pipeline = pipeline.webp({
215
+ quality,
216
+ });
217
+ break;
218
+ case 'avif':
219
+ pipeline = pipeline.avif({
220
+ quality,
221
+ });
222
+ break;
223
+ case 'svg':
224
+ break;
225
+ }
226
+ const tempPath = `${outputPath}.tmp`;
227
+ await pipeline.toFile(tempPath);
228
+ await fs.rename(tempPath, outputPath);
229
+ }
230
+ async function processSvgFile({ file, outputPath, instructions, }) {
231
+ let svg = await fs.readFile(file, 'utf8');
232
+ if (instructions.width) {
233
+ svg = setSvgAttribute(svg, 'width', `${instructions.width}px`);
234
+ }
235
+ if (instructions.height) {
236
+ svg = setSvgAttribute(svg, 'height', `${instructions.height}px`);
237
+ }
238
+ await fs.writeFile(outputPath, svg);
239
+ }
240
+ function setSvgAttribute(svg, attribute, value) {
241
+ const regexp = new RegExp(`${attribute}="[^"]*"`, 'i');
242
+ if (regexp.test(svg)) {
243
+ return svg.replace(regexp, `${attribute}="${value}"`);
244
+ }
245
+ return svg.replace('<svg', `<svg ${attribute}="${value}"`);
246
+ }
247
+ //# sourceMappingURL=optimizer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"optimizer.js","sourceRoot":"","sources":["../src/optimizer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare function scanImages(inputDir: string): Promise<string[]>;
@@ -0,0 +1,12 @@
1
+ import fg from 'fast-glob';
2
+ const IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'webp', 'avif', 'tiff', 'svg'];
3
+ export async function scanImages(inputDir) {
4
+ const normalizedDir = inputDir.replace(/\\/g, '/');
5
+ const patterns = IMAGE_EXTENSIONS.map(ext => `${normalizedDir}/**/*.${ext}`);
6
+ return fg(patterns, {
7
+ onlyFiles: true,
8
+ dot: false,
9
+ unique: true,
10
+ });
11
+ }
12
+ //# sourceMappingURL=scanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner.js","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,WAAW,CAAA;AAE1B,MAAM,gBAAgB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,CAAA;AAE9E,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,QAAgB;IAC7C,MAAM,aAAa,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;IAElD,MAAM,QAAQ,GAAG,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,aAAa,SAAS,GAAG,EAAE,CAAC,CAAA;IAE5E,OAAO,EAAE,CAAC,QAAQ,EAAE;QAChB,SAAS,EAAE,IAAI;QACf,GAAG,EAAE,KAAK;QACV,MAAM,EAAE,IAAI;KACf,CAAC,CAAA;AACN,CAAC"}
@@ -0,0 +1,71 @@
1
+ export type ImageFormat = 'jpeg' | 'jpg' | 'png' | 'webp' | 'avif';
2
+ export type InputImageFormat = ImageFormat | 'svg' | 'tiff';
3
+ export type OutputImageFormat = ImageFormat | 'svg';
4
+ export type OutputsConfig = Partial<Record<InputImageFormat, OutputImageFormat[]>>;
5
+ export interface ImageOptimizerConfig {
6
+ /**
7
+ * Directory with source images.
8
+ */
9
+ inputDir?: string;
10
+ /**
11
+ * Directory where processed images will be saved.
12
+ */
13
+ outputDir?: string;
14
+ /**
15
+ * Compression quality from 1 to 100.
16
+ *
17
+ * @default 80
18
+ */
19
+ quality?: number;
20
+ /**
21
+ * Force all images to be converted to this format.
22
+ *
23
+ * CLI `--format` has higher priority.
24
+ * Filename instructions have the highest priority.
25
+ */
26
+ format?: ImageFormat;
27
+ /**
28
+ * Base output rules by input format.
29
+ *
30
+ * Example:
31
+ * png: ["png", "webp"]
32
+ *
33
+ * Means:
34
+ * image.png -> image.png + image.webp
35
+ */
36
+ outputs?: OutputsConfig;
37
+ /**
38
+ * Simulate processing without writing files.
39
+ *
40
+ * @default false
41
+ */
42
+ dryRun?: boolean;
43
+ /**
44
+ * Clean output directory before processing.
45
+ *
46
+ * @default false
47
+ */
48
+ clean?: boolean;
49
+ }
50
+ export interface OptimizeOptions extends ImageOptimizerConfig {
51
+ /**
52
+ * Path to custom config file.
53
+ */
54
+ config?: string;
55
+ }
56
+ export interface ImageOutput {
57
+ format: OutputImageFormat;
58
+ keepOriginal: boolean;
59
+ }
60
+ export interface ImageInstructions {
61
+ width: number | null;
62
+ height: number | null;
63
+ percent: number | null;
64
+ scale: number | null;
65
+ quality: number | null;
66
+ outputs: ImageOutput[];
67
+ }
68
+ export interface OutputTask {
69
+ format: OutputImageFormat | null;
70
+ outputPath: string;
71
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,20 @@
1
+ import type { ImageFormat, ImageInstructions, InputImageFormat, OutputImageFormat } from './types.js';
2
+ export declare function pathExists(filePath: string): Promise<boolean>;
3
+ export declare function ensureDirectory(dir: string): Promise<void>;
4
+ export declare function cleanDirectory(dir: string): Promise<void>;
5
+ export declare function getFileSize(file: string): Promise<number>;
6
+ export declare function formatBytes(bytes: number): string;
7
+ export declare function normalizeFormat(format?: string | null): ImageFormat | undefined;
8
+ export declare function normalizeOutputFormat(format?: string | null): OutputImageFormat | undefined;
9
+ export declare function getInputFormat(filePath: string): InputImageFormat;
10
+ export declare function validateQuality(q: number): void;
11
+ export declare function validateFormat(format?: string | null): void;
12
+ export declare function parseImageInstructions(filePath: string): ImageInstructions;
13
+ export declare function removeInstructionsFromName(filePath: string): string;
14
+ export declare function getCleanRelativePath(inputDir: string, filePath: string): string;
15
+ export declare function getOutputPath({ inputDir, outputDir, filePath, format, }: {
16
+ inputDir: string;
17
+ outputDir?: string;
18
+ filePath: string;
19
+ format?: OutputImageFormat | null;
20
+ }): string;
package/dist/utils.js ADDED
@@ -0,0 +1,173 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ const SUPPORTED_OUTPUT_FORMATS = [
4
+ 'jpeg',
5
+ 'jpg',
6
+ 'png',
7
+ 'webp',
8
+ 'avif',
9
+ 'svg',
10
+ ];
11
+ export async function pathExists(filePath) {
12
+ try {
13
+ await fs.access(filePath);
14
+ return true;
15
+ }
16
+ catch {
17
+ return false;
18
+ }
19
+ }
20
+ export async function ensureDirectory(dir) {
21
+ await fs.mkdir(dir, { recursive: true });
22
+ }
23
+ export async function cleanDirectory(dir) {
24
+ await fs.rm(dir, { recursive: true, force: true });
25
+ await ensureDirectory(dir);
26
+ }
27
+ export async function getFileSize(file) {
28
+ return (await fs.stat(file)).size;
29
+ }
30
+ export function formatBytes(bytes) {
31
+ if (!bytes)
32
+ return '0 B';
33
+ const units = ['B', 'KB', 'MB', 'GB'];
34
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
35
+ return `${(bytes / Math.pow(1024, i)).toFixed(2)} ${units[i]}`;
36
+ }
37
+ export function normalizeFormat(format) {
38
+ if (!format)
39
+ return undefined;
40
+ if (format === 'jpg')
41
+ return 'jpeg';
42
+ return format;
43
+ }
44
+ export function normalizeOutputFormat(format) {
45
+ if (!format)
46
+ return undefined;
47
+ if (format === 'jpg')
48
+ return 'jpeg';
49
+ return format;
50
+ }
51
+ export function getInputFormat(filePath) {
52
+ const ext = path.extname(filePath).replace('.', '').toLowerCase();
53
+ if (ext === 'jpg')
54
+ return 'jpeg';
55
+ return ext;
56
+ }
57
+ export function validateQuality(q) {
58
+ if (!Number.isFinite(q) || q < 1 || q > 100) {
59
+ throw new Error('Quality must be between 1 and 100');
60
+ }
61
+ }
62
+ export function validateFormat(format) {
63
+ if (!format)
64
+ return;
65
+ if (!SUPPORTED_OUTPUT_FORMATS.includes(format)) {
66
+ throw new Error(`Unsupported format "${format}"`);
67
+ }
68
+ }
69
+ export function parseImageInstructions(filePath) {
70
+ const fileName = path.basename(filePath);
71
+ const match = fileName.match(/\[([^\]]+)]/);
72
+ const instructions = {
73
+ width: null,
74
+ height: null,
75
+ percent: null,
76
+ scale: null,
77
+ quality: null,
78
+ outputs: [],
79
+ };
80
+ if (!match) {
81
+ return instructions;
82
+ }
83
+ const parts = match[1]
84
+ .split(',')
85
+ .map(part => part.trim())
86
+ .filter(Boolean);
87
+ for (const part of parts) {
88
+ parseInstructionPart(part, instructions);
89
+ }
90
+ return instructions;
91
+ }
92
+ function parseInstructionPart(part, instructions) {
93
+ if (/^\d+$/.test(part)) {
94
+ instructions.width = Number(part);
95
+ return;
96
+ }
97
+ if (/^\d+x\d+$/.test(part)) {
98
+ const [width, height] = part.split('x').map(Number);
99
+ instructions.width = width;
100
+ instructions.height = height;
101
+ return;
102
+ }
103
+ if (/^w=\d+$/.test(part)) {
104
+ instructions.width = Number(part.replace('w=', ''));
105
+ return;
106
+ }
107
+ if (/^h=\d+$/.test(part)) {
108
+ instructions.height = Number(part.replace('h=', ''));
109
+ return;
110
+ }
111
+ if (/^\d+%$/.test(part)) {
112
+ instructions.percent = Number(part.replace('%', ''));
113
+ return;
114
+ }
115
+ if (/^\d+(\.\d+)?x$/.test(part)) {
116
+ instructions.scale = Number(part.replace('x', ''));
117
+ return;
118
+ }
119
+ if (/^(q|quality)=\d+$/.test(part)) {
120
+ const quality = Number(part.split('=')[1]);
121
+ validateQuality(quality);
122
+ instructions.quality = quality;
123
+ return;
124
+ }
125
+ if (/^\+\w+$/.test(part)) {
126
+ const format = normalizeOutputFormat(part.replace('+', ''));
127
+ validateFormat(format);
128
+ instructions.outputs.push({
129
+ format: format,
130
+ keepOriginal: true,
131
+ });
132
+ return;
133
+ }
134
+ if (/^(webp|avif|png|jpg|jpeg|svg)$/.test(part)) {
135
+ const format = normalizeOutputFormat(part);
136
+ instructions.outputs.push({
137
+ format: format,
138
+ keepOriginal: false,
139
+ });
140
+ return;
141
+ }
142
+ if (/^\d+\.\+?(webp|avif|png|jpg|jpeg)$/.test(part)) {
143
+ const [width, rawFormat] = part.split('.');
144
+ instructions.width = Number(width);
145
+ const keepOriginal = rawFormat.startsWith('+');
146
+ const format = normalizeOutputFormat(rawFormat.replace('+', ''));
147
+ validateFormat(format);
148
+ instructions.outputs.push({
149
+ format: format,
150
+ keepOriginal,
151
+ });
152
+ return;
153
+ }
154
+ throw new Error(`Unknown filename instruction: "${part}"`);
155
+ }
156
+ export function removeInstructionsFromName(filePath) {
157
+ const ext = path.extname(filePath);
158
+ const name = path.basename(filePath, ext);
159
+ return `${name.replace(/\[[^\]]+]/g, '')}${ext}`;
160
+ }
161
+ export function getCleanRelativePath(inputDir, filePath) {
162
+ const relative = path.relative(inputDir, filePath);
163
+ const dir = path.dirname(relative);
164
+ const cleanName = removeInstructionsFromName(relative);
165
+ return path.join(dir, cleanName);
166
+ }
167
+ export function getOutputPath({ inputDir, outputDir, filePath, format, }) {
168
+ const cleanRelativePath = getCleanRelativePath(inputDir, filePath);
169
+ const parsed = path.parse(cleanRelativePath);
170
+ const ext = format ? `.${normalizeOutputFormat(format)}` : parsed.ext;
171
+ return path.join(outputDir || inputDir, parsed.dir, `${parsed.name}${ext}`);
172
+ }
173
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@valdiszz53/image-optimizer-cli",
3
+ "version": "1.0.0",
4
+ "description": "CLI package for optimizing images from a directory",
5
+ "type": "module",
6
+ "bin": {
7
+ "image-optimizer": "./dist/cli.js"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "files": [
12
+ "dist",
13
+ "LICENSE",
14
+ "README.md"
15
+ ],
16
+ "author": {
17
+ "name": "Vladislav Tregubov"
18
+ },
19
+ "license": "MIT",
20
+ "scripts": {
21
+ "build": "tsc",
22
+ "dev": "tsx src/cli.ts",
23
+ "start": "node dist/cli.js",
24
+ "playground": "npm run build && node dist/cli.js --config image-optimizer.config.js",
25
+ "format": "prettier --write \"src/**/*.ts\"",
26
+ "prepublishOnly": "npm run build"
27
+ },
28
+ "dependencies": {
29
+ "chalk": "^5.3.0",
30
+ "commander": "^12.1.0",
31
+ "fast-glob": "^3.3.2",
32
+ "sharp": "^0.33.5"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "^22.0.0",
36
+ "prettier": "^3.9.1",
37
+ "tsx": "^4.19.0",
38
+ "typescript": "^5.5.0"
39
+ }
40
+ }