Files
md-blog/post-build.ts
klemek 767fb8cfc6
Deploy / Build (push) Has been cancelled
Deploy / Deploy to Stapler (push) Has been cancelled
Lint / ESLint (push) Has been cancelled
Lint / Oxlint (push) Has been cancelled
Lint / Type Check (push) Has been cancelled
ci: add Makefile and stricter eslint config
2026-05-02 00:08:13 +02:00

142 lines
4.3 KiB
TypeScript

import fs from "fs";
import process from "process";
import { Feed } from "feed";
import articlesConfig from "./articles/config.json";
function getFiles(dir: string): string[] {
return fs.readdirSync(dir).flatMap((name) => {
const path = `${dir}/${name}`;
if (fs.statSync(path).isDirectory()) {
return getFiles(path);
} else {
return [path];
}
});
}
const METADATA_BLOCK_REGEX = /^---\n([\s\S]*?)---\n/m;
const METADATA_REGEX = /^(\w+):(.*)$/gm;
function readArticleMetadata(path: string): Record<string, string> | null {
const content = fs.readFileSync(path, { encoding: "utf8" });
const match: RegExpExecArray | null = METADATA_BLOCK_REGEX.exec(content);
if (!match?.[1]) {
console.warn(`No metadata for: ${path}`);
return null;
}
let subMatch: RegExpExecArray | null = null;
const metadata: Record<string, string> = {
path: path.replaceAll("/index.md", ""),
};
do {
subMatch = METADATA_REGEX.exec(match[1]);
if (subMatch?.[1] && subMatch[2]) {
metadata[subMatch[1]] = subMatch[2].trim();
}
} while (subMatch);
return metadata;
}
function formatArticlePage(
metadata: Record<string, string>,
baseHtml: string,
): string {
let outHtml = baseHtml;
outHtml = outHtml.replace(/<.*?property="og:url".*?>/gm, "");
outHtml = outHtml.replace(
/<\/head>/gm,
`<meta property="og:url" content="${articlesConfig.base_url}${metadata.path}/">\n</head>`,
);
const blog_title = articlesConfig.title.replace(/(<([^>]+)>)/gi, "").trim();
if (metadata.title) {
const title = metadata.title.replace(/(<([^>]+)>)/gi, "").trim();
outHtml = outHtml.replace(
/<title>.*?<\/title>/gm,
`<title>${blog_title}${title}</title>`,
);
outHtml = outHtml.replace(/<.*?property="og:title".*?>/gm, "");
outHtml = outHtml.replace(
/<\/head>/gm,
`<meta property="og:title" content="${title}">\n</head>`,
);
outHtml = outHtml.replace(/<.*?property="og:description".*?>/gm, "");
outHtml = outHtml.replace(
/<\/head>/gm,
`<meta property="og:description" content="${blog_title}">\n</head>`,
);
}
if (metadata.thumbnail) {
outHtml = outHtml.replace(/<.*?property="og:image".*?>/gm, "");
outHtml = outHtml.replace(
/<\/head>/gm,
`<meta property="og:image" content="${metadata.thumbnail.replace("./", articlesConfig.base_url + metadata.path + "/")}">\n</head>`,
);
}
return outHtml;
}
function addFeedArticle(metadata: Record<string, string>, feed: Feed) {
if (metadata.draft !== "true") {
feed.addItem({
title: metadata.title.replace(/(<([^>]+)>)/gi, "").trim(),
id: metadata.path,
link: `${articlesConfig.base_url}${metadata.path}/`,
date: new Date(Date.parse(metadata.date)),
image: metadata.thumbnail.replace(
"./",
articlesConfig.base_url + metadata.path + "/",
),
});
}
}
const indexContent = fs.readFileSync("dist/index.html", { encoding: "utf8" });
if (!indexContent) {
console.error("Could not read dist/index.html");
process.exit(1);
}
const metadatas = getFiles("articles")
.filter((path) => /\/index.md$/.exec(path))
.map((path) => readArticleMetadata(path))
.filter((metadata) => !!metadata);
const feed = new Feed({
title: articlesConfig.title.replace(/(<([^>]+)>)/gi, "").trim(),
id: articlesConfig.base_url,
link: articlesConfig.base_url,
language: articlesConfig.lang,
favicon: articlesConfig.base_url + "articles/favicon.ico",
generator: "md-blog",
feedLinks: {
json: articlesConfig.base_url + "feed.json",
atom: articlesConfig.base_url + "atom.xml",
rss: articlesConfig.base_url + "rss",
},
updated: new Date(
Math.max(
0,
...metadatas
.filter((metadata) => metadata.draft !== "true")
.map((metadata) => Date.parse(metadata.date)),
),
),
});
metadatas.forEach((metadata) => {
fs.writeFileSync(
`dist/${metadata.path}/index.html`,
formatArticlePage(metadata, indexContent),
);
console.info(`Wrote dist/${metadata.path}/index.html`);
addFeedArticle(metadata, feed);
});
fs.writeFileSync("dist/feed.json", feed.json1());
console.info(`Wrote dist/feed.json`);
fs.writeFileSync("dist/atom.xml", feed.atom1());
console.info(`Wrote dist/atom.xml`);
fs.writeFileSync("dist/rss.xml", feed.rss2());
console.info(`Wrote dist/rss.xml`);