#!/usr/bin/env bun /** * gantifontshighlight.ts - Cheerio Edition (Strict pre-code + General Guard) * Usage: bun run gantifontshighlight.ts [--quiet] [--no-backup] [--dry-run] */ import fs from "fs/promises"; import { readdirSync } from "fs"; import path from "path"; import * as cheerio from "cheerio"; // -------------------- CLI OPTIONS -------------------- const args = process.argv.slice(2); const quiet = args.includes("--quiet"); const noBackup = args.includes("--no-backup"); const dryRun = args.includes("--dry-run"); // -------------------- DATA STRUCTURES -------------------- // Tambahan properti isHljs buat nandain mana aja resource yang masuk geng highlight.js type MapItem = { rx: RegExp; repl: string; isHljs?: boolean }; const MAP: MapItem[] = [ // --- GENG HIGHLIGHT.JS & PRISM (isHljs: true) --- { rx: /https:\/\/.*\/github-dark-dimmed\.min\.css/i, repl: "/ext/github-dark-dimmed.min.css", isHljs: true }, { rx: /https:\/\/.*\/github-dark-dimmed\.css/i, repl: "/ext/github-dark-dimmed.css", isHljs: true }, { rx: /https:\/\/.*\/github-dark\.min\.css/i, repl: "/ext/github-dark.min.css", isHljs: true }, { rx: /https:\/\/.*\/github-dark\.css/i, repl: "/ext/github-dark.css", isHljs: true }, { rx: /https:\/\/.*\/github\.min\.css/i, repl: "/ext/github.min.css", isHljs: true }, { rx: /https:\/\/.*\/github\.css/i, repl: "/ext/github.css", isHljs: true }, { rx: /https:\/\/.*\/atom-one-dark\.min\.css/i, repl: "/ext/atom-one-dark.min.css", isHljs: true }, { rx: /https:\/\/.*\/atom-one-light\.min\.css/i, repl: "/ext/atom-one-light.min.css", isHljs: true }, { rx: /https:\/\/.*\/tokyo-night-dark\.min\.css/i, repl: "/ext/tokyo-night-dark.min.css", isHljs: true }, { rx: /https:\/\/.*\/prism-okaidia\.min\.css/i, repl: "/ext/prism-okaidia.min.css", isHljs: true }, { rx: /https:\/\/.*\/prism-tomorrow\.min\.css/i, repl: "/ext/prism-tomorrow.min.css", isHljs: true }, { rx: /https:\/\/.*\/prism-toolbar\.min\.css/i, repl: "/ext/prism-toolbar.min.css", isHljs: true }, { rx: /https:\/\/.*\/prism\.min\.css/i, repl: "/ext/prism.min.css", isHljs: true }, { rx: /https:\/\/.*\/(prism-vsc-dark-plus|vs-dark)\.min\.css/i, repl: "/ext/vs-dark.min.css", isHljs: true }, { rx: /https:\/\/.*\/vs\.min\.css/i, repl: "/ext/vs.min.css", isHljs: true }, { rx: /https:\/\/.*\/monokai\.min\.css/i, repl: "/ext/monokai.min.css", isHljs: true }, { rx: /https:\/\/.*\/(styles\/default|prism-coy)\.min\.css/i, repl: "/ext/default.min.css", isHljs: true }, { rx: /https?:\/\/.*\/highlight\.min\.js/i, repl: "/ext/highlight.js", isHljs: true }, // --- LIBRARY LAIN (isHljs tidak diset / false) --- { rx: /https:\/\/.*\/leaflet\.css/i, repl: "/ext/leaflet.css" }, { rx: /https:\/\/cdnjs\.cloudflare\.com\/ajax\/libs\/font-awesome\/[\d.\-a-z]+\/css\/all(?:\.min)?\.css/i, repl: "/ext/fontawesome.css" }, { rx: /https:\/\/use\.fontawesome\.com\/releases\/v[\d.\-a-z]+\/css\/all(?:\.min)?\.css/i, repl: "/ext/fontawesome.css" }, { rx: /https:\/\/cdn\.jsdelivr\.net\/npm\/@fortawesome\/fontawesome-free@[\d.\-a-z]+\/css\/all(?:\.min)?\.css/i, repl: "/ext/fontawesome.css" }, { rx: /https:\/\/unpkg\.com\/@fortawesome\/fontawesome-free@[\d.\-a-z]+\/css\/all(?:\.min)?\.css/i, repl: "/ext/fontawesome.css" }, { rx: /https:\/\/cdnjs\.cloudflare\.com\/ajax\/libs\/html2canvas\/[\d.]+\/html2canvas\.min\.js/i, repl: "/ext/html2canvas.min.js" }, { rx: /https:\/\/html2canvas\.hertzen\.com\/dist\/html2canvas\.min\.js/i, repl: "/ext/html2canvas.min.js" }, { rx: /https:\/\/cdn\.jsdelivr\.net\/npm\/html2canvas@[\d.]+\/dist\/html2canvas\.min\.js/i, repl: "/ext/html2canvas.min.js" }, { rx: /https:\/\/cdnjs\.cloudflare\.com\/ajax\/libs\/html2pdf\.js\/[\d.]+\/html2pdf\.bundle\.min\.js/i, repl: "/ext/html2pdf.bundle.min.js" }, { rx: /https?:\/\/.*\/qrcode\.min\.js/i, repl: "/ext/qrcode.min.js" }, ]; // Atribut yang mubazir setelah resource dipindah ke lokal const STALE_ATTRS = ["integrity", "crossorigin", "referrertarget", "referrerpolicy"]; // Regex untuk mendeteksi tag "); injected = 1; } else if (!hasHljsScript && hasTrigger && !isLibraryAlreadyInjected) { // Trigger ada, library kosong -> suntik library triggerScriptEls.first().before('\n'); libFixed = 1; } else if (!hasHljsScript && !hasTrigger && !isLibraryAlreadyInjected && !isTriggerAlreadyInjected) { // KASUS EKSTREM: Ada
tapi library & trigger hilang semua!
// Solusi: Suntik keduanya langsung di bagian paling bawah
$("body").append(
`\n\n\n`
);
injected = 1;
libFixed = 1;
}
}
// ---- Step 4: Simpan jika ada perubahan ----
if (replaced > 0 || cleaned > 0 || injected > 0 || libFixed > 0 || removedHljs > 0) {
const newContent = $.html({ _useHtmlParser2: true } as cheerio.CheerioOptions);
if (!dryRun) {
if (!noBackup) await fs.copyFile(file, `${file}.bak`);
await fs.writeFile(file, newContent, "utf8");
}
return { file, replaced, cleaned, injected, libFixed, removedHljs };
}
return null;
}
// -------------------- EXECUTION & REPORTING --------------------
const files = expandGlobs(["*.html", "artikelx/*.html", "artikel/*.html"]);
if (files.length === 0) {
if (!quiet) console.log("โ ๏ธ Tidak ada file HTML ditemukan.");
process.exit(0);
}
const results = await Promise.all(files.map(processFile));
const validResults = results.filter((r): r is ProcessResult => r !== null);
const stats = validResults.reduce(
(acc, curr) => ({
files: acc.files + 1,
rep: acc.rep + curr.replaced,
cln: acc.cln + curr.cleaned,
inj: acc.inj + curr.injected,
lib: acc.lib + curr.libFixed,
rmv: acc.rmv + curr.removedHljs, // Rekap seberapa banyak yang dibasmi
}),
{ files: 0, rep: 0, cln: 0, inj: 0, lib: 0, rmv: 0 }
);
if (!quiet) {
console.log(`\n---------------------------------------------------------`);
console.log(`๐ฏ Operasi "Layar Kosong Bersih" Selesai! (Strict Guard Edition)`);
console.log(`---------------------------------------------------------`);
console.log(`โ
File diperbarui : ${stats.files}`);
console.log(`๐ URL CDN diganti : ${stats.rep}`);
console.log(`๐งน Atribut dibersihkan : ${stats.cln}`);
console.log(`๐๏ธ Hljs tak terpakai dibasmi : ${stats.rmv}`);
console.log(`๐ Trigger baru disuntik : ${stats.inj}`);
console.log(`๐ ๏ธ Library highlight.js dipulihkan : ${stats.lib}`);
console.log(`---------------------------------------------------------`);
const statusMessage =
stats.files > 0
? "Semua artikel sudah ramping, SEO ready, dan anti double inject!"
: "Tidak ada perubahan, blog sudah sangat bersih!";
console.log(`๐ ${statusMessage}`);
console.log(`---------------------------------------------------------`);
}