blog/lib/tasks/articles.ts

170 lines
6.1 KiB
TypeScript

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",
`<abbr title="${attributes["$positional"]}">${target}</abbr>`,
);
});
});
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/"));