/** * public/uni-logo altındaki PNG'leri kayıpsıza yakın sıkıştırır (palet * kuantalama). Logolar en fazla ~80px CSS boyutunda gösterildiği için * 300×300 kaynak korunur; yalnızca kodlama küçülür. %10'dan az kazanç * veren dosyaya dokunulmaz (gradyanlı logolarda bantlanma riskine değmez). * * sharp, next'in bağımlılığından çözülür — ayrı kurulum gerektirmez. * pnpm'in izole node_modules'ü yüzünden .pnpm dizininden bulunur. * Kullanım: npx tsx scripts/logo-optimize.ts */ import { readdirSync, readFileSync, writeFileSync, statSync } from "node:fs"; import { createRequire } from "node:module"; import path from "node:path"; function sharpYolu(): string { const pnpmDir = path.join(process.cwd(), "node_modules", ".pnpm"); const aday = readdirSync(pnpmDir).find((d) => d.startsWith("sharp@")); if (!aday) throw new Error("sharp bulunamadı (node_modules/.pnpm)"); return path.join(pnpmDir, aday, "node_modules", "sharp"); } // sharp doğrudan bağımlılık olmadığı için tipleri elde yok; kullanılan // yüzey kadarı tanımlanır. type SharpPng = { png(opts: { palette: boolean; quality: number; effort: number; compressionLevel: number; }): { toBuffer(): Promise }; }; const req = createRequire(import.meta.url); const sharp = req(sharpYolu()) as (girdi: Buffer) => SharpPng; async function main() { const dir = path.join(process.cwd(), "public", "uni-logo"); const dosyalar = readdirSync(dir).filter((f) => f.endsWith(".png")); let onceToplam = 0; let sonraToplam = 0; for (const f of dosyalar) { const yol = path.join(dir, f); const once = statSync(yol).size; onceToplam += once; const buf = await sharp(readFileSync(yol)) .png({ palette: true, quality: 90, effort: 10, compressionLevel: 9 }) .toBuffer(); if (buf.length < once * 0.9) { writeFileSync(yol, buf); sonraToplam += buf.length; } else { sonraToplam += once; } } const mb = (n: number) => (n / 1024 / 1024).toFixed(1); console.log( `${dosyalar.length} logo: ${mb(onceToplam)} MB → ${mb(sonraToplam)} MB`, ); } main().catch((e) => { console.error(e); process.exit(1); });