Initial commit
Deploy / validate-dispatch (push) Successful in 43s
Deploy / scheduled-rebuild-dev (push) Skipped
Deploy / scheduled-rebuild-master (push) Skipped
Deploy / rollback-dev (push) Skipped
Deploy / rollback-master (push) Skipped
Deploy / changes (push) Successful in 13s
Deploy / secret-scan (push) Successful in 9s
Deploy / app-quality (push) Successful in 11m21s
Deploy / security-deps (push) Successful in 14s
Deploy / context (push) Failing after 1s
Deploy / app-image (push) Skipped
Deploy / deploy-dev (push) Skipped
Deploy / deploy-master (push) Skipped
Deploy / validate-dispatch (push) Successful in 43s
Deploy / scheduled-rebuild-dev (push) Skipped
Deploy / scheduled-rebuild-master (push) Skipped
Deploy / rollback-dev (push) Skipped
Deploy / rollback-master (push) Skipped
Deploy / changes (push) Successful in 13s
Deploy / secret-scan (push) Successful in 9s
Deploy / app-quality (push) Successful in 11m21s
Deploy / security-deps (push) Successful in 14s
Deploy / context (push) Failing after 1s
Deploy / app-image (push) Skipped
Deploy / deploy-dev (push) Skipped
Deploy / deploy-master (push) Skipped
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
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.`,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user