@puntoycoma/bulki 3.0.2 → 3.0.4

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/output/format-bytes.ts","../src/pipeline/filter.ts","../src/pipeline/optimize.ts","../src/concurrency/pool.ts","../src/pipeline/encode.ts","../src/pipeline/paths.ts","../src/validation/options.ts","../src/pipeline/plan.ts","../src/pipeline/discover.ts","../src/commands/hook.ts","../src/commands/watch.ts","../src/output/results.ts"],"sourcesContent":["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 sharp from 'sharp'\n\n/** Detected as HEIC/HEIF (e.g. iPhone photos): libvips prebuilt binaries can't decode HEVC. */\nexport const UNSUPPORTED = 'unsupported'\n\nconst PNG_MAGIC = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])\nconst AVIF_BRANDS = new Set(['avif', 'avis'])\nconst HEIF_BRANDS = new Set(['heic', 'heix', 'hevc', 'hevx', 'heim', 'heis', 'mif1', 'msf1'])\n\n/**\n * Identify an image by its magic bytes, never by extension.\n * Returns 'jpeg' | 'png' | 'webp' | 'gif' | 'avif', UNSUPPORTED for HEIC/HEIF,\n * or null when the file is not an image.\n */\nexport async function detectFormat(filePath: string): Promise<string | null> {\n\tconst buffer = Buffer.alloc(12)\n\ttry {\n\t\tconst handle = await fs.open(filePath, 'r')\n\t\ttry {\n\t\t\tconst { bytesRead } = await handle.read(buffer, 0, 12, 0)\n\t\t\tif (bytesRead < 4) return null\n\t\t} finally {\n\t\t\tawait handle.close()\n\t\t}\n\t} catch {\n\t\treturn null\n\t}\n\n\tif (buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff) return 'jpeg'\n\tif (buffer.subarray(0, 8).equals(PNG_MAGIC)) return 'png'\n\tif (buffer.toString('latin1', 0, 4) === 'RIFF' && buffer.toString('latin1', 8, 12) === 'WEBP')\n\t\treturn 'webp'\n\tif (buffer.toString('latin1', 0, 3) === 'GIF') return 'gif'\n\n\t// ISOBMFF container: [box size][ftyp][major brand]\n\tif (buffer.toString('latin1', 4, 8) === 'ftyp') {\n\t\tconst brand = buffer.toString('latin1', 8, 12)\n\t\tif (AVIF_BRANDS.has(brand)) return 'avif'\n\t\tif (HEIF_BRANDS.has(brand)) {\n\t\t\t// `mif1` may wrap AV1 too — ask libvips which codec it really is\n\t\t\ttry {\n\t\t\t\tconst meta = await sharp(filePath).metadata()\n\t\t\t\tif (meta.format === 'heif' && meta.compression === 'av1') return 'avif'\n\t\t\t} catch {\n\t\t\t\t// header unreadable without the HEVC codec\n\t\t\t}\n\t\t\treturn UNSUPPORTED\n\t\t}\n\t}\n\n\treturn null\n}\n\nexport async function isImage(filePath: string): Promise<boolean> {\n\tconst fmt = await detectFormat(filePath)\n\treturn fmt !== null && fmt !== UNSUPPORTED\n}\n","import fs from 'node:fs/promises'\nimport path from 'node:path'\nimport { processPool } from '../concurrency/pool.js'\nimport type { EncodeSettings, FileResult, ImageJob, JsonError } from '../types/index.js'\nimport { encodeImage } from './encode.js'\n\nexport { getFormatOptions } from './encode.js'\n\n/** Optimize one image and write it to `job.output` (atomically: temp file + rename). */\nexport async function optimizeImage(job: ImageJob, settings: EncodeSettings): Promise<FileResult> {\n\tconst start = performance.now()\n\tconst input = await fs.readFile(job.input)\n\tconst result = await encodeImage(input, job.detectedFormat, settings, job.input)\n\n\tawait fs.mkdir(path.dirname(job.output), { recursive: true })\n\tconst tmp = `${job.output}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`\n\ttry {\n\t\tawait fs.writeFile(tmp, result.data)\n\t\tawait fs.rename(tmp, job.output)\n\t} catch (err) {\n\t\tawait fs.rm(tmp, { force: true })\n\t\tthrow err\n\t}\n\n\treturn {\n\t\tinputPath: job.input,\n\t\toutputPath: job.output,\n\t\toriginalSize: input.length,\n\t\tnewSize: result.data.length,\n\t\tformat: result.format,\n\t\tduration: performance.now() - start,\n\t\tkeptOriginal: result.keptOriginal,\n\t}\n}\n\nexport interface BatchResult {\n\tresults: FileResult[]\n\terrors: JsonError[]\n\tduration: number\n}\n\n/**\n * Run many jobs in parallel. Never throws: per-file failures are collected in `errors`.\n * When `signal` aborts, jobs that haven't started yet are skipped.\n */\nexport async function runBatch(\n\tjobs: ImageJob[],\n\tsettings: EncodeSettings,\n\tconcurrency: number,\n\tonFileDone?: (job: ImageJob, result: FileResult | null, error?: Error) => void,\n\tsignal?: AbortSignal,\n): Promise<BatchResult> {\n\tconst start = performance.now()\n\tconst errors: JsonError[] = []\n\tconst outcomes = await processPool(\n\t\tjobs,\n\t\tasync (job) => {\n\t\t\tif (signal?.aborted) return null\n\t\t\ttry {\n\t\t\t\tconst result = await optimizeImage(job, settings)\n\t\t\t\tonFileDone?.(job, result)\n\t\t\t\treturn result\n\t\t\t} catch (err) {\n\t\t\t\tconst error = err instanceof Error ? err : new Error(String(err))\n\t\t\t\terrors.push({ file: job.input, message: friendlyError(error) })\n\t\t\t\tonFileDone?.(job, null, error)\n\t\t\t\treturn null\n\t\t\t}\n\t\t},\n\t\tconcurrency,\n\t)\n\treturn {\n\t\tresults: outcomes.filter((r): r is FileResult => r !== null),\n\t\terrors,\n\t\tduration: performance.now() - start,\n\t}\n}\n\nexport function friendlyError(err: Error): string {\n\tconst code = (err as NodeJS.ErrnoException).code\n\tif (code === 'EACCES' || code === 'EPERM') return 'permission denied'\n\tif (code === 'ENOSPC') return 'disk is full'\n\tif (code === 'ENOENT') return 'file disappeared before it could be read'\n\tconst msg = err.message.split('\\n')[0]\n\tif (/VipsJpeg|premature end|corrupt|bad seek|unexpected end/i.test(msg))\n\t\treturn `damaged image (${msg})`\n\treturn msg\n}\n","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 { execFile } from 'node:child_process'\nimport fs from 'node:fs/promises'\nimport os from 'node:os'\nimport path from 'node:path'\nimport { promisify } from 'node:util'\nimport sharp from 'sharp'\nimport type { EncodeSettings, OutputFormat } from '../types/index.js'\nimport { UNSUPPORTED } from './filter.js'\n\n// Every file in a batch is different — libvips' operation cache only costs memory here.\nsharp.cache(false)\n\nconst execFileAsync = promisify(execFile)\n\n/** Formats sharp can write with more than one frame. */\nconst ANIMATED_OUTPUTS = new Set<string>(['gif', 'webp'])\nconst ANIMATED_INPUTS = new Set<string>(['gif', 'webp'])\n\nexport function getFormatOptions(quality: number): Record<OutputFormat, object> {\n\treturn {\n\t\tjpeg: { quality, mozjpeg: true },\n\t\t// `quality` only has an effect together with `palette` (lossy quantisation, like TinyPNG)\n\t\tpng: { quality, palette: quality < 100, compressionLevel: 9, adaptiveFiltering: true },\n\t\twebp: { quality },\n\t\tavif: { quality },\n\t\tgif: {},\n\t}\n}\n\n/** Format written when the user didn't ask for a conversion. */\nexport function defaultOutputFormat(detectedFormat: string): OutputFormat {\n\treturn detectedFormat === UNSUPPORTED ? 'jpeg' : (detectedFormat as OutputFormat)\n}\n\nexport function extensionFor(format: OutputFormat): string {\n\treturn format === 'jpeg' ? '.jpg' : `.${format}`\n}\n\nexport interface EncodeResult {\n\tdata: Buffer\n\tformat: OutputFormat\n\t/** true when the re-encoded image was larger, so the original bytes are returned unchanged */\n\tkeptOriginal: boolean\n}\n\n/**\n * Re-encode one image in memory.\n * - honours EXIF orientation (phone photos don't end up sideways)\n * - never upscales (`resize` is a maximum width)\n * - flattens transparency onto white for JPEG (instead of black)\n * - keeps animation when the output format supports it\n * - returns the original bytes if re-encoding to the same format made it bigger\n */\nexport async function encodeImage(\n\tinput: Buffer,\n\tdetectedFormat: string,\n\tsettings: EncodeSettings,\n\tsourcePath?: string,\n): Promise<EncodeResult> {\n\tconst outputFormat = settings.format ?? defaultOutputFormat(detectedFormat)\n\n\tlet source = input\n\tlet sourceFormat = detectedFormat\n\tif (detectedFormat === UNSUPPORTED) {\n\t\tsource = await decodeHeic(input, sourcePath)\n\t\tsourceFormat = 'jpeg'\n\t}\n\n\tconst animated = ANIMATED_INPUTS.has(sourceFormat) && ANIMATED_OUTPUTS.has(outputFormat)\n\tlet pipeline = sharp(source, { animated, failOn: 'error' })\n\tif (!animated) pipeline = pipeline.autoOrient()\n\tif (settings.resize !== undefined) {\n\t\tpipeline = pipeline.resize({ width: settings.resize, withoutEnlargement: true })\n\t}\n\tif (outputFormat === 'jpeg') pipeline = pipeline.flatten({ background: '#ffffff' })\n\n\tconst data = await pipeline\n\t\t.toFormat(outputFormat, getFormatOptions(settings.quality)[outputFormat])\n\t\t.toBuffer()\n\n\tif (\n\t\toutputFormat === sourceFormat &&\n\t\tdetectedFormat !== UNSUPPORTED &&\n\t\tdata.length >= input.length\n\t) {\n\t\t// Only safe to fall back when the original already satisfies the requested size\n\t\tconst fitsWidth =\n\t\t\tsettings.resize === undefined ||\n\t\t\t((await sharp(input).metadata()).width ?? Number.POSITIVE_INFINITY) <= settings.resize\n\t\tif (fitsWidth) return { data: input, format: outputFormat, keptOriginal: true }\n\t}\n\n\treturn { data, format: outputFormat, keptOriginal: false }\n}\n\n/**\n * The libvips build bundled with sharp can't decode HEVC (HEIC photos from iPhones).\n * On macOS the built-in `sips` tool can, so we convert through it.\n */\nasync function decodeHeic(input: Buffer, sourcePath?: string): Promise<Buffer> {\n\tif (process.platform !== 'darwin') {\n\t\tthrow new Error(\n\t\t\t'HEIC/HEIF photos are not supported on this system. Export them as JPEG first (or run bulki on macOS).',\n\t\t)\n\t}\n\tconst tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'bulki-heic-'))\n\ttry {\n\t\tlet inPath = sourcePath\n\t\tif (!inPath) {\n\t\t\tinPath = path.join(tmpDir, 'input.heic')\n\t\t\tawait fs.writeFile(inPath, input)\n\t\t}\n\t\tconst outPath = path.join(tmpDir, 'converted.jpg')\n\t\tawait execFileAsync('sips', [\n\t\t\t'-s',\n\t\t\t'format',\n\t\t\t'jpeg',\n\t\t\t'-s',\n\t\t\t'formatOptions',\n\t\t\t'best',\n\t\t\tinPath,\n\t\t\t'--out',\n\t\t\toutPath,\n\t\t])\n\t\treturn await fs.readFile(outPath)\n\t} catch (err) {\n\t\tthrow new Error(`Could not convert HEIC photo: ${(err as Error).message.split('\\n')[0]}`)\n\t} finally {\n\t\tawait fs.rm(tmpDir, { recursive: true, force: true })\n\t}\n}\n","import fs from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst isPosix = path.sep === '/'\n\n/**\n * Normalize ONE path the way a human typed, pasted or drag-and-dropped it.\n *\n * Terminals hand us the same folder in many shapes:\n * '/Users/me/My Photos' (macOS Terminal / iTerm drag & drop: single quotes)\n * /Users/me/My\\ Photos (shell-escaped spaces)\n * \"C:\\Users\\me\\My Photos\" (Windows \"Copy as path\")\n * file:///Users/me/My%20Photos\n * usually with a trailing space. All of them must resolve to the same folder.\n */\nexport function cleanPathInput(raw: string): string {\n\tlet value = raw.trim()\n\n\twhile (\n\t\tvalue.length >= 2 &&\n\t\t((value.startsWith(\"'\") && value.endsWith(\"'\")) ||\n\t\t\t(value.startsWith('\"') && value.endsWith('\"')))\n\t) {\n\t\tvalue = value.slice(1, -1).trim()\n\t}\n\n\tif (value.startsWith('file://')) {\n\t\ttry {\n\t\t\tvalue = fileURLToPath(value)\n\t\t} catch {\n\t\t\t// not a valid file URL — keep as typed\n\t\t}\n\t}\n\n\t// Shell escapes (`My\\ Photos`) — only on POSIX, where `\\` is never a separator\n\tif (isPosix) value = value.replace(/\\\\(.)/g, '$1')\n\n\treturn expandHome(value)\n}\n\nfunction expandHome(value: string): string {\n\tif (value === '~') return os.homedir()\n\tif (value.startsWith('~/') || value.startsWith('~\\\\'))\n\t\treturn path.join(os.homedir(), value.slice(2))\n\treturn value\n}\n\n/**\n * Split shell-like input into words, honoring quotes and backslash escapes.\n * Dragging several files into a terminal produces `'/a/one.jpg' '/a/two.jpg' `.\n */\nexport function splitShellWords(raw: string): string[] {\n\tconst words: string[] = []\n\tlet current = ''\n\tlet quote: \"'\" | '\"' | null = null\n\tlet hasWord = false\n\tfor (let i = 0; i < raw.length; i++) {\n\t\tconst ch = raw[i]\n\t\tif (quote) {\n\t\t\tif (ch === quote) quote = null\n\t\t\telse if (ch === '\\\\' && quote === '\"' && isPosix && i + 1 < raw.length) current += raw[++i]\n\t\t\telse current += ch\n\t\t} else if (ch === \"'\" || ch === '\"') {\n\t\t\tquote = ch\n\t\t\thasWord = true\n\t\t} else if (ch === '\\\\' && isPosix && i + 1 < raw.length) {\n\t\t\tcurrent += raw[++i]\n\t\t\thasWord = true\n\t\t} else if (/\\s/.test(ch)) {\n\t\t\tif (hasWord) words.push(current)\n\t\t\tcurrent = ''\n\t\t\thasWord = false\n\t\t} else {\n\t\t\tcurrent += ch\n\t\t\thasWord = true\n\t\t}\n\t}\n\tif (hasWord) words.push(current)\n\treturn words.map(expandHome)\n}\n\n/** cleanPathInput + make absolute. */\nexport function resolveUserPath(raw: string, cwd: string = process.cwd()): string {\n\treturn path.resolve(cwd, cleanPathInput(raw))\n}\n\nexport type ParsedPaths = { ok: true; paths: string[] } | { ok: false; error: string }\n\n/**\n * Turn free-form user input (typed, pasted or dropped) into one or more existing\n * absolute paths. Never throws — returns a human readable error instead.\n */\nexport function parsePathInput(raw: string | undefined, cwd: string = process.cwd()): ParsedPaths {\n\tif (!raw || raw.trim() === '') return { ok: false, error: 'Please enter a path' }\n\n\t// 1. The whole thing is a single path (most common, handles spaces without quotes)\n\tconst single = resolveUserPath(raw, cwd)\n\tif (fs.existsSync(single)) return { ok: true, paths: [single] }\n\n\t// 2. Several dropped files / quoted paths\n\tconst words = splitShellWords(raw.trim())\n\tif (words.length > 1) {\n\t\tconst resolved = words.map((w) => path.resolve(cwd, w))\n\t\tconst missing = resolved.filter((p) => !fs.existsSync(p))\n\t\tif (missing.length === 0) return { ok: true, paths: resolved }\n\t}\n\n\treturn {\n\t\tok: false,\n\t\terror: `Can't find \"${single}\". Check the spelling, or drag the folder into this window.`,\n\t}\n}\n\n/** True when `child` is `parent` or lives inside it. Both must be absolute. */\nexport function isInside(child: string, parent: string): boolean {\n\tconst rel = path.relative(parent, child)\n\treturn rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel))\n}\n\nexport function samePath(a: string, b: string): boolean {\n\tconst ra = path.resolve(a)\n\tconst rb = path.resolve(b)\n\t// macOS and Windows file systems are case-insensitive by default\n\treturn process.platform === 'linux' ? ra === rb : ra.toLowerCase() === rb.toLowerCase()\n}\n\n/** Deepest directory that contains every given absolute directory. */\nexport function commonDir(dirs: string[]): string {\n\tif (dirs.length === 0) return process.cwd()\n\tlet common = path.resolve(dirs[0])\n\tfor (const d of dirs.slice(1)) {\n\t\twhile (!isInside(path.resolve(d), common)) {\n\t\t\tconst parent = path.dirname(common)\n\t\t\tif (parent === common) return common\n\t\t\tcommon = parent\n\t\t}\n\t}\n\treturn common\n}\n\n/** Absolute path shown to humans, with the home dir shortened to `~`. */\nexport function displayPath(absPath: string): string {\n\tconst home = os.homedir()\n\tif (absPath === home) return '~'\n\tif (absPath.startsWith(home + path.sep)) return `~${absPath.slice(home.length)}`\n\treturn absPath\n}\n","import { availableParallelism } from 'node:os'\nimport path from 'node:path'\nimport { cleanPathInput } from '../pipeline/paths.js'\nimport type { CompressOptions, EncodeSettings, OutputFormat } from '../types/index.js'\n\nexport interface RawOptions {\n\tquality: number\n\tformat: string\n\tresize: number\n\t/** Folder where optimized images are written */\n\toutput: string\n\t/** @deprecated legacy: base dir, combined with outputFolder → `<destination>/<outputFolder>` */\n\tdestination: string\n\t/** @deprecated legacy: subfolder name inside `destination` */\n\toutputFolder: string\n\tconcurrency: number\n\texclude: string[]\n\tdryRun: boolean\n\tverbose: boolean\n\tjson: boolean\n\tmaxSize: string\n}\n\nexport const DEFAULT_OUTPUT_FOLDER = 'bulki-output'\n\nexport const DEFAULTS = {\n\tquality: 70,\n\toutput: `./${DEFAULT_OUTPUT_FOLDER}`,\n} as const\n\nexport const OUTPUT_FORMATS: OutputFormat[] = ['jpeg', 'png', 'webp', 'gif', 'avif']\nexport const VALID_FORMATS = ['jpeg', 'jpg', 'png', 'webp', 'gif', 'avif']\n\n/** libvips already parallelises each image internally; more than 8 jobs only adds memory pressure. */\nexport function defaultConcurrency(): number {\n\treturn Math.max(1, Math.min(availableParallelism(), 8))\n}\n\nexport function parseSize(input: string): number {\n\tconst match = /^(\\d+(?:\\.\\d+)?)\\s*(b|kb|mb)$/i.exec(input.trim())\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\n/** Strict integer parsing for CLI flags: \"80\" → 80, \"80abc\" / \"abc\" / \"\" → NaN. */\nexport function parseInteger(value: string): number {\n\treturn /^\\s*-?\\d+\\s*$/.test(value) ? Number(value) : Number.NaN\n}\n\nexport function normalizeFormat(format: string | undefined): OutputFormat | undefined {\n\tif (format === undefined || format === '') return undefined\n\tconst f = format.toLowerCase()\n\tif (!VALID_FORMATS.includes(f)) {\n\t\tthrow new Error(\n\t\t\t`Invalid format: \"${format}\" is not a supported format. Use one of: ${OUTPUT_FORMATS.join(', ')}`,\n\t\t)\n\t}\n\treturn (f === 'jpg' ? 'jpeg' : f) as OutputFormat\n}\n\nexport function validateEncodeSettings(raw: {\n\tquality?: unknown\n\tformat?: string\n\tresize?: unknown\n}): EncodeSettings {\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\tconst format = normalizeFormat(raw.format)\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 (width in pixels)')\n\t\t}\n\t}\n\n\treturn { quality, format, resize: resize as number | undefined }\n}\n\n/**\n * Work out the absolute output folder.\n *\n * --output <dir> → <dir>\n * (nothing) → ./bulki-output\n * Legacy flags (still accepted so existing scripts and the GitHub Action keep working):\n * --destination <base> → <base>/bulki-output\n * --destination <base> --output <x> → <base>/<x> (x relative)\n * --output-folder <name> → <base or .>/<name>\n */\nexport function resolveOutputDir(\n\traw: Partial<Pick<RawOptions, 'output' | 'destination' | 'outputFolder'>>,\n\tcwd: string = process.cwd(),\n): string {\n\tconst base =\n\t\traw.destination !== undefined ? path.resolve(cwd, cleanPathInput(raw.destination)) : cwd\n\n\tif (raw.output !== undefined && raw.output.trim() !== '') {\n\t\treturn path.resolve(base, cleanPathInput(raw.output))\n\t}\n\n\tconst folder = raw.outputFolder ?? DEFAULT_OUTPUT_FOLDER\n\tif (\n\t\tfolder.includes('/') ||\n\t\tfolder.includes('\\\\') ||\n\t\tfolder.includes('..') ||\n\t\tfolder.trim() === ''\n\t) {\n\t\tthrow new Error(\n\t\t\t'Invalid outputFolder: must be a simple directory name. Use --output <path> to choose any folder.',\n\t\t)\n\t}\n\treturn path.resolve(base, folder)\n}\n\nexport function validateOptions(\n\traw: Partial<RawOptions>,\n\tcwd: string = process.cwd(),\n): CompressOptions {\n\tconst settings = validateEncodeSettings(raw)\n\n\tconst concurrency = raw.concurrency ?? defaultConcurrency()\n\tif (\n\t\ttypeof concurrency !== 'number' ||\n\t\t!Number.isInteger(concurrency) ||\n\t\tconcurrency < 1 ||\n\t\tconcurrency > 32\n\t) {\n\t\tthrow new Error('Invalid concurrency: must be an integer 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\treturn {\n\t\t...settings,\n\t\toutput: resolveOutputDir(raw, cwd),\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 { processPool } from '../concurrency/pool.js'\nimport type { ImageJob, OutputFormat } from '../types/index.js'\nimport { discoverFiles } from './discover.js'\nimport { defaultOutputFormat, extensionFor } from './encode.js'\nimport { detectFormat, UNSUPPORTED } from './filter.js'\nimport { commonDir, isInside, samePath } from './paths.js'\n\nexport class PlanError extends Error {}\n\nexport interface Scan {\n\tinputs: string[]\n\t/** Folder whose structure is mirrored inside the output folder */\n\tsourceRoot: string\n\timages: { file: string; detectedFormat: string }[]\n}\n\nexport interface Plan {\n\tsourceRoot: string\n\toutput: string\n\tjobs: ImageJob[]\n\t/** Images that were found but won't be processed, with the reason */\n\tskipped: { file: string; reason: string }[]\n}\n\n/** Find every image under the given files/folders (absolute paths). Nothing is written. */\nexport async function scanInputs(inputs: string[], exclude: string[] = []): Promise<Scan> {\n\tif (inputs.length === 0) throw new PlanError('No input path given')\n\n\tconst candidates: string[] = []\n\tconst dirs: string[] = []\n\tfor (const input of inputs) {\n\t\tconst stat = await fs.stat(input).catch(() => null)\n\t\tif (!stat) throw new PlanError(`Path not found: ${input}`)\n\t\tif (stat.isDirectory()) {\n\t\t\tdirs.push(input)\n\t\t\tfor await (const file of discoverFiles(input, exclude)) candidates.push(file)\n\t\t} else {\n\t\t\tdirs.push(path.dirname(input))\n\t\t\tcandidates.push(input)\n\t\t}\n\t}\n\n\tconst unique = [...new Set(candidates)]\n\tconst formats = await processPool(unique, detectFormat, 32)\n\tconst images = unique\n\t\t.map((file, i) => ({ file, detectedFormat: formats[i] as string }))\n\t\t.filter((img) => img.detectedFormat !== null)\n\t\t.sort((a, b) => a.file.localeCompare(b.file))\n\n\treturn { inputs, sourceRoot: commonDir(dirs), images }\n}\n\n/** Decide where each optimized copy goes. Throws PlanError if the output would overwrite originals. */\nexport function assignOutputs(scan: Scan, output: string, format?: OutputFormat): Plan {\n\tif (samePath(output, scan.sourceRoot) || scan.inputs.some((i) => samePath(i, output))) {\n\t\tthrow new PlanError(\n\t\t\t'The output folder is the same as the source folder — that would overwrite your originals. Choose a different output folder.',\n\t\t)\n\t}\n\n\tconst jobs: ImageJob[] = []\n\tconst skipped: Plan['skipped'] = []\n\tconst taken = new Set<string>()\n\n\tfor (const { file, detectedFormat } of scan.images) {\n\t\t// Never re-process our own output when it lives inside the source folder\n\t\tif (isInside(file, output)) continue\n\n\t\tconst target = format ?? defaultOutputFormat(detectedFormat)\n\t\tconst parsed = path.parse(path.relative(scan.sourceRoot, file))\n\t\tconst keepExt = format === undefined && detectedFormat !== UNSUPPORTED\n\t\tconst ext = keepExt ? parsed.ext : extensionFor(target)\n\n\t\tlet out = path.join(output, parsed.dir, `${parsed.name}${ext}`)\n\t\t// photo.png + photo.jpg → both photo.webp: give the second one a distinct name\n\t\tif (taken.has(out.toLowerCase())) {\n\t\t\tout = path.join(\n\t\t\t\toutput,\n\t\t\t\tparsed.dir,\n\t\t\t\t`${parsed.name}-${parsed.ext.slice(1).toLowerCase()}${ext}`,\n\t\t\t)\n\t\t}\n\t\tif (taken.has(out.toLowerCase())) {\n\t\t\tskipped.push({ file, reason: 'another image already produces the same output name' })\n\t\t\tcontinue\n\t\t}\n\t\ttaken.add(out.toLowerCase())\n\t\tjobs.push({ input: file, detectedFormat, output: out })\n\t}\n\n\treturn { sourceRoot: scan.sourceRoot, output, jobs, skipped }\n}\n\nexport interface PlanInput {\n\tinputs: string[]\n\toutput: string\n\tformat?: OutputFormat\n\texclude?: string[]\n}\n\nexport async function planJobs({ inputs, output, format, exclude = [] }: PlanInput): Promise<Plan> {\n\treturn assignOutputs(await scanInputs(inputs, exclude), output, format)\n}\n","import fs from 'node:fs/promises'\nimport path from 'node:path'\nimport { DEFAULT_OUTPUT_FOLDER } from '../validation/options.js'\n\n/**\n * Recursively yield absolute file paths under `dirPath`.\n * Skips hidden entries, symlinks, directories whose name is in `exclude`,\n * and folders called `bulki-output` (results of previous runs).\n */\nexport async function* discoverFiles(\n\tdirPath: string,\n\texclude: string[] = [],\n): 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) || entry.name === DEFAULT_OUTPUT_FOLDER) continue\n\t\t\ttry {\n\t\t\t\tyield* discoverFiles(fullPath, exclude)\n\t\t\t} catch {\n\t\t\t\t// An unreadable subfolder (permissions, removed mid-scan) must not abort the whole scan\n\t\t\t}\n\t\t} else if (entry.isFile()) {\n\t\t\tyield fullPath\n\t\t}\n\t}\n}\n","import { execFile } from 'node:child_process'\nimport fs from 'node:fs/promises'\nimport path from 'node:path'\nimport { promisify } from 'node:util'\nimport { Command } from 'commander'\n\nconst execFileAsync = promisify(execFile)\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\n# Prefer a local/global install; npx on every image is slow\nif command -v bulki >/dev/null 2>&1; then\n BULKI=\"bulki\"\nelif [ -x ./node_modules/.bin/bulki ]; then\n BULKI=\"./node_modules/.bin/bulki\"\nelse\n BULKI=\"npx --yes @puntoycoma/bulki\"\nfi\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 $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 class HookError extends Error {}\n\nexport async function installHook(cwd: string = process.cwd()): Promise<void> {\n\tconst hooksDir = await findHooksDir(cwd)\n\tif (!hooksDir) {\n\t\tthrow new HookError('Not a git repository. Run this from inside a git project.')\n\t}\n\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(cwd: string = process.cwd()): Promise<void> {\n\tconst hooksDir = await findHooksDir(cwd)\n\tif (!hooksDir) {\n\t\tthrow new HookError('Not a git repository.')\n\t}\n\n\tconst hookPath = path.join(hooksDir, '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(() => runOrExit(installHook))\n\nhookCommand\n\t.command('uninstall')\n\t.description('Remove the bulki pre-commit hook')\n\t.action(() => runOrExit(uninstallHook))\n\nasync function runOrExit(fn: () => Promise<void>): Promise<void> {\n\ttry {\n\t\tawait fn()\n\t} catch (err) {\n\t\tconsole.error(`error: ${(err as Error).message}`)\n\t\tprocess.exit(1)\n\t}\n}\n\n/**\n * Resolve the hooks directory through git itself, so worktrees, submodules\n * (where `.git` is a file) and `core.hooksPath` all work. Falls back to walking\n * up for `.git/` when git isn't installed.\n */\nasync function findHooksDir(startPath: string): Promise<string | null> {\n\ttry {\n\t\tconst { stdout } = await execFileAsync('git', ['rev-parse', '--git-path', 'hooks'], {\n\t\t\tcwd: startPath,\n\t\t})\n\t\treturn path.resolve(startPath, stdout.trim())\n\t} catch {\n\t\t// not a repo, or git not installed\n\t}\n\tlet current = path.resolve(startPath)\n\twhile (true) {\n\t\tconst gitPath = path.join(current, '.git')\n\t\tconst stat = await fs.stat(gitPath).catch(() => null)\n\t\tif (stat?.isDirectory()) return path.join(gitPath, 'hooks')\n\t\tconst parent = path.dirname(current)\n\t\tif (parent === current) return null\n\t\tcurrent = parent\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 { defaultOutputFormat, extensionFor } from '../pipeline/encode.js'\nimport { detectFormat } from '../pipeline/filter.js'\nimport { friendlyError, optimizeImage } from '../pipeline/optimize.js'\nimport { displayPath, isInside, resolveUserPath, samePath } from '../pipeline/paths.js'\nimport type { EncodeSettings } from '../types/index.js'\nimport {\n\tDEFAULT_OUTPUT_FOLDER,\n\tDEFAULTS,\n\tdefaultConcurrency,\n\tparseInteger,\n\tresolveOutputDir,\n\tvalidateEncodeSettings,\n} from '../validation/options.js'\n\nexport interface WatchOptions extends EncodeSettings {\n\t/** Absolute output folder */\n\toutput: string\n\tlog?: (line: string) => void\n\tlogError?: (line: string) => void\n}\n\nexport interface WatchHandle {\n\toutput: string\n\tclose(): void\n}\n\n/** How long a file must stop changing before we treat it as fully written. */\nconst SETTLE_MS = 400\n\n/**\n * Watch `dir` recursively and optimize every image that appears or changes.\n * Throws if `dir` isn't a folder; per-file errors are logged, never fatal.\n */\nexport async function startWatch(dir: string, opts: WatchOptions): Promise<WatchHandle> {\n\tconst root = path.resolve(dir)\n\tconst stat = await fs.promises.stat(root).catch(() => null)\n\tif (!stat) throw new Error(`Path not found: ${root}`)\n\tif (!stat.isDirectory()) throw new Error(`Watch path must be a folder: ${root}`)\n\tif (samePath(root, opts.output)) {\n\t\tthrow new Error(\n\t\t\t'The output folder is the same as the watched folder. Choose a different output folder.',\n\t\t)\n\t}\n\n\tconst log = opts.log ?? console.log\n\tconst logError = opts.logError ?? console.error\n\tconst settings: EncodeSettings = {\n\t\tquality: opts.quality,\n\t\tformat: opts.format,\n\t\tresize: opts.resize,\n\t}\n\n\tconst timers = new Map<string, NodeJS.Timeout>()\n\tconst queue: string[] = []\n\tconst queued = new Set<string>()\n\tlet active = 0\n\tconst maxActive = defaultConcurrency()\n\n\tconst pump = () => {\n\t\twhile (active < maxActive && queue.length > 0) {\n\t\t\tconst file = queue.shift() as string\n\t\t\tqueued.delete(file)\n\t\t\tactive++\n\t\t\tprocessFile(file).finally(() => {\n\t\t\t\tactive--\n\t\t\t\tpump()\n\t\t\t})\n\t\t}\n\t}\n\n\tconst processFile = async (file: string) => {\n\t\tconst detectedFormat = await detectFormat(file)\n\t\tif (!detectedFormat) return\n\t\tconst rel = path.relative(root, file)\n\t\tconst parsed = path.parse(rel)\n\t\tconst ext = settings.format ? extensionFor(settings.format) : parsed.ext\n\t\tconst target = settings.format ?? defaultOutputFormat(detectedFormat)\n\t\tconst output = path.join(\n\t\t\topts.output,\n\t\t\tparsed.dir,\n\t\t\t`${parsed.name}${detectedFormat === 'unsupported' && !settings.format ? extensionFor(target) : ext}`,\n\t\t)\n\t\ttry {\n\t\t\tconst result = await optimizeImage({ input: file, detectedFormat, output }, settings)\n\t\t\tlog(formatFileResult(result))\n\t\t} catch (err) {\n\t\t\tlogError(`✗ ${rel}: ${friendlyError(err as Error)}`)\n\t\t}\n\t}\n\n\tconst schedule = (file: string) => {\n\t\tclearTimeout(timers.get(file))\n\t\ttimers.set(\n\t\t\tfile,\n\t\t\tsetTimeout(async () => {\n\t\t\t\ttimers.delete(file)\n\t\t\t\tconst s = await fs.promises.stat(file).catch(() => null)\n\t\t\t\tif (!s?.isFile() || queued.has(file)) return\n\t\t\t\tqueue.push(file)\n\t\t\t\tqueued.add(file)\n\t\t\t\tpump()\n\t\t\t}, SETTLE_MS),\n\t\t)\n\t}\n\n\tconst watcher = fs.watch(root, { recursive: true }, (_event, filename) => {\n\t\tif (!filename) return\n\t\tconst file = path.resolve(root, filename.toString())\n\t\tconst rel = path.relative(root, file)\n\t\tif (isInside(file, opts.output)) return\n\t\tif (rel.split(path.sep).some((part) => part.startsWith('.') || part === DEFAULT_OUTPUT_FOLDER))\n\t\t\treturn\n\t\tif (file.endsWith('.tmp')) return\n\t\tschedule(file)\n\t})\n\twatcher.on('error', (err) => logError(`Watcher error: ${err.message}`))\n\n\treturn {\n\t\toutput: opts.output,\n\t\tclose() {\n\t\t\twatcher.close()\n\t\t\tfor (const t of timers.values()) clearTimeout(t)\n\t\t\ttimers.clear()\n\t\t},\n\t}\n}\n\nexport const watchCommand = new Command('watch')\n\t.description('Watch a folder and auto-optimize images as they appear')\n\t.argument('<path>', 'Folder to watch')\n\t.option('-o, --output <dir>', 'Folder where optimized images are written', DEFAULTS.output)\n\t.option('-q, --quality <n>', 'Output quality (1-100)', parseInteger, DEFAULTS.quality)\n\t.option('-f, --format <fmt>', 'Convert to jpeg, png, webp, gif or avif (default: keep format)')\n\t.option('-r, --resize <width>', 'Max width in px (never enlarges)', parseInteger)\n\t.option('-d, --destination <dir>', 'Deprecated: base folder; output goes to <dir>/bulki-output')\n\t.option('--output-folder <name>', 'Deprecated: subfolder name inside --destination')\n\t.action(async (dirPath: string, opts, cmd: Command) => {\n\t\ttry {\n\t\t\tconst settings = validateEncodeSettings(opts)\n\t\t\tconst outputFromCli = cmd.getOptionValueSource('output') === 'cli'\n\t\t\tconst root = resolveUserPath(dirPath)\n\t\t\tconst output = resolveOutputDir({ ...opts, output: outputFromCli ? opts.output : undefined })\n\t\t\tconst handle = await startWatch(root, { ...settings, output })\n\t\t\tconsole.log(`Watching ${displayPath(root)} → ${displayPath(output)}`)\n\t\t\tconsole.log('Press Ctrl+C to stop.\\n')\n\t\t\tprocess.once('SIGINT', () => {\n\t\t\t\thandle.close()\n\t\t\t\tconsole.log('\\nWatch stopped.')\n\t\t\t\tprocess.exit(0)\n\t\t\t})\n\t\t} catch (err) {\n\t\t\tconsole.error(`error: ${(err as Error).message}`)\n\t\t\tprocess.exit(1)\n\t\t}\n\t})\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\tconst note = result.keptOriginal ? ' — already optimal, copied as-is' : ''\n\treturn `${filename} ${original} -> ${newSize} (${pct})${note}`\n}\n\nexport function buildJsonOutput(\n\tresults: FileResult[],\n\terrors: JsonError[],\n\ttotalDuration: number,\n\toutputDir?: string,\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\n\treturn {\n\t\t...(outputDir ? { output: outputDir } : {}),\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\t...(r.keptOriginal ? { keptOriginal: true } : {}),\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: calcReduction(totalOriginal, totalNew),\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: ArrayLike<unknown>,\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 = calcReduction(totalOriginal, totalNew)\n\tconst duration = `${(totalDuration / 1000).toFixed(1)}s`\n\tconst fileCount = results.length\n\tlet summary = `${fileCount} ${fileCount === 1 ? 'file' : 'files'} | ${formatBytes(totalOriginal)} -> ${formatBytes(totalNew)} (${reduction}%) | ${duration}`\n\tif (errors.length > 0) {\n\t\tsummary += ` | ${errors.length} ${errors.length === 1 ? 'error' : 'errors'}`\n\t}\n\treturn summary\n}\n"],"mappings":"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","names":["fs","path","fs","sharp","sharp","fs","fs","path","fs","os","path","path","path","fs","path","fs","path","fs","path","fs","path","execFile","fs","path","promisify","execFileAsync","fs","path","Command","path","path","path","fs","Command"]}