import { compileFile } from "pug"; import { readFileSync } from "node:fs"; import { env } from "node:process"; import { join, basename, dirname } from "node:path"; import { Transform } from "node:stream"; import asciidoctor, { Document } from "asciidoctor"; import File from "vinyl"; import { DateTime } from "luxon"; import { unified } from "unified"; import rehypeParse from "rehype-parse"; import rehypeStringify from "rehype-stringify"; import rehypePresetMinify from "rehype-preset-minify"; import rehypePrism from "@mapbox/rehype-prism"; import { PRODUCTION, DEFAULT_DATE } from "../environment.js"; import rehypeExternalLinks from "rehype-external-links"; import visit from "unist-util-visit"; import { src, dest } from "gulp"; import { readAssetManifest } from "../hash.js"; const Asciidoctor = asciidoctor(); const EXTENSION_REGISTRY = Asciidoctor.Extensions.create(); EXTENSION_REGISTRY.inlineMacro("abbr", function () { this.process(function (parent, target, attributes) { return this.createInline( parent, "quoted", `${target}`, ); }); }); const SITE_TITLE = "Ad\xE6dra's blog"; const SITE_DESCRIPTION = [ "Ad\xE6dra", "Software Developper in Paris, France", "Rust, Ruby, Typescript, Linux", ].join(" \u2022 "); const SITE_DEFAULT_META = { "og:image": "https://adaedra.blob.core.windows.net/blog-assets/og_preview.png", "og:image:alt": "Ad\xE6dra's mascot", "og:locale": "en_GB", }; function extractImages(gulp: Transform, file: File) { return function () { return function (tree) { visit(tree, "element", function (node: any) { if (node.tagName !== "img") { return; } const path = join(dirname(file.path)); const image = new File({ path: join(basename(file.path, ".asciidoc"), node.properties.src), contents: readFileSync(path), }); gulp.push(image); }); }; }; } function renderArticle() { const allArticles: [string, DateTime, Document][] = []; const renderLayout = compileFile("src/layout.pug"); const renderArticleLayout = compileFile("src/article.pug"); const assetManifest = readAssetManifest(); const asset = (path: string) => { if (assetManifest.hasOwnProperty(path)) { path = assetManifest[path]; } return env.ASSETS_HOSTS ? new URL(path, env.ASSETS_HOSTS).toString() : join("/_assets/", path); }; return new Transform({ readableObjectMode: true, writableObjectMode: true, async transform(file: File, _, callback) { try { const slug = basename(file.path, ".asciidoc"); const article = Asciidoctor.load(file.contents.toString(), { extension_registry: EXTENSION_REGISTRY, }); const date = DateTime.fromISO(article.getAttribute("docdate"), { zone: "UTC" }); if (PRODUCTION && date.equals(DEFAULT_DATE)) { callback(null); return; } allArticles.push([slug, date, article]); const vfile = await unified() .use(rehypeParse) .use(rehypeExternalLinks, { rel: "noopener", target: "_blank" }) .use(extractImages(this, file)) .use(rehypePrism) .use(rehypePresetMinify) .use(rehypeStringify) .process(article.convert()); const content = renderLayout({ SITE_TITLE, asset, title: article.getDoctitle({}), meta: Object.assign({}, SITE_DEFAULT_META, { description: article.getAttribute("description"), keywords: article.getAttribute("keywords"), "og:title": article.getDoctitle({}), "og:type": "article", "og:article:published_time": article.getAttribute("docdate"), "og:url": `https://adaedra.eu/${slug}/`, "og:description": article.getAttribute("description"), "og:site_name": SITE_TITLE, }), render() { return renderArticleLayout({ asset, article, date, DateTime, body: vfile.toString(), }); }, }); file.contents = Buffer.from(content); file.path = join(slug, "index.html"); file.base = null; callback(null, file); } catch (e) { console.error(e); callback(e); } }, final(callback) { try { allArticles.sort(([, a], [, b]) => b.diff(a).toMillis()); const renderIndex = compileFile("src/index.pug"); const contents = renderLayout({ SITE_TITLE, asset, meta: Object.assign({}, SITE_DEFAULT_META, { description: SITE_DESCRIPTION, "og:title": SITE_TITLE, "og:type": "website", "og:url": `https://adaedra.eu`, }), render() { return renderIndex({ articles: allArticles, asset }); }, }); this.push(new File({ path: "index.html", contents: Buffer.from(contents) })); callback(null); } catch (e) { console.error(e); callback(e); } }, }); } export const articles = () => src("articles/**/*.asciidoc").pipe(renderArticle()).pipe(dest("dist/"));