@puntoycoma/bulki 3.0.1 → 3.0.3
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 +175 -116
- package/dist/bin/bulki.js +144 -220
- package/dist/bin/bulki.js.map +1 -1
- package/dist/chunk-YUAKNSBH.js +851 -0
- package/dist/chunk-YUAKNSBH.js.map +1 -0
- package/dist/interactive-MTL4CMVQ.js +566 -0
- package/dist/interactive-MTL4CMVQ.js.map +1 -0
- package/package.json +2 -1
- package/dist/chunk-OMZC5IWI.js +0 -501
- package/dist/chunk-OMZC5IWI.js.map +0 -1
- package/dist/interactive-LGL325GZ.js +0 -294
- package/dist/interactive-LGL325GZ.js.map +0 -1
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
collectImageFiles,
|
|
4
|
-
formatSummary,
|
|
5
|
-
installHook,
|
|
6
|
-
optimizeImage,
|
|
7
|
-
processPool,
|
|
8
|
-
startWatch,
|
|
9
|
-
uninstallHook
|
|
10
|
-
} from "./chunk-OMZC5IWI.js";
|
|
11
|
-
|
|
12
|
-
// src/interactive/index.ts
|
|
13
|
-
import { availableParallelism } from "os";
|
|
14
|
-
import path from "path";
|
|
15
|
-
import * as p from "@clack/prompts";
|
|
16
|
-
import { isCancel } from "@clack/prompts";
|
|
17
|
-
var PRESETS = {
|
|
18
|
-
web: {
|
|
19
|
-
label: "Web optimized",
|
|
20
|
-
hint: "WebP, quality 80, max 1920px",
|
|
21
|
-
options: { format: "webp", quality: 80, resize: 1920 }
|
|
22
|
-
},
|
|
23
|
-
social: {
|
|
24
|
-
label: "Social media",
|
|
25
|
-
hint: "PNG, quality 85, max 1080px \u2014 lossless colors",
|
|
26
|
-
options: { format: "png", quality: 85, resize: 1080 }
|
|
27
|
-
},
|
|
28
|
-
custom: {
|
|
29
|
-
label: "Custom settings",
|
|
30
|
-
hint: "Choose each option manually",
|
|
31
|
-
options: null
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
async function collectPresetOptions() {
|
|
35
|
-
const presetKey = await p.select({
|
|
36
|
-
message: "Choose a preset or configure manually",
|
|
37
|
-
options: [
|
|
38
|
-
{ value: "web", label: PRESETS.web.label, hint: PRESETS.web.hint },
|
|
39
|
-
{ value: "social", label: PRESETS.social.label, hint: PRESETS.social.hint },
|
|
40
|
-
{ value: "custom", label: PRESETS.custom.label, hint: PRESETS.custom.hint }
|
|
41
|
-
]
|
|
42
|
-
});
|
|
43
|
-
if (isCancel(presetKey)) {
|
|
44
|
-
p.cancel("Operation cancelled.");
|
|
45
|
-
process.exit(0);
|
|
46
|
-
}
|
|
47
|
-
let format;
|
|
48
|
-
let quality = 70;
|
|
49
|
-
let resize;
|
|
50
|
-
if (presetKey !== "custom") {
|
|
51
|
-
const preset = PRESETS[presetKey];
|
|
52
|
-
const opts = preset.options;
|
|
53
|
-
format = opts.format;
|
|
54
|
-
quality = opts.quality;
|
|
55
|
-
resize = opts.resize;
|
|
56
|
-
} else {
|
|
57
|
-
const settings = await p.group(
|
|
58
|
-
{
|
|
59
|
-
format: () => p.select({
|
|
60
|
-
message: "Output format",
|
|
61
|
-
options: [
|
|
62
|
-
{ value: "original", label: "Keep original", hint: "no conversion" },
|
|
63
|
-
{ value: "webp", label: "WebP", hint: "best compression/quality ratio" },
|
|
64
|
-
{ value: "avif", label: "AVIF", hint: "smallest files, slower encoding" },
|
|
65
|
-
{ value: "jpeg", label: "JPEG", hint: "universal compatibility" },
|
|
66
|
-
{ value: "png", label: "PNG", hint: "lossless" }
|
|
67
|
-
]
|
|
68
|
-
}),
|
|
69
|
-
quality: () => p.text({
|
|
70
|
-
message: "Quality (1-100)",
|
|
71
|
-
placeholder: "70",
|
|
72
|
-
initialValue: "70",
|
|
73
|
-
validate(value) {
|
|
74
|
-
const n = parseInt(value ?? "", 10);
|
|
75
|
-
if (Number.isNaN(n) || n < 1 || n > 100) return "Must be between 1 and 100";
|
|
76
|
-
}
|
|
77
|
-
}),
|
|
78
|
-
resize: () => p.select({
|
|
79
|
-
message: "Resize images?",
|
|
80
|
-
options: [
|
|
81
|
-
{ value: "none", label: "No resize", hint: "keep original dimensions" },
|
|
82
|
-
{ value: "1920", label: "1920px", hint: "Full HD width" },
|
|
83
|
-
{ value: "1080", label: "1080px", hint: "HD width" },
|
|
84
|
-
{ value: "800", label: "800px", hint: "web thumbnails" },
|
|
85
|
-
{ value: "custom", label: "Custom width..." }
|
|
86
|
-
]
|
|
87
|
-
})
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
onCancel: () => {
|
|
91
|
-
p.cancel("Operation cancelled.");
|
|
92
|
-
process.exit(0);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
);
|
|
96
|
-
let resizeValue;
|
|
97
|
-
if (settings.resize === "custom") {
|
|
98
|
-
const customWidth = await p.text({
|
|
99
|
-
message: "Enter custom width (px)",
|
|
100
|
-
placeholder: "1200",
|
|
101
|
-
validate(value) {
|
|
102
|
-
const n = parseInt(value ?? "", 10);
|
|
103
|
-
if (Number.isNaN(n) || n <= 0) return "Must be a positive number";
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
if (isCancel(customWidth)) {
|
|
107
|
-
p.cancel("Operation cancelled.");
|
|
108
|
-
process.exit(0);
|
|
109
|
-
}
|
|
110
|
-
resizeValue = parseInt(customWidth, 10);
|
|
111
|
-
} else if (settings.resize !== "none") {
|
|
112
|
-
resizeValue = parseInt(settings.resize, 10);
|
|
113
|
-
}
|
|
114
|
-
format = settings.format === "original" ? void 0 : settings.format;
|
|
115
|
-
quality = parseInt(settings.quality, 10);
|
|
116
|
-
resize = resizeValue;
|
|
117
|
-
}
|
|
118
|
-
return { format, quality, resize };
|
|
119
|
-
}
|
|
120
|
-
async function runCompress() {
|
|
121
|
-
const { format, quality, resize } = await collectPresetOptions();
|
|
122
|
-
const inputPath = await p.text({
|
|
123
|
-
message: "Path to images (file or directory)",
|
|
124
|
-
placeholder: "./photos",
|
|
125
|
-
validate(value) {
|
|
126
|
-
if (!value || value.trim() === "") return "Path is required";
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
-
if (isCancel(inputPath)) {
|
|
130
|
-
p.cancel("Operation cancelled.");
|
|
131
|
-
process.exit(0);
|
|
132
|
-
}
|
|
133
|
-
const resolvedPath = path.resolve(inputPath);
|
|
134
|
-
let filePaths;
|
|
135
|
-
let isDirectory;
|
|
136
|
-
try {
|
|
137
|
-
const collected = await collectImageFiles(resolvedPath, []);
|
|
138
|
-
filePaths = collected.files;
|
|
139
|
-
isDirectory = collected.isDirectory;
|
|
140
|
-
} catch {
|
|
141
|
-
p.cancel(`Path not found: ${resolvedPath}`);
|
|
142
|
-
process.exit(1);
|
|
143
|
-
}
|
|
144
|
-
if (filePaths.length === 0) {
|
|
145
|
-
p.outro("No images found to process.");
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
const destInput = await p.text({
|
|
149
|
-
message: "Destination directory",
|
|
150
|
-
placeholder: ".",
|
|
151
|
-
initialValue: "."
|
|
152
|
-
});
|
|
153
|
-
if (isCancel(destInput)) {
|
|
154
|
-
p.cancel("Operation cancelled.");
|
|
155
|
-
process.exit(0);
|
|
156
|
-
}
|
|
157
|
-
const destination = destInput || ".";
|
|
158
|
-
const resizeLabel = resize ? `${resize}px` : "No resize";
|
|
159
|
-
const formatLabel = format ?? "Keep original";
|
|
160
|
-
p.note(
|
|
161
|
-
[
|
|
162
|
-
`Source: ${resolvedPath} (${filePaths.length} images found)`,
|
|
163
|
-
`Format: ${formatLabel}`,
|
|
164
|
-
`Quality: ${quality}`,
|
|
165
|
-
`Resize: ${resizeLabel}`,
|
|
166
|
-
`Output: ${path.join(destination, "bulki-output")}`
|
|
167
|
-
].join("\n"),
|
|
168
|
-
"Summary"
|
|
169
|
-
);
|
|
170
|
-
const confirmed = await p.confirm({ message: "Start processing?" });
|
|
171
|
-
if (isCancel(confirmed) || !confirmed) {
|
|
172
|
-
p.cancel("Operation cancelled.");
|
|
173
|
-
process.exit(0);
|
|
174
|
-
}
|
|
175
|
-
const options = {
|
|
176
|
-
quality,
|
|
177
|
-
format,
|
|
178
|
-
resize,
|
|
179
|
-
destination,
|
|
180
|
-
outputFolder: "bulki-output",
|
|
181
|
-
concurrency: availableParallelism(),
|
|
182
|
-
exclude: [],
|
|
183
|
-
dryRun: false,
|
|
184
|
-
verbose: false,
|
|
185
|
-
json: false
|
|
186
|
-
};
|
|
187
|
-
const sourcePath = isDirectory ? resolvedPath : path.dirname(resolvedPath);
|
|
188
|
-
const startTime = performance.now();
|
|
189
|
-
const errors = [];
|
|
190
|
-
const bar = p.progress({ max: filePaths.length });
|
|
191
|
-
bar.start("Processing images");
|
|
192
|
-
const processedResults = await processPool(
|
|
193
|
-
filePaths,
|
|
194
|
-
async (file) => {
|
|
195
|
-
try {
|
|
196
|
-
const result = await optimizeImage(file.path, {
|
|
197
|
-
...options,
|
|
198
|
-
sourcePath,
|
|
199
|
-
detectedFormat: file.format
|
|
200
|
-
});
|
|
201
|
-
bar.advance(1, `Processed ${path.basename(file.path)}`);
|
|
202
|
-
return result;
|
|
203
|
-
} catch (err) {
|
|
204
|
-
errors.push(err);
|
|
205
|
-
bar.advance(1, `Failed ${path.basename(file.path)}`);
|
|
206
|
-
return null;
|
|
207
|
-
}
|
|
208
|
-
},
|
|
209
|
-
options.concurrency
|
|
210
|
-
);
|
|
211
|
-
bar.stop("All images processed");
|
|
212
|
-
const validResults = processedResults.filter((r) => r !== null);
|
|
213
|
-
const totalDuration = performance.now() - startTime;
|
|
214
|
-
const summary = formatSummary(validResults, errors, totalDuration);
|
|
215
|
-
p.note(summary, "Results");
|
|
216
|
-
p.outro("Done! Your optimized images are ready.");
|
|
217
|
-
}
|
|
218
|
-
async function runWatch() {
|
|
219
|
-
const watchDir = await p.text({
|
|
220
|
-
message: "Directory to watch",
|
|
221
|
-
placeholder: "./photos",
|
|
222
|
-
validate(value) {
|
|
223
|
-
if (!value?.trim()) return "Path is required";
|
|
224
|
-
}
|
|
225
|
-
});
|
|
226
|
-
if (isCancel(watchDir)) {
|
|
227
|
-
p.cancel("Operation cancelled.");
|
|
228
|
-
process.exit(0);
|
|
229
|
-
}
|
|
230
|
-
const { format, quality, resize } = await collectPresetOptions();
|
|
231
|
-
const destInput = await p.text({
|
|
232
|
-
message: "Destination directory",
|
|
233
|
-
placeholder: ".",
|
|
234
|
-
initialValue: "."
|
|
235
|
-
});
|
|
236
|
-
if (isCancel(destInput)) {
|
|
237
|
-
p.cancel("Operation cancelled.");
|
|
238
|
-
process.exit(0);
|
|
239
|
-
}
|
|
240
|
-
const destination = destInput || ".";
|
|
241
|
-
p.log.info(`Watching ${watchDir}... Press Ctrl+C to stop.`);
|
|
242
|
-
await startWatch(watchDir, {
|
|
243
|
-
quality,
|
|
244
|
-
format,
|
|
245
|
-
resize,
|
|
246
|
-
destination
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
async function runHook() {
|
|
250
|
-
const hookAction = await p.select({
|
|
251
|
-
message: "Git hook action",
|
|
252
|
-
options: [
|
|
253
|
-
{ value: "install", label: "Install", hint: "auto-optimize images on commit" },
|
|
254
|
-
{ value: "uninstall", label: "Uninstall", hint: "remove the hook" }
|
|
255
|
-
]
|
|
256
|
-
});
|
|
257
|
-
if (isCancel(hookAction)) {
|
|
258
|
-
p.cancel("Operation cancelled.");
|
|
259
|
-
process.exit(0);
|
|
260
|
-
}
|
|
261
|
-
if (hookAction === "install") {
|
|
262
|
-
await installHook();
|
|
263
|
-
p.outro("Git hook installed! Images will be auto-optimized on commit.");
|
|
264
|
-
} else {
|
|
265
|
-
await uninstallHook();
|
|
266
|
-
p.outro("Git hook removed.");
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
async function runInteractive() {
|
|
270
|
-
p.intro("Punto y Coma \u2014 bulki");
|
|
271
|
-
const action = await p.select({
|
|
272
|
-
message: "What do you want to do?",
|
|
273
|
-
options: [
|
|
274
|
-
{ value: "compress", label: "Compress images", hint: "optimize files or directories" },
|
|
275
|
-
{ value: "watch", label: "Watch directory", hint: "auto-optimize new images" },
|
|
276
|
-
{ value: "hook", label: "Git hook", hint: "auto-optimize on commit" }
|
|
277
|
-
]
|
|
278
|
-
});
|
|
279
|
-
if (isCancel(action)) {
|
|
280
|
-
p.cancel("Operation cancelled.");
|
|
281
|
-
process.exit(0);
|
|
282
|
-
}
|
|
283
|
-
if (action === "compress") {
|
|
284
|
-
await runCompress();
|
|
285
|
-
} else if (action === "watch") {
|
|
286
|
-
await runWatch();
|
|
287
|
-
} else {
|
|
288
|
-
await runHook();
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
export {
|
|
292
|
-
runInteractive
|
|
293
|
-
};
|
|
294
|
-
//# sourceMappingURL=interactive-LGL325GZ.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/interactive/index.ts"],"sourcesContent":["import { availableParallelism } from 'node:os'\nimport path from 'node:path'\nimport * as p from '@clack/prompts'\nimport { isCancel } from '@clack/prompts'\nimport { installHook, uninstallHook } from '../commands/hook.js'\nimport { startWatch } from '../commands/watch.js'\nimport { processPool } from '../concurrency/pool.js'\nimport { formatSummary } from '../output/results.js'\nimport { collectImageFiles } from '../pipeline/discover.js'\nimport { optimizeImage } from '../pipeline/optimize.js'\nimport type { CompressOptions, FileResult } from '../types/index.js'\n\nconst PRESETS = {\n\tweb: {\n\t\tlabel: 'Web optimized',\n\t\thint: 'WebP, quality 80, max 1920px',\n\t\toptions: { format: 'webp', quality: 80, resize: 1920 },\n\t},\n\tsocial: {\n\t\tlabel: 'Social media',\n\t\thint: 'PNG, quality 85, max 1080px — lossless colors',\n\t\toptions: { format: 'png', quality: 85, resize: 1080 },\n\t},\n\tcustom: {\n\t\tlabel: 'Custom settings',\n\t\thint: 'Choose each option manually',\n\t\toptions: null,\n\t},\n}\n\nasync function collectPresetOptions(): Promise<{\n\tformat: string | undefined\n\tquality: number\n\tresize: number | undefined\n}> {\n\tconst presetKey = await p.select({\n\t\tmessage: 'Choose a preset or configure manually',\n\t\toptions: [\n\t\t\t{ value: 'web', label: PRESETS.web.label, hint: PRESETS.web.hint },\n\t\t\t{ value: 'social', label: PRESETS.social.label, hint: PRESETS.social.hint },\n\t\t\t{ value: 'custom', label: PRESETS.custom.label, hint: PRESETS.custom.hint },\n\t\t],\n\t})\n\n\tif (isCancel(presetKey)) {\n\t\tp.cancel('Operation cancelled.')\n\t\tprocess.exit(0)\n\t}\n\n\tlet format: string | undefined\n\tlet quality = 70\n\tlet resize: number | undefined\n\n\tif (presetKey !== 'custom') {\n\t\tconst preset = PRESETS[presetKey as keyof typeof PRESETS]\n\t\tconst opts = preset.options as { format: string; quality: number; resize: number }\n\t\tformat = opts.format\n\t\tquality = opts.quality\n\t\tresize = opts.resize\n\t} else {\n\t\t// Custom mode\n\t\tconst settings = await p.group(\n\t\t\t{\n\t\t\t\tformat: () =>\n\t\t\t\t\tp.select({\n\t\t\t\t\t\tmessage: 'Output format',\n\t\t\t\t\t\toptions: [\n\t\t\t\t\t\t\t{ value: 'original', label: 'Keep original', hint: 'no conversion' },\n\t\t\t\t\t\t\t{ value: 'webp', label: 'WebP', hint: 'best compression/quality ratio' },\n\t\t\t\t\t\t\t{ value: 'avif', label: 'AVIF', hint: 'smallest files, slower encoding' },\n\t\t\t\t\t\t\t{ value: 'jpeg', label: 'JPEG', hint: 'universal compatibility' },\n\t\t\t\t\t\t\t{ value: 'png', label: 'PNG', hint: 'lossless' },\n\t\t\t\t\t\t],\n\t\t\t\t\t}),\n\t\t\t\tquality: () =>\n\t\t\t\t\tp.text({\n\t\t\t\t\t\tmessage: 'Quality (1-100)',\n\t\t\t\t\t\tplaceholder: '70',\n\t\t\t\t\t\tinitialValue: '70',\n\t\t\t\t\t\tvalidate(value) {\n\t\t\t\t\t\t\tconst n = parseInt(value ?? '', 10)\n\t\t\t\t\t\t\tif (Number.isNaN(n) || n < 1 || n > 100) return 'Must be between 1 and 100'\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\tresize: () =>\n\t\t\t\t\tp.select({\n\t\t\t\t\t\tmessage: 'Resize images?',\n\t\t\t\t\t\toptions: [\n\t\t\t\t\t\t\t{ value: 'none', label: 'No resize', hint: 'keep original dimensions' },\n\t\t\t\t\t\t\t{ value: '1920', label: '1920px', hint: 'Full HD width' },\n\t\t\t\t\t\t\t{ value: '1080', label: '1080px', hint: 'HD width' },\n\t\t\t\t\t\t\t{ value: '800', label: '800px', hint: 'web thumbnails' },\n\t\t\t\t\t\t\t{ value: 'custom', label: 'Custom width...' },\n\t\t\t\t\t\t],\n\t\t\t\t\t}),\n\t\t\t},\n\t\t\t{\n\t\t\t\tonCancel: () => {\n\t\t\t\t\tp.cancel('Operation cancelled.')\n\t\t\t\t\tprocess.exit(0)\n\t\t\t\t},\n\t\t\t},\n\t\t)\n\n\t\tlet resizeValue: number | undefined\n\t\tif (settings.resize === 'custom') {\n\t\t\tconst customWidth = await p.text({\n\t\t\t\tmessage: 'Enter custom width (px)',\n\t\t\t\tplaceholder: '1200',\n\t\t\t\tvalidate(value) {\n\t\t\t\t\tconst n = parseInt(value ?? '', 10)\n\t\t\t\t\tif (Number.isNaN(n) || n <= 0) return 'Must be a positive number'\n\t\t\t\t},\n\t\t\t})\n\t\t\tif (isCancel(customWidth)) {\n\t\t\t\tp.cancel('Operation cancelled.')\n\t\t\t\tprocess.exit(0)\n\t\t\t}\n\t\t\tresizeValue = parseInt(customWidth as string, 10)\n\t\t} else if (settings.resize !== 'none') {\n\t\t\tresizeValue = parseInt(settings.resize as string, 10)\n\t\t}\n\n\t\tformat = settings.format === 'original' ? undefined : (settings.format as string)\n\t\tquality = parseInt(settings.quality as string, 10)\n\t\tresize = resizeValue\n\t}\n\n\treturn { format, quality, resize }\n}\n\nasync function runCompress(): Promise<void> {\n\tconst { format, quality, resize } = await collectPresetOptions()\n\n\t// Path input\n\tconst inputPath = await p.text({\n\t\tmessage: 'Path to images (file or directory)',\n\t\tplaceholder: './photos',\n\t\tvalidate(value) {\n\t\t\tif (!value || value.trim() === '') return 'Path is required'\n\t\t},\n\t})\n\n\tif (isCancel(inputPath)) {\n\t\tp.cancel('Operation cancelled.')\n\t\tprocess.exit(0)\n\t}\n\n\tconst resolvedPath = path.resolve(inputPath as string)\n\n\tlet filePaths: { path: string; format: string }[]\n\tlet isDirectory: boolean\n\ttry {\n\t\tconst collected = await collectImageFiles(resolvedPath, [])\n\t\tfilePaths = collected.files\n\t\tisDirectory = collected.isDirectory\n\t} catch {\n\t\tp.cancel(`Path not found: ${resolvedPath}`)\n\t\tprocess.exit(1)\n\t}\n\n\tif (filePaths.length === 0) {\n\t\tp.outro('No images found to process.')\n\t\treturn\n\t}\n\n\t// Destination input\n\tconst destInput = await p.text({\n\t\tmessage: 'Destination directory',\n\t\tplaceholder: '.',\n\t\tinitialValue: '.',\n\t})\n\n\tif (isCancel(destInput)) {\n\t\tp.cancel('Operation cancelled.')\n\t\tprocess.exit(0)\n\t}\n\n\tconst destination = (destInput as string) || '.'\n\n\t// Show summary\n\tconst resizeLabel = resize ? `${resize}px` : 'No resize'\n\tconst formatLabel = format ?? 'Keep original'\n\tp.note(\n\t\t[\n\t\t\t`Source: ${resolvedPath} (${filePaths.length} images found)`,\n\t\t\t`Format: ${formatLabel}`,\n\t\t\t`Quality: ${quality}`,\n\t\t\t`Resize: ${resizeLabel}`,\n\t\t\t`Output: ${path.join(destination, 'bulki-output')}`,\n\t\t].join('\\n'),\n\t\t'Summary',\n\t)\n\n\tconst confirmed = await p.confirm({ message: 'Start processing?' })\n\n\tif (isCancel(confirmed) || !confirmed) {\n\t\tp.cancel('Operation cancelled.')\n\t\tprocess.exit(0)\n\t}\n\n\t// Process images\n\tconst options: CompressOptions = {\n\t\tquality: quality,\n\t\tformat,\n\t\tresize,\n\t\tdestination,\n\t\toutputFolder: 'bulki-output',\n\t\tconcurrency: availableParallelism(),\n\t\texclude: [],\n\t\tdryRun: false,\n\t\tverbose: false,\n\t\tjson: false,\n\t}\n\n\tconst sourcePath = isDirectory ? resolvedPath : path.dirname(resolvedPath)\n\tconst startTime = performance.now()\n\tconst errors: Error[] = []\n\n\tconst bar = p.progress({ max: filePaths.length })\n\tbar.start('Processing images')\n\n\tconst processedResults = await processPool(\n\t\tfilePaths,\n\t\tasync (file) => {\n\t\t\ttry {\n\t\t\t\tconst result = await optimizeImage(file.path, {\n\t\t\t\t\t...options,\n\t\t\t\t\tsourcePath,\n\t\t\t\t\tdetectedFormat: file.format,\n\t\t\t\t})\n\t\t\t\tbar.advance(1, `Processed ${path.basename(file.path)}`)\n\t\t\t\treturn result\n\t\t\t} catch (err) {\n\t\t\t\terrors.push(err as Error)\n\t\t\t\tbar.advance(1, `Failed ${path.basename(file.path)}`)\n\t\t\t\treturn null\n\t\t\t}\n\t\t},\n\t\toptions.concurrency,\n\t)\n\n\tbar.stop('All images processed')\n\n\tconst validResults = processedResults.filter((r): r is FileResult => r !== null)\n\tconst totalDuration = performance.now() - startTime\n\n\tconst summary = formatSummary(validResults, errors, totalDuration)\n\tp.note(summary, 'Results')\n\n\tp.outro('Done! Your optimized images are ready.')\n}\n\nasync function runWatch(): Promise<void> {\n\tconst watchDir = await p.text({\n\t\tmessage: 'Directory to watch',\n\t\tplaceholder: './photos',\n\t\tvalidate(value) {\n\t\t\tif (!value?.trim()) return 'Path is required'\n\t\t},\n\t})\n\n\tif (isCancel(watchDir)) {\n\t\tp.cancel('Operation cancelled.')\n\t\tprocess.exit(0)\n\t}\n\n\tconst { format, quality, resize } = await collectPresetOptions()\n\n\tconst destInput = await p.text({\n\t\tmessage: 'Destination directory',\n\t\tplaceholder: '.',\n\t\tinitialValue: '.',\n\t})\n\n\tif (isCancel(destInput)) {\n\t\tp.cancel('Operation cancelled.')\n\t\tprocess.exit(0)\n\t}\n\n\tconst destination = (destInput as string) || '.'\n\n\tp.log.info(`Watching ${watchDir}... Press Ctrl+C to stop.`)\n\n\tawait startWatch(watchDir as string, {\n\t\tquality,\n\t\tformat,\n\t\tresize,\n\t\tdestination,\n\t})\n}\n\nasync function runHook(): Promise<void> {\n\tconst hookAction = await p.select({\n\t\tmessage: 'Git hook action',\n\t\toptions: [\n\t\t\t{ value: 'install', label: 'Install', hint: 'auto-optimize images on commit' },\n\t\t\t{ value: 'uninstall', label: 'Uninstall', hint: 'remove the hook' },\n\t\t],\n\t})\n\n\tif (isCancel(hookAction)) {\n\t\tp.cancel('Operation cancelled.')\n\t\tprocess.exit(0)\n\t}\n\n\tif (hookAction === 'install') {\n\t\tawait installHook()\n\t\tp.outro('Git hook installed! Images will be auto-optimized on commit.')\n\t} else {\n\t\tawait uninstallHook()\n\t\tp.outro('Git hook removed.')\n\t}\n}\n\nexport async function runInteractive(): Promise<void> {\n\tp.intro('Punto y Coma — bulki')\n\n\tconst action = await p.select({\n\t\tmessage: 'What do you want to do?',\n\t\toptions: [\n\t\t\t{ value: 'compress', label: 'Compress images', hint: 'optimize files or directories' },\n\t\t\t{ value: 'watch', label: 'Watch directory', hint: 'auto-optimize new images' },\n\t\t\t{ value: 'hook', label: 'Git hook', hint: 'auto-optimize on commit' },\n\t\t],\n\t})\n\n\tif (isCancel(action)) {\n\t\tp.cancel('Operation cancelled.')\n\t\tprocess.exit(0)\n\t}\n\n\tif (action === 'compress') {\n\t\tawait runCompress()\n\t} else if (action === 'watch') {\n\t\tawait runWatch()\n\t} else {\n\t\tawait runHook()\n\t}\n}\n"],"mappings":"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","names":[]}
|