import fs from "node:fs"; import path from "node:path"; import sharp from "sharp"; const PUBLIC_IMAGES_DIR = path.resolve(import.meta.dirname, "../public/images"); const SUPPORTED_EXTENSIONS = new Set([".jpg", ".jpeg", ".png", ".webp", ".avif"]); const METADATA_FIELDS = ["exif", "icc", "iptc", "xmp"]; const CHECK_ONLY = process.argv.includes("--check"); function collectImages(directory) { if (!fs.existsSync(directory)) return []; const files = []; for (const entry of fs.readdirSync(directory, { withFileTypes: true })) { const fullPath = path.join(directory, entry.name); if (entry.isDirectory()) { files.push(...collectImages(fullPath)); continue; } if (entry.isFile() && SUPPORTED_EXTENSIONS.has(path.extname(entry.name).toLowerCase())) { files.push(fullPath); } } return files; } function getEncoder(pipeline, extension) { if (extension === ".png") { return pipeline.png({ compressionLevel: 9, adaptiveFiltering: true }); } if (extension === ".webp") { return pipeline.webp({ quality: 80 }); } if (extension === ".avif") { return pipeline.avif({ quality: 50 }); } return pipeline.jpeg({ quality: 85, progressive: true, mozjpeg: true }); } async function hasMetadata(filePath) { const metadata = await sharp(filePath).metadata(); return METADATA_FIELDS.some((field) => Boolean(metadata[field])); } async function stripMetadata(filePath) { const extension = path.extname(filePath).toLowerCase(); const tempPath = `${filePath}.tmp`; if (fs.existsSync(tempPath)) { fs.rmSync(tempPath); } const pipeline = sharp(filePath).rotate(); await getEncoder(pipeline, extension).toFile(tempPath); fs.renameSync(tempPath, filePath); } const files = collectImages(PUBLIC_IMAGES_DIR); const filesWithMetadata = []; for (const file of files) { if (await hasMetadata(file)) { filesWithMetadata.push(file); } } if (CHECK_ONLY) { for (const file of filesWithMetadata) { console.error(`[images:check-metadata] Metadata found: ${file}`); } if (filesWithMetadata.length > 0) { process.exitCode = 1; } else { console.log(`[images:check-metadata] ${files.length} images checked; no metadata blocks found.`); } } else { for (const file of filesWithMetadata) { await stripMetadata(file); } console.log( `[images:strip-metadata] ${filesWithMetadata.length} of ${files.length} images rewritten.`, ); }