@puntoycoma/bulki 3.0.2 → 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.
@@ -1,532 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/concurrency/pool.ts
4
- async function processPool(items, processor, concurrency) {
5
- const results = new Array(items.length);
6
- const executing = /* @__PURE__ */ new Set();
7
- let firstError;
8
- let hasError = false;
9
- for (let i = 0; i < items.length; i++) {
10
- const index = i;
11
- const p = processor(items[index]).then((result) => {
12
- results[index] = result;
13
- }).catch((err) => {
14
- if (!hasError) {
15
- hasError = true;
16
- firstError = err;
17
- }
18
- }).finally(() => {
19
- executing.delete(p);
20
- });
21
- executing.add(p);
22
- if (executing.size >= concurrency) {
23
- await Promise.race(executing);
24
- }
25
- }
26
- await Promise.all(executing);
27
- if (hasError) {
28
- throw firstError;
29
- }
30
- return results;
31
- }
32
-
33
- // src/output/results.ts
34
- import path from "path";
35
-
36
- // src/output/format-bytes.ts
37
- function formatBytes(bytes) {
38
- if (bytes <= 0) return "0 B";
39
- const GB = 1024 * 1024 * 1024;
40
- const MB = 1024 * 1024;
41
- const KB = 1024;
42
- if (bytes >= GB) return `${(bytes / GB).toFixed(2)} GB`;
43
- if (bytes >= MB) return `${(bytes / MB).toFixed(2)} MB`;
44
- if (bytes >= KB) return `${(bytes / KB).toFixed(2)} KB`;
45
- return `${bytes} B`;
46
- }
47
-
48
- // src/output/results.ts
49
- function calcReduction(originalSize, newSize) {
50
- return originalSize > 0 ? Math.round((1 - newSize / originalSize) * 100) : 0;
51
- }
52
- function formatFileResult(result) {
53
- const filename = path.basename(result.inputPath);
54
- const original = formatBytes(result.originalSize);
55
- const newSize = formatBytes(result.newSize);
56
- const reduction = calcReduction(result.originalSize, result.newSize);
57
- const pct = reduction < 0 ? `+${Math.abs(reduction)}%` : `${reduction}%`;
58
- return `${filename} ${original} -> ${newSize} (${pct})`;
59
- }
60
- function buildJsonOutput(results, errors, totalDuration) {
61
- const totalOriginal = results.reduce((sum, r) => sum + r.originalSize, 0);
62
- const totalNew = results.reduce((sum, r) => sum + r.newSize, 0);
63
- const totalReduction = calcReduction(totalOriginal, totalNew);
64
- return {
65
- files: results.map((r) => ({
66
- input: r.inputPath,
67
- output: r.outputPath,
68
- originalSize: r.originalSize,
69
- newSize: r.newSize,
70
- reduction: calcReduction(r.originalSize, r.newSize),
71
- format: r.format,
72
- duration: r.duration
73
- })),
74
- summary: {
75
- totalFiles: results.length,
76
- totalOriginalSize: totalOriginal,
77
- totalNewSize: totalNew,
78
- totalReduction,
79
- duration: totalDuration,
80
- errors: errors.length
81
- },
82
- errors
83
- };
84
- }
85
- function formatSummary(results, errors, totalDuration) {
86
- const totalOriginal = results.reduce((sum, r) => sum + r.originalSize, 0);
87
- const totalNew = results.reduce((sum, r) => sum + r.newSize, 0);
88
- const reduction = totalOriginal > 0 ? Math.round((1 - totalNew / totalOriginal) * 100) : 0;
89
- const duration = `${(totalDuration / 1e3).toFixed(1)}s`;
90
- const fileCount = results.length;
91
- let summary = `${fileCount} files | ${formatBytes(totalOriginal)} -> ${formatBytes(totalNew)} (${reduction}%) | ${duration}`;
92
- if (errors.length > 0) {
93
- summary += ` | ${errors.length} errors`;
94
- }
95
- return summary;
96
- }
97
-
98
- // src/pipeline/discover.ts
99
- import fs2 from "fs/promises";
100
- import path2 from "path";
101
-
102
- // src/pipeline/filter.ts
103
- import fs from "fs/promises";
104
- import sharp from "sharp";
105
- async function detectFormat(filePath) {
106
- try {
107
- const handle = await fs.open(filePath, "r");
108
- const buffer = Buffer.alloc(12);
109
- try {
110
- await handle.read(buffer, 0, 12, 0);
111
- } finally {
112
- await handle.close();
113
- }
114
- if (buffer[0] === 255 && buffer[1] === 216 && buffer[2] === 255) return "jpeg";
115
- if (buffer[0] === 137 && buffer[1] === 80 && buffer[2] === 78 && buffer[3] === 71 && buffer[4] === 13 && buffer[5] === 10 && buffer[6] === 26 && buffer[7] === 10)
116
- return "png";
117
- if (buffer[0] === 82 && buffer[1] === 73 && buffer[2] === 70 && buffer[3] === 70 && buffer[8] === 87 && buffer[9] === 69 && buffer[10] === 66 && buffer[11] === 80)
118
- return "webp";
119
- if (buffer[0] === 71 && buffer[1] === 73 && buffer[2] === 70) return "gif";
120
- const ext = filePath.split(".").pop()?.toLowerCase();
121
- if (ext === "avif" || ext === "heic" || ext === "heif") {
122
- try {
123
- const metadata = await sharp(filePath).metadata();
124
- if (metadata.format === "heif") return "avif";
125
- } catch {
126
- return null;
127
- }
128
- }
129
- return null;
130
- } catch {
131
- return null;
132
- }
133
- }
134
-
135
- // src/pipeline/discover.ts
136
- async function collectImageFiles(inputPath, exclude = []) {
137
- const stats = await fs2.stat(inputPath);
138
- const isDirectory = stats.isDirectory();
139
- const files = [];
140
- if (isDirectory) {
141
- for await (const file of discoverFiles(inputPath, exclude)) {
142
- const fmt = await detectFormat(file);
143
- if (fmt) files.push({ path: file, format: fmt });
144
- }
145
- } else {
146
- const fmt = await detectFormat(inputPath);
147
- if (fmt) files.push({ path: inputPath, format: fmt });
148
- }
149
- return { files, isDirectory };
150
- }
151
- async function* discoverFiles(dirPath, exclude) {
152
- const entries = await fs2.readdir(dirPath, { withFileTypes: true });
153
- for (const entry of entries) {
154
- if (entry.name.startsWith(".")) continue;
155
- if (entry.isSymbolicLink()) continue;
156
- const fullPath = path2.resolve(dirPath, entry.name);
157
- if (entry.isDirectory()) {
158
- if (exclude?.includes(entry.name)) continue;
159
- yield* discoverFiles(fullPath, exclude);
160
- } else if (entry.isFile()) {
161
- yield fullPath;
162
- }
163
- }
164
- }
165
-
166
- // src/pipeline/optimize.ts
167
- import fs4 from "fs/promises";
168
- import sharp2 from "sharp";
169
-
170
- // src/pipeline/destination.ts
171
- import fs3 from "fs/promises";
172
- import path3 from "path";
173
- async function createDestinationPath(filePath, options) {
174
- const relativePath = path3.relative(options.sourcePath, filePath);
175
- if (relativePath.startsWith("..") || path3.isAbsolute(relativePath)) {
176
- throw new Error(`Path traversal detected: ${filePath}`);
177
- }
178
- let outputPath = path3.join(path3.resolve(options.destination), options.outputFolder, relativePath);
179
- if (options.toFormat) {
180
- const ext = path3.extname(outputPath);
181
- outputPath = `${outputPath.slice(0, outputPath.length - ext.length)}.${options.toFormat}`;
182
- }
183
- await fs3.mkdir(path3.dirname(outputPath), { recursive: true });
184
- return outputPath;
185
- }
186
-
187
- // src/pipeline/optimize.ts
188
- function getFormatOptions(quality) {
189
- return {
190
- jpeg: { mozjpeg: true, quality },
191
- png: { quality },
192
- webp: { quality },
193
- avif: { quality },
194
- gif: {}
195
- };
196
- }
197
- async function optimizeImage(filePath, options) {
198
- const start = performance.now();
199
- const format = options.detectedFormat;
200
- const outputFormat = options.format ?? format;
201
- const destinationPath = await createDestinationPath(filePath, {
202
- destination: options.destination,
203
- outputFolder: options.outputFolder,
204
- sourcePath: options.sourcePath,
205
- toFormat: options.format
206
- });
207
- const formatOptions = getFormatOptions(options.quality);
208
- let pipeline = sharp2(filePath, {
209
- animated: format === "gif" || format === "webp" || format === "avif"
210
- });
211
- if (options.resize !== void 0) {
212
- pipeline = pipeline.resize(options.resize);
213
- }
214
- pipeline = pipeline.toFormat(
215
- outputFormat,
216
- formatOptions[outputFormat] ?? {}
217
- );
218
- await pipeline.toFile(destinationPath);
219
- const [originalStat, newStat] = await Promise.all([fs4.stat(filePath), fs4.stat(destinationPath)]);
220
- const duration = performance.now() - start;
221
- return {
222
- inputPath: filePath,
223
- outputPath: destinationPath,
224
- originalSize: originalStat.size,
225
- newSize: newStat.size,
226
- format: outputFormat,
227
- duration
228
- };
229
- }
230
-
231
- // src/validation/options.ts
232
- import { availableParallelism } from "os";
233
- function parseSize(input) {
234
- const match = /^(\d+(?:\.\d+)?)(b|kb|mb)$/i.exec(input);
235
- if (!match) {
236
- throw new Error(`Invalid size format: "${input}". Expected format like 200kb, 1mb, 500b`);
237
- }
238
- const value = Number(match[1]);
239
- const unit = match[2].toLowerCase();
240
- if (unit === "b") return value;
241
- if (unit === "kb") return value * 1024;
242
- return value * 1024 * 1024;
243
- }
244
- var VALID_FORMATS = ["jpeg", "jpg", "png", "webp", "gif", "avif"];
245
- var DEFAULTS = {
246
- quality: 70,
247
- destination: ".",
248
- outputFolder: "bulki-output"
249
- };
250
- function validateOptions(raw) {
251
- const quality = raw.quality ?? DEFAULTS.quality;
252
- if (typeof quality !== "number" || !Number.isInteger(quality) || quality < 1 || quality > 100) {
253
- throw new Error(`Invalid quality: must be an integer between 1 and 100`);
254
- }
255
- let format = raw.format;
256
- if (format !== void 0 && !VALID_FORMATS.includes(format)) {
257
- throw new Error(`Invalid format: "${format}" is not a supported format`);
258
- }
259
- if (format === "jpg") format = "jpeg";
260
- const resize = raw.resize;
261
- if (resize !== void 0) {
262
- if (typeof resize !== "number" || !Number.isInteger(resize) || resize <= 0) {
263
- throw new Error(`Invalid resize: must be a positive integer`);
264
- }
265
- }
266
- const concurrency = raw.concurrency ?? availableParallelism();
267
- if (typeof concurrency !== "number" || concurrency < 1 || concurrency > 32) {
268
- throw new Error(`Invalid concurrency: must be between 1 and 32`);
269
- }
270
- let maxSize;
271
- if (raw.maxSize !== void 0) {
272
- maxSize = parseSize(raw.maxSize);
273
- }
274
- const outputFolder = raw.outputFolder ?? DEFAULTS.outputFolder;
275
- if (outputFolder.includes("/") || outputFolder.includes("\\") || outputFolder.includes("..")) {
276
- throw new Error("Invalid outputFolder: must be a simple directory name without path separators");
277
- }
278
- return {
279
- quality,
280
- format,
281
- resize,
282
- destination: raw.destination ?? DEFAULTS.destination,
283
- outputFolder,
284
- concurrency,
285
- exclude: raw.exclude ?? [],
286
- dryRun: raw.dryRun ?? false,
287
- verbose: raw.verbose ?? false,
288
- json: raw.json ?? false,
289
- maxSize
290
- };
291
- }
292
-
293
- // src/commands/hook.ts
294
- import fs5 from "fs/promises";
295
- import path4 from "path";
296
- import { Command } from "commander";
297
- var HOOK_MARKER = "# bulki-pre-commit-hook";
298
- var HOOK_END_MARKER = "# bulki-pre-commit-hook-end";
299
- var HOOK_SCRIPT = `#!/bin/sh
300
- ${HOOK_MARKER}
301
- # Auto-optimize staged images before commit
302
- # Installed by: bulki hook install
303
- # Remove with: bulki hook uninstall
304
-
305
- STAGED=$(git diff --cached --name-only -z --diff-filter=ACM | tr '\\0' '\\n' | grep -iE '\\.(jpe?g|png|webp|gif|avif)$')
306
-
307
- if [ -z "$STAGED" ]; then
308
- exit 0
309
- fi
310
-
311
- echo "bulki: optimizing staged images..."
312
-
313
- tmpdir=$(mktemp -d)
314
- trap 'rm -rf "$tmpdir"' EXIT
315
-
316
- echo "$STAGED" | while IFS= read -r img; do
317
- if [ -f "$img" ]; then
318
- ext="\${img##*.}"
319
- case "$(echo "$ext" | tr '[:upper:]' '[:lower:]')" in
320
- jpg|jpeg) fmt="jpeg" ;;
321
- png) fmt="png" ;;
322
- webp) fmt="webp" ;;
323
- gif) fmt="gif" ;;
324
- avif) fmt="avif" ;;
325
- *) continue ;;
326
- esac
327
- tmpfile="$tmpdir/$(basename "$img")"
328
- if npx --yes @puntoycoma/bulki pipe -f "$fmt" -q 80 < "$img" > "$tmpfile" 2>/dev/null; then
329
- if [ -s "$tmpfile" ]; then
330
- mv "$tmpfile" "$img"
331
- git add "$img"
332
- fi
333
- fi
334
- fi
335
- done
336
-
337
- echo "bulki: done"
338
- ${HOOK_END_MARKER}
339
- `;
340
- async function installHook() {
341
- const gitDir = await findGitDir(process.cwd());
342
- if (!gitDir) {
343
- console.error("Not a git repository. Run this from inside a git project.");
344
- process.exit(1);
345
- }
346
- const hooksDir = path4.join(gitDir, "hooks");
347
- const hookPath = path4.join(hooksDir, "pre-commit");
348
- await fs5.mkdir(hooksDir, { recursive: true });
349
- try {
350
- const existing = await fs5.readFile(hookPath, "utf-8");
351
- if (existing.includes(HOOK_MARKER)) {
352
- console.log("bulki hook is already installed.");
353
- return;
354
- }
355
- await fs5.appendFile(hookPath, `
356
- ${HOOK_SCRIPT}`);
357
- await fs5.chmod(hookPath, 493);
358
- console.log("bulki hook appended to existing pre-commit hook.");
359
- } catch {
360
- await fs5.writeFile(hookPath, HOOK_SCRIPT, { mode: 493 });
361
- console.log("bulki pre-commit hook installed.");
362
- }
363
- console.log("Staged images will be auto-optimized before each commit.");
364
- }
365
- async function uninstallHook() {
366
- const gitDir = await findGitDir(process.cwd());
367
- if (!gitDir) {
368
- console.error("Not a git repository.");
369
- process.exit(1);
370
- }
371
- const hookPath = path4.join(gitDir, "hooks", "pre-commit");
372
- try {
373
- const content = await fs5.readFile(hookPath, "utf-8");
374
- if (!content.includes(HOOK_MARKER)) {
375
- console.log("No bulki hook found in pre-commit.");
376
- return;
377
- }
378
- const lines = content.split("\n");
379
- const markerIndex = lines.findIndex((l) => l.includes(HOOK_MARKER));
380
- let endIndex = lines.findIndex((l) => l.includes(HOOK_END_MARKER));
381
- if (endIndex === -1) endIndex = lines.length - 1;
382
- const before = lines.slice(0, markerIndex).join("\n");
383
- const after = lines.slice(endIndex + 1).join("\n");
384
- const trimmed = `${before}
385
- ${after}`.trim();
386
- if (trimmed === "" || trimmed === "#!/bin/sh") {
387
- await fs5.unlink(hookPath);
388
- console.log("bulki pre-commit hook removed.");
389
- } else {
390
- await fs5.writeFile(hookPath, `${trimmed}
391
- `, { mode: 493 });
392
- console.log("bulki section removed from pre-commit hook.");
393
- }
394
- } catch {
395
- console.log("No pre-commit hook found.");
396
- }
397
- }
398
- var hookCommand = new Command("hook").description(
399
- "Manage git pre-commit hook for auto image optimization"
400
- );
401
- hookCommand.command("install").description("Install pre-commit hook to optimize images on commit").action(async () => {
402
- await installHook();
403
- });
404
- hookCommand.command("uninstall").description("Remove the bulki pre-commit hook").action(async () => {
405
- await uninstallHook();
406
- });
407
- async function findGitDir(startPath) {
408
- let current = path4.resolve(startPath);
409
- while (true) {
410
- const gitPath = path4.join(current, ".git");
411
- try {
412
- await fs5.stat(gitPath);
413
- return gitPath;
414
- } catch {
415
- const parent = path4.dirname(current);
416
- if (parent === current) return null;
417
- current = parent;
418
- }
419
- }
420
- }
421
-
422
- // src/commands/watch.ts
423
- import fs6 from "fs";
424
- import path5 from "path";
425
- import { Command as Command2 } from "commander";
426
- async function startWatch(dirPath, opts = {}) {
427
- const resolvedPath = path5.resolve(dirPath);
428
- try {
429
- const stats = await fs6.promises.stat(resolvedPath);
430
- if (!stats.isDirectory()) {
431
- console.error("Watch path must be a directory");
432
- process.exit(1);
433
- }
434
- } catch {
435
- console.error(`Path not found: ${resolvedPath}`);
436
- process.exit(1);
437
- }
438
- const quality = opts.quality ?? DEFAULTS.quality;
439
- const format = opts.format === "jpg" ? "jpeg" : opts.format;
440
- const resize = opts.resize;
441
- const destination = opts.destination ?? DEFAULTS.destination;
442
- const outputFolder = opts.outputFolder ?? DEFAULTS.outputFolder;
443
- console.log(`Watching ${resolvedPath} for new images...`);
444
- console.log("Press Ctrl+C to stop.\n");
445
- if (process.platform === "linux") {
446
- console.log(
447
- "Note: recursive watching may not work on all Linux systems. Use macOS or Windows for reliable subdirectory monitoring."
448
- );
449
- }
450
- const processing = /* @__PURE__ */ new Set();
451
- let activeJobs = 0;
452
- const maxJobs = 4;
453
- const watcher = fs6.watch(resolvedPath, { recursive: true }, async (_eventType, filename) => {
454
- if (!filename) return;
455
- const fullPath = path5.resolve(resolvedPath, filename);
456
- if (processing.has(fullPath)) return;
457
- processing.add(fullPath);
458
- while (activeJobs >= maxJobs) {
459
- await new Promise((r) => setTimeout(r, 100));
460
- }
461
- activeJobs++;
462
- const resolvedDest = destination === "." ? resolvedPath : path5.resolve(destination);
463
- const outputDir = path5.join(resolvedDest, outputFolder);
464
- if (fullPath === outputDir || fullPath.startsWith(`${outputDir}${path5.sep}`)) {
465
- processing.delete(fullPath);
466
- return;
467
- }
468
- await new Promise((r) => setTimeout(r, 100));
469
- try {
470
- await fs6.promises.stat(fullPath);
471
- } catch {
472
- processing.delete(fullPath);
473
- return;
474
- }
475
- const detectedFormat = await detectFormat(fullPath);
476
- if (!detectedFormat) {
477
- processing.delete(fullPath);
478
- return;
479
- }
480
- try {
481
- const result = await optimizeImage(fullPath, {
482
- quality,
483
- format,
484
- resize,
485
- destination,
486
- outputFolder,
487
- concurrency: 1,
488
- exclude: [],
489
- dryRun: false,
490
- verbose: false,
491
- json: false,
492
- sourcePath: resolvedPath,
493
- detectedFormat
494
- });
495
- console.log(formatFileResult(result));
496
- } catch (err) {
497
- console.error(`Error processing ${filename}: ${err.message}`);
498
- } finally {
499
- activeJobs--;
500
- setTimeout(() => processing.delete(fullPath), 1e3);
501
- }
502
- });
503
- process.on("SIGINT", () => {
504
- watcher.close();
505
- console.log("\nWatch stopped.");
506
- process.exit(0);
507
- });
508
- }
509
- var watchCommand = new Command2("watch").description("Watch a directory and auto-optimize new images").argument("<path>", "Directory to watch").option("-q, --quality <n>", "Output quality (1-100)", (v) => parseInt(v, 10), 70).option("-f, --format <fmt>", "Output format (jpeg, png, webp, gif, avif)").option("-r, --resize <width>", "Resize to width (px)", (v) => parseInt(v, 10)).option("-d, --destination <path>", "Destination directory", ".").option("-o, --output-folder <name>", "Output subfolder name", "bulki-output").action(async (dirPath, opts) => {
510
- await startWatch(dirPath, opts);
511
- });
512
-
513
- export {
514
- processPool,
515
- formatFileResult,
516
- buildJsonOutput,
517
- formatSummary,
518
- detectFormat,
519
- collectImageFiles,
520
- getFormatOptions,
521
- optimizeImage,
522
- parseSize,
523
- VALID_FORMATS,
524
- DEFAULTS,
525
- validateOptions,
526
- installHook,
527
- uninstallHook,
528
- hookCommand,
529
- startWatch,
530
- watchCommand
531
- };
532
- //# sourceMappingURL=chunk-7TFBGWBB.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/concurrency/pool.ts","../src/output/results.ts","../src/output/format-bytes.ts","../src/pipeline/discover.ts","../src/pipeline/filter.ts","../src/pipeline/optimize.ts","../src/pipeline/destination.ts","../src/validation/options.ts","../src/commands/hook.ts","../src/commands/watch.ts"],"sourcesContent":["export async function processPool<T, R>(\n\titems: T[],\n\tprocessor: (item: T) => Promise<R>,\n\tconcurrency: number,\n): Promise<R[]> {\n\tconst results: R[] = new Array(items.length)\n\tconst executing = new Set<Promise<void>>()\n\tlet firstError: unknown\n\tlet hasError = false\n\n\tfor (let i = 0; i < items.length; i++) {\n\t\tconst index = i\n\t\tconst p: Promise<void> = processor(items[index])\n\t\t\t.then((result) => {\n\t\t\t\tresults[index] = result\n\t\t\t})\n\t\t\t.catch((err) => {\n\t\t\t\tif (!hasError) {\n\t\t\t\t\thasError = true\n\t\t\t\t\tfirstError = err\n\t\t\t\t}\n\t\t\t})\n\t\t\t.finally(() => {\n\t\t\t\texecuting.delete(p)\n\t\t\t})\n\t\texecuting.add(p)\n\n\t\tif (executing.size >= concurrency) {\n\t\t\tawait Promise.race(executing)\n\t\t}\n\t}\n\n\tawait Promise.all(executing)\n\n\tif (hasError) {\n\t\tthrow firstError\n\t}\n\n\treturn results\n}\n","import path from 'node:path'\nimport type { FileResult, JsonError, JsonOutput } from '../types/index.js'\nimport { formatBytes } from './format-bytes.js'\n\nfunction calcReduction(originalSize: number, newSize: number): number {\n\treturn originalSize > 0 ? Math.round((1 - newSize / originalSize) * 100) : 0\n}\n\nexport type { FileResult }\n\nexport function formatFileResult(result: FileResult): string {\n\tconst filename = path.basename(result.inputPath)\n\tconst original = formatBytes(result.originalSize)\n\tconst newSize = formatBytes(result.newSize)\n\tconst reduction = calcReduction(result.originalSize, result.newSize)\n\tconst pct = reduction < 0 ? `+${Math.abs(reduction)}%` : `${reduction}%`\n\treturn `${filename} ${original} -> ${newSize} (${pct})`\n}\n\nexport function buildJsonOutput(\n\tresults: FileResult[],\n\terrors: JsonError[],\n\ttotalDuration: number,\n): JsonOutput {\n\tconst totalOriginal = results.reduce((sum, r) => sum + r.originalSize, 0)\n\tconst totalNew = results.reduce((sum, r) => sum + r.newSize, 0)\n\tconst totalReduction = calcReduction(totalOriginal, totalNew)\n\n\treturn {\n\t\tfiles: results.map((r) => ({\n\t\t\tinput: r.inputPath,\n\t\t\toutput: r.outputPath,\n\t\t\toriginalSize: r.originalSize,\n\t\t\tnewSize: r.newSize,\n\t\t\treduction: calcReduction(r.originalSize, r.newSize),\n\t\t\tformat: r.format,\n\t\t\tduration: r.duration,\n\t\t})),\n\t\tsummary: {\n\t\t\ttotalFiles: results.length,\n\t\t\ttotalOriginalSize: totalOriginal,\n\t\t\ttotalNewSize: totalNew,\n\t\t\ttotalReduction,\n\t\t\tduration: totalDuration,\n\t\t\terrors: errors.length,\n\t\t},\n\t\terrors,\n\t}\n}\n\nexport function formatSummary(\n\tresults: FileResult[],\n\terrors: Error[],\n\ttotalDuration: number,\n): string {\n\tconst totalOriginal = results.reduce((sum, r) => sum + r.originalSize, 0)\n\tconst totalNew = results.reduce((sum, r) => sum + r.newSize, 0)\n\tconst reduction = totalOriginal > 0 ? Math.round((1 - totalNew / totalOriginal) * 100) : 0\n\tconst duration = `${(totalDuration / 1000).toFixed(1)}s`\n\tconst fileCount = results.length\n\tlet summary = `${fileCount} files | ${formatBytes(totalOriginal)} -> ${formatBytes(totalNew)} (${reduction}%) | ${duration}`\n\tif (errors.length > 0) {\n\t\tsummary += ` | ${errors.length} errors`\n\t}\n\treturn summary\n}\n","export function formatBytes(bytes: number): string {\n\tif (bytes <= 0) return '0 B'\n\tconst GB = 1024 * 1024 * 1024\n\tconst MB = 1024 * 1024\n\tconst KB = 1024\n\tif (bytes >= GB) return `${(bytes / GB).toFixed(2)} GB`\n\tif (bytes >= MB) return `${(bytes / MB).toFixed(2)} MB`\n\tif (bytes >= KB) return `${(bytes / KB).toFixed(2)} KB`\n\treturn `${bytes} B`\n}\n","import fs from 'node:fs/promises'\nimport path from 'node:path'\nimport { detectFormat } from './filter.js'\n\nexport async function collectImageFiles(\n\tinputPath: string,\n\texclude: string[] = [],\n): Promise<{ files: { path: string; format: string }[]; isDirectory: boolean }> {\n\tconst stats = await fs.stat(inputPath)\n\tconst isDirectory = stats.isDirectory()\n\tconst files: { path: string; format: string }[] = []\n\n\tif (isDirectory) {\n\t\tfor await (const file of discoverFiles(inputPath, exclude)) {\n\t\t\tconst fmt = await detectFormat(file)\n\t\t\tif (fmt) files.push({ path: file, format: fmt })\n\t\t}\n\t} else {\n\t\tconst fmt = await detectFormat(inputPath)\n\t\tif (fmt) files.push({ path: inputPath, format: fmt })\n\t}\n\n\treturn { files, isDirectory }\n}\n\nexport async function* discoverFiles(dirPath: string, exclude?: string[]): AsyncGenerator<string> {\n\tconst entries = await fs.readdir(dirPath, { withFileTypes: true })\n\tfor (const entry of entries) {\n\t\tif (entry.name.startsWith('.')) continue\n\t\tif (entry.isSymbolicLink()) continue\n\t\tconst fullPath = path.resolve(dirPath, entry.name)\n\t\tif (entry.isDirectory()) {\n\t\t\tif (exclude?.includes(entry.name)) continue\n\t\t\tyield* discoverFiles(fullPath, exclude)\n\t\t} else if (entry.isFile()) {\n\t\t\tyield fullPath\n\t\t}\n\t}\n}\n","import fs from 'node:fs/promises'\nimport sharp from 'sharp'\n\nexport async function detectFormat(filePath: string): Promise<string | null> {\n\ttry {\n\t\tconst handle = await fs.open(filePath, 'r')\n\t\tconst buffer = Buffer.alloc(12)\n\t\ttry {\n\t\t\tawait handle.read(buffer, 0, 12, 0)\n\t\t} finally {\n\t\t\tawait handle.close()\n\t\t}\n\n\t\t// JPEG\n\t\tif (buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff) return 'jpeg'\n\t\t// PNG\n\t\tif (\n\t\t\tbuffer[0] === 0x89 &&\n\t\t\tbuffer[1] === 0x50 &&\n\t\t\tbuffer[2] === 0x4e &&\n\t\t\tbuffer[3] === 0x47 &&\n\t\t\tbuffer[4] === 0x0d &&\n\t\t\tbuffer[5] === 0x0a &&\n\t\t\tbuffer[6] === 0x1a &&\n\t\t\tbuffer[7] === 0x0a\n\t\t)\n\t\t\treturn 'png'\n\t\t// WebP (RIFF....WEBP)\n\t\tif (\n\t\t\tbuffer[0] === 0x52 &&\n\t\t\tbuffer[1] === 0x49 &&\n\t\t\tbuffer[2] === 0x46 &&\n\t\t\tbuffer[3] === 0x46 &&\n\t\t\tbuffer[8] === 0x57 &&\n\t\t\tbuffer[9] === 0x45 &&\n\t\t\tbuffer[10] === 0x42 &&\n\t\t\tbuffer[11] === 0x50\n\t\t)\n\t\t\treturn 'webp'\n\t\t// GIF\n\t\tif (buffer[0] === 0x47 && buffer[1] === 0x49 && buffer[2] === 0x46) return 'gif'\n\n\t\t// AVIF fallback via Sharp (ISOBMFF container makes magic bytes unreliable)\n\t\t// Only try Sharp for potential AVIF files (extension hint)\n\t\tconst ext = filePath.split('.').pop()?.toLowerCase()\n\t\tif (ext === 'avif' || ext === 'heic' || ext === 'heif') {\n\t\t\ttry {\n\t\t\t\tconst metadata = await sharp(filePath).metadata()\n\t\t\t\tif (metadata.format === 'heif') return 'avif'\n\t\t\t} catch {\n\t\t\t\treturn null\n\t\t\t}\n\t\t}\n\n\t\treturn null\n\t} catch {\n\t\treturn null\n\t}\n}\n\nexport async function isImage(filePath: string): Promise<boolean> {\n\treturn (await detectFormat(filePath)) !== null\n}\n","import fs from 'node:fs/promises'\nimport type { FormatEnum } from 'sharp'\nimport sharp from 'sharp'\nimport type { CompressOptions, FileResult, OptimizeParams } from '../types/index.js'\nimport { createDestinationPath } from './destination.js'\n\nexport function getFormatOptions(quality: number): Record<string, object> {\n\treturn {\n\t\tjpeg: { mozjpeg: true, quality },\n\t\tpng: { quality },\n\t\twebp: { quality },\n\t\tavif: { quality },\n\t\tgif: {},\n\t}\n}\n\nexport async function optimizeImage(\n\tfilePath: string,\n\toptions: OptimizeParams | (CompressOptions & { sourcePath: string; detectedFormat: string }),\n): Promise<FileResult> {\n\tconst start = performance.now()\n\n\tconst format = options.detectedFormat\n\n\tconst outputFormat = options.format ?? format\n\n\tconst destinationPath = await createDestinationPath(filePath, {\n\t\tdestination: options.destination,\n\t\toutputFolder: options.outputFolder,\n\t\tsourcePath: options.sourcePath,\n\t\ttoFormat: options.format,\n\t})\n\n\tconst formatOptions = getFormatOptions(options.quality)\n\n\tlet pipeline = sharp(filePath, {\n\t\tanimated: format === 'gif' || format === 'webp' || format === 'avif',\n\t})\n\n\tif (options.resize !== undefined) {\n\t\tpipeline = pipeline.resize(options.resize)\n\t}\n\n\tpipeline = pipeline.toFormat(\n\t\toutputFormat as keyof FormatEnum,\n\t\tformatOptions[outputFormat as keyof FormatEnum] ?? {},\n\t)\n\n\tawait pipeline.toFile(destinationPath)\n\n\tconst [originalStat, newStat] = await Promise.all([fs.stat(filePath), fs.stat(destinationPath)])\n\n\tconst duration = performance.now() - start\n\n\treturn {\n\t\tinputPath: filePath,\n\t\toutputPath: destinationPath,\n\t\toriginalSize: originalStat.size,\n\t\tnewSize: newStat.size,\n\t\tformat: outputFormat,\n\t\tduration,\n\t}\n}\n","import fs from 'node:fs/promises'\nimport path from 'node:path'\n\nexport async function createDestinationPath(\n\tfilePath: string,\n\toptions: {\n\t\tdestination: string\n\t\toutputFolder: string\n\t\tsourcePath: string\n\t\ttoFormat?: string\n\t},\n): Promise<string> {\n\tconst relativePath = path.relative(options.sourcePath, filePath)\n\n\t// Prevent path traversal — relative path must not escape the source\n\tif (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {\n\t\tthrow new Error(`Path traversal detected: ${filePath}`)\n\t}\n\n\tlet outputPath = path.join(path.resolve(options.destination), options.outputFolder, relativePath)\n\n\tif (options.toFormat) {\n\t\tconst ext = path.extname(outputPath)\n\t\toutputPath = `${outputPath.slice(0, outputPath.length - ext.length)}.${options.toFormat}`\n\t}\n\n\tawait fs.mkdir(path.dirname(outputPath), { recursive: true })\n\treturn outputPath\n}\n","import { availableParallelism } from 'node:os'\nimport type { CompressOptions } from '../types/index.js'\n\nexport interface RawOptions {\n\tquality: number\n\tformat: string\n\tresize: number\n\tdestination: string\n\toutputFolder: string\n\tconcurrency: number\n\texclude: string[]\n\tdryRun: boolean\n\tverbose: boolean\n\tjson: boolean\n\tmaxSize: string\n}\n\nexport function parseSize(input: string): number {\n\tconst match = /^(\\d+(?:\\.\\d+)?)(b|kb|mb)$/i.exec(input)\n\tif (!match) {\n\t\tthrow new Error(`Invalid size format: \"${input}\". Expected format like 200kb, 1mb, 500b`)\n\t}\n\tconst value = Number(match[1])\n\tconst unit = match[2].toLowerCase()\n\tif (unit === 'b') return value\n\tif (unit === 'kb') return value * 1024\n\treturn value * 1024 * 1024\n}\n\nexport const VALID_FORMATS = ['jpeg', 'jpg', 'png', 'webp', 'gif', 'avif']\n\nexport const DEFAULTS = {\n\tquality: 70,\n\tdestination: '.',\n\toutputFolder: 'bulki-output',\n} as const\n\nexport function validateOptions(raw: Partial<RawOptions>): CompressOptions {\n\tconst quality = raw.quality ?? DEFAULTS.quality\n\tif (typeof quality !== 'number' || !Number.isInteger(quality) || quality < 1 || quality > 100) {\n\t\tthrow new Error(`Invalid quality: must be an integer between 1 and 100`)\n\t}\n\n\tlet format = raw.format\n\tif (format !== undefined && !VALID_FORMATS.includes(format)) {\n\t\tthrow new Error(`Invalid format: \"${format}\" is not a supported format`)\n\t}\n\tif (format === 'jpg') format = 'jpeg'\n\n\tconst resize = raw.resize\n\tif (resize !== undefined) {\n\t\tif (typeof resize !== 'number' || !Number.isInteger(resize) || resize <= 0) {\n\t\t\tthrow new Error(`Invalid resize: must be a positive integer`)\n\t\t}\n\t}\n\n\tconst concurrency = raw.concurrency ?? availableParallelism()\n\tif (typeof concurrency !== 'number' || concurrency < 1 || concurrency > 32) {\n\t\tthrow new Error(`Invalid concurrency: must be between 1 and 32`)\n\t}\n\n\tlet maxSize: number | undefined\n\tif (raw.maxSize !== undefined) {\n\t\tmaxSize = parseSize(raw.maxSize)\n\t}\n\n\tconst outputFolder = raw.outputFolder ?? DEFAULTS.outputFolder\n\tif (outputFolder.includes('/') || outputFolder.includes('\\\\') || outputFolder.includes('..')) {\n\t\tthrow new Error('Invalid outputFolder: must be a simple directory name without path separators')\n\t}\n\n\treturn {\n\t\tquality,\n\t\tformat,\n\t\tresize,\n\t\tdestination: raw.destination ?? DEFAULTS.destination,\n\t\toutputFolder,\n\t\tconcurrency,\n\t\texclude: raw.exclude ?? [],\n\t\tdryRun: raw.dryRun ?? false,\n\t\tverbose: raw.verbose ?? false,\n\t\tjson: raw.json ?? false,\n\t\tmaxSize,\n\t}\n}\n","import fs from 'node:fs/promises'\nimport path from 'node:path'\nimport { Command } from 'commander'\n\nconst HOOK_MARKER = '# bulki-pre-commit-hook'\nconst HOOK_END_MARKER = '# bulki-pre-commit-hook-end'\n\nconst HOOK_SCRIPT = `#!/bin/sh\n${HOOK_MARKER}\n# Auto-optimize staged images before commit\n# Installed by: bulki hook install\n# Remove with: bulki hook uninstall\n\nSTAGED=$(git diff --cached --name-only -z --diff-filter=ACM | tr '\\\\0' '\\\\n' | grep -iE '\\\\.(jpe?g|png|webp|gif|avif)$')\n\nif [ -z \"$STAGED\" ]; then\n exit 0\nfi\n\necho \"bulki: optimizing staged images...\"\n\ntmpdir=$(mktemp -d)\ntrap 'rm -rf \"$tmpdir\"' EXIT\n\necho \"$STAGED\" | while IFS= read -r img; do\n if [ -f \"$img\" ]; then\n ext=\"\\${img##*.}\"\n case \"$(echo \"$ext\" | tr '[:upper:]' '[:lower:]')\" in\n jpg|jpeg) fmt=\"jpeg\" ;;\n png) fmt=\"png\" ;;\n webp) fmt=\"webp\" ;;\n gif) fmt=\"gif\" ;;\n avif) fmt=\"avif\" ;;\n *) continue ;;\n esac\n tmpfile=\"$tmpdir/$(basename \"$img\")\"\n if npx --yes @puntoycoma/bulki pipe -f \"$fmt\" -q 80 < \"$img\" > \"$tmpfile\" 2>/dev/null; then\n if [ -s \"$tmpfile\" ]; then\n mv \"$tmpfile\" \"$img\"\n git add \"$img\"\n fi\n fi\n fi\ndone\n\necho \"bulki: done\"\n${HOOK_END_MARKER}\n`\n\nexport async function installHook(): Promise<void> {\n\tconst gitDir = await findGitDir(process.cwd())\n\tif (!gitDir) {\n\t\tconsole.error('Not a git repository. Run this from inside a git project.')\n\t\tprocess.exit(1)\n\t}\n\n\tconst hooksDir = path.join(gitDir, 'hooks')\n\tconst hookPath = path.join(hooksDir, 'pre-commit')\n\n\tawait fs.mkdir(hooksDir, { recursive: true })\n\n\ttry {\n\t\tconst existing = await fs.readFile(hookPath, 'utf-8')\n\t\tif (existing.includes(HOOK_MARKER)) {\n\t\t\tconsole.log('bulki hook is already installed.')\n\t\t\treturn\n\t\t}\n\t\tawait fs.appendFile(hookPath, `\\n${HOOK_SCRIPT}`)\n\t\tawait fs.chmod(hookPath, 0o755)\n\t\tconsole.log('bulki hook appended to existing pre-commit hook.')\n\t} catch {\n\t\tawait fs.writeFile(hookPath, HOOK_SCRIPT, { mode: 0o755 })\n\t\tconsole.log('bulki pre-commit hook installed.')\n\t}\n\n\tconsole.log('Staged images will be auto-optimized before each commit.')\n}\n\nexport async function uninstallHook(): Promise<void> {\n\tconst gitDir = await findGitDir(process.cwd())\n\tif (!gitDir) {\n\t\tconsole.error('Not a git repository.')\n\t\tprocess.exit(1)\n\t}\n\n\tconst hookPath = path.join(gitDir, 'hooks', 'pre-commit')\n\n\ttry {\n\t\tconst content = await fs.readFile(hookPath, 'utf-8')\n\t\tif (!content.includes(HOOK_MARKER)) {\n\t\t\tconsole.log('No bulki hook found in pre-commit.')\n\t\t\treturn\n\t\t}\n\n\t\tconst lines = content.split('\\n')\n\t\tconst markerIndex = lines.findIndex((l) => l.includes(HOOK_MARKER))\n\n\t\tlet endIndex = lines.findIndex((l) => l.includes(HOOK_END_MARKER))\n\t\tif (endIndex === -1) endIndex = lines.length - 1\n\n\t\tconst before = lines.slice(0, markerIndex).join('\\n')\n\t\tconst after = lines.slice(endIndex + 1).join('\\n')\n\t\tconst trimmed = `${before}\\n${after}`.trim()\n\n\t\tif (trimmed === '' || trimmed === '#!/bin/sh') {\n\t\t\tawait fs.unlink(hookPath)\n\t\t\tconsole.log('bulki pre-commit hook removed.')\n\t\t} else {\n\t\t\tawait fs.writeFile(hookPath, `${trimmed}\\n`, { mode: 0o755 })\n\t\t\tconsole.log('bulki section removed from pre-commit hook.')\n\t\t}\n\t} catch {\n\t\tconsole.log('No pre-commit hook found.')\n\t}\n}\n\nexport const hookCommand = new Command('hook').description(\n\t'Manage git pre-commit hook for auto image optimization',\n)\n\nhookCommand\n\t.command('install')\n\t.description('Install pre-commit hook to optimize images on commit')\n\t.action(async () => {\n\t\tawait installHook()\n\t})\n\nhookCommand\n\t.command('uninstall')\n\t.description('Remove the bulki pre-commit hook')\n\t.action(async () => {\n\t\tawait uninstallHook()\n\t})\n\nasync function findGitDir(startPath: string): Promise<string | null> {\n\tlet current = path.resolve(startPath)\n\twhile (true) {\n\t\tconst gitPath = path.join(current, '.git')\n\t\ttry {\n\t\t\tawait fs.stat(gitPath)\n\t\t\treturn gitPath\n\t\t} catch {\n\t\t\tconst parent = path.dirname(current)\n\t\t\tif (parent === current) return null\n\t\t\tcurrent = parent\n\t\t}\n\t}\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport { Command } from 'commander'\nimport { formatFileResult } from '../output/results.js'\nimport { detectFormat } from '../pipeline/filter.js'\nimport { optimizeImage } from '../pipeline/optimize.js'\nimport { DEFAULTS } from '../validation/options.js'\n\nexport interface WatchOptions {\n\tquality?: number\n\tformat?: string\n\tresize?: number\n\tdestination?: string\n\toutputFolder?: string\n}\n\nexport async function startWatch(dirPath: string, opts: WatchOptions = {}): Promise<void> {\n\tconst resolvedPath = path.resolve(dirPath)\n\n\t// Verify path exists and is a directory\n\ttry {\n\t\tconst stats = await fs.promises.stat(resolvedPath)\n\t\tif (!stats.isDirectory()) {\n\t\t\tconsole.error('Watch path must be a directory')\n\t\t\tprocess.exit(1)\n\t\t}\n\t} catch {\n\t\tconsole.error(`Path not found: ${resolvedPath}`)\n\t\tprocess.exit(1)\n\t}\n\n\tconst quality = opts.quality ?? DEFAULTS.quality\n\tconst format = opts.format === 'jpg' ? 'jpeg' : opts.format\n\tconst resize = opts.resize\n\tconst destination = opts.destination ?? DEFAULTS.destination\n\tconst outputFolder = opts.outputFolder ?? DEFAULTS.outputFolder\n\n\tconsole.log(`Watching ${resolvedPath} for new images...`)\n\tconsole.log('Press Ctrl+C to stop.\\n')\n\n\tif (process.platform === 'linux') {\n\t\tconsole.log(\n\t\t\t'Note: recursive watching may not work on all Linux systems. Use macOS or Windows for reliable subdirectory monitoring.',\n\t\t)\n\t}\n\n\t// Track processed files to avoid duplicates (fs.watch can fire multiple events)\n\tconst processing = new Set<string>()\n\tlet activeJobs = 0\n\tconst maxJobs = 4\n\n\t// Use fs.watch with recursive option\n\tconst watcher = fs.watch(resolvedPath, { recursive: true }, async (_eventType, filename) => {\n\t\tif (!filename) return\n\n\t\tconst fullPath = path.resolve(resolvedPath, filename)\n\n\t\t// Skip if already processing this file\n\t\tif (processing.has(fullPath)) return\n\t\tprocessing.add(fullPath)\n\n\t\t// Concurrency limit\n\t\twhile (activeJobs >= maxJobs) {\n\t\t\tawait new Promise((r) => setTimeout(r, 100))\n\t\t}\n\t\tactiveJobs++\n\n\t\t// Skip files in the output folder to avoid infinite loops\n\t\tconst resolvedDest = destination === '.' ? resolvedPath : path.resolve(destination)\n\t\tconst outputDir = path.join(resolvedDest, outputFolder)\n\t\tif (fullPath === outputDir || fullPath.startsWith(`${outputDir}${path.sep}`)) {\n\t\t\tprocessing.delete(fullPath)\n\t\t\treturn\n\t\t}\n\n\t\t// Small delay to let file writes complete\n\t\tawait new Promise((r) => setTimeout(r, 100))\n\n\t\t// Check if file exists (could be a delete event)\n\t\ttry {\n\t\t\tawait fs.promises.stat(fullPath)\n\t\t} catch {\n\t\t\tprocessing.delete(fullPath)\n\t\t\treturn\n\t\t}\n\n\t\t// Check if it's an image\n\t\tconst detectedFormat = await detectFormat(fullPath)\n\t\tif (!detectedFormat) {\n\t\t\tprocessing.delete(fullPath)\n\t\t\treturn\n\t\t}\n\n\t\ttry {\n\t\t\tconst result = await optimizeImage(fullPath, {\n\t\t\t\tquality,\n\t\t\t\tformat,\n\t\t\t\tresize,\n\t\t\t\tdestination,\n\t\t\t\toutputFolder,\n\t\t\t\tconcurrency: 1,\n\t\t\t\texclude: [],\n\t\t\t\tdryRun: false,\n\t\t\t\tverbose: false,\n\t\t\t\tjson: false,\n\t\t\t\tsourcePath: resolvedPath,\n\t\t\t\tdetectedFormat,\n\t\t\t})\n\t\t\tconsole.log(formatFileResult(result))\n\t\t} catch (err) {\n\t\t\tconsole.error(`Error processing ${filename}: ${(err as Error).message}`)\n\t\t} finally {\n\t\t\tactiveJobs--\n\t\t\t// Remove from processing after a delay to debounce\n\t\t\tsetTimeout(() => processing.delete(fullPath), 1000)\n\t\t}\n\t})\n\n\t// Keep process alive and handle Ctrl+C\n\tprocess.on('SIGINT', () => {\n\t\twatcher.close()\n\t\tconsole.log('\\nWatch stopped.')\n\t\tprocess.exit(0)\n\t})\n}\n\nexport const watchCommand = new Command('watch')\n\t.description('Watch a directory and auto-optimize new images')\n\t.argument('<path>', 'Directory to watch')\n\t.option('-q, --quality <n>', 'Output quality (1-100)', (v) => parseInt(v, 10), 70)\n\t.option('-f, --format <fmt>', 'Output format (jpeg, png, webp, gif, avif)')\n\t.option('-r, --resize <width>', 'Resize to width (px)', (v) => parseInt(v, 10))\n\t.option('-d, --destination <path>', 'Destination directory', '.')\n\t.option('-o, --output-folder <name>', 'Output subfolder name', 'bulki-output')\n\t.action(async (dirPath: string, opts) => {\n\t\tawait startWatch(dirPath, opts)\n\t})\n"],"mappings":"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","names":["fs","path","fs","path","fs","sharp","fs","path","sharp","fs","fs","path","fs","path","Command","path","fs","Command"]}