// @ts-check import { readdir, readFile, writeFile, mkdir, stat } from "node:fs/promises"; import { renderPreviewImg } from "imagen"; import GM from "gm"; function rewriteExtn(filename, extn) { const split = filename.split("."); split[split.length - 1] = extn; return split.join("."); } const toplevel = (await readdir("public/blog")).filter(x => x !== "assets"); const removeExtn = path => { const parts = path.split("."); parts.splice(parts.length - 1); return parts.join("."); }; const parseMetadata = (slug, contents) => { return Object.fromEntries( contents .split("---")[1] .trim() .split("\n") .map(line => { const [name, value] = line.split(/:(.*)/).map(x => x.trim()); return [name, value]; }) .concat([["slug", slug]]), ); }; /** * @type {import("./blog.types").Data} */ const data = Object.fromEntries( await Promise.all( toplevel.map(year => readdir("public/blog/" + year).then(slugs => Promise.all( slugs.map(async slug => { const path = `public/blog/${year}/${slug}`; const contents = await readFile(path, "utf-8"); return parseMetadata(removeExtn(slug), contents); }), ) .then(list => list .sort( (a, b) => new Date(a["featured-img"]).valueOf() - new Date(b["featured-img"]).valueOf(), ) .map(x => [x.slug, x]), ) .then(list => Object.fromEntries(list)) .then(cont => [year, cont]), ), ), ), ); writeFile("src/blog.json", JSON.stringify(data, null, "\t"), "utf-8").then(() => console.log("Done"), ); /** * * @param {import("./blog.types").Data} data */ async function generateFeaturedImages(data) { for (const year in data) { const yearData = data[year]; for (const slug in yearData) { const article = yearData[slug]; await renderPreviewImg( [1920, 1080], "public/blog/assets/" + article["featured-img"], article.title, ["MKRhere", article.category, article.published].join(" ยท "), "public/blog/assets/featured/" + rewriteExtn(article["featured-img"], "jpg"), ); } } } mkdir("public/blog/assets/featured", { recursive: true }).then(() => generateFeaturedImages(data), ); const convert = (gm, img, size, fmt) => new Promise((resolve, reject) => gm("public/blog/assets/" + img) .resize(size) .noProfile() .write( "public/blog/assets/gen/" + rewriteExtn(img, size + "." + fmt), err => { if (err) return reject(err); else resolve(true); }, ), ); async function generateOptimisedImages() { await mkdir("public/blog/assets/gen/", { recursive: true }); const imgs = await readdir("public/blog/assets"); for (const img of imgs) { if (!(await stat("public/blog/assets/" + img)).isFile()) continue; const gm = GM.subClass({ imageMagick: true }); for (const size of [480, 800]) for (const fmt of ["jpg", "webp", "avif"]) await convert(gm, img, size, fmt); } } generateOptimisedImages();