import fs from "node:fs/promises"; import path from "node:path"; import sharp from "sharp"; const ROOT = process.cwd(); const TARGET_DIRS = ["public", "images"]; const SKIP_DIRS = new Set(["node_modules", ".next", "out", ".git"]); const EXTENSIONS = new Set([".jpg", ".jpeg", ".png"]); async function exists(p) { try { await fs.access(p); return true; } catch { return false; } } async function walk(dir, files = []) { const entries = await fs.readdir(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { if (SKIP_DIRS.has(entry.name)) continue; await walk(fullPath, files); } else if (entry.isFile()) { const ext = path.extname(entry.name).toLowerCase(); if (EXTENSIONS.has(ext)) { files.push(fullPath); } } } return files; } async function optimizeImage(filePath) { const ext = path.extname(filePath).toLowerCase(); const input = sharp(filePath); const tempPath = `${filePath}.tmp`; if (ext === ".png") { await input.png({ compressionLevel: 9, adaptiveFiltering: true }).toFile(tempPath); } else { await input.jpeg({ quality: 82, mozjpeg: true }).toFile(tempPath); } const [origStat, tempStat] = await Promise.all([ fs.stat(filePath), fs.stat(tempPath), ]); if (tempStat.size <= origStat.size) { await fs.rename(tempPath, filePath); } else { await fs.unlink(tempPath); } } async function main() { const targets = []; for (const dir of TARGET_DIRS) { const full = path.join(ROOT, dir); if (await exists(full)) targets.push(full); } if (targets.length === 0) { console.log("No image directories found (public/, images/)."); return; } const files = []; for (const dir of targets) { await walk(dir, files); } if (files.length === 0) { console.log("No .jpg/.jpeg/.png files found."); return; } console.log(`Optimizing ${files.length} images...`); for (const file of files) { try { await optimizeImage(file); } catch (err) { console.warn(`Skipping ${file}: ${err?.message ?? err}`); } } } main().catch((err) => { console.error(err); process.exit(1); });