Feat: Add NewsArticle JSON-LD, large image previews and automatic modified dates

Articles get NewsArticle structured data (headline, image, dates, author,
publisher) plus article:published_time and article:modified_time, and the
homepage gets WebSite and Organization data. Every indexable page allows
max-image-preview:large. The modified date is the article folder's last
commit, so editing an article updates it, the sitemap lastmod included.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-23 22:28:11 +00:00
co-authored by Claude Opus 5.5
parent fcce38b79a
commit f7bc273af8
4 changed files with 67 additions and 7 deletions
+13
View File
@@ -1,4 +1,5 @@
import fs from "node:fs/promises";
import { execFileSync } from "node:child_process";
import path from "node:path";
import fg from "fast-glob";
import matter from "gray-matter";
@@ -21,6 +22,17 @@ const getUnlistedSlugs = async () => {
}
};
// When the article's folder was last committed, so editing an article (text or images) updates its modified date on
// its own. CI checks out full history for this. "" for an uncommitted article, which then counts as unmodified.
const lastCommit = (dir) => {
try {
const d = execFileSync("git", ["log", "-1", "--format=%aI", "--", dir], { encoding: "utf8" }).trim();
return d && new Date(d).toISOString();
} catch {
return "";
}
};
const copyArticleAssets = async (file, slug) => {
const dir = path.dirname(file);
const entries = await fs.readdir(dir, { withFileTypes: true });
@@ -54,6 +66,7 @@ const run = async () => {
sourceFile: file,
markdown: md,
isUnlisted: unlisted.has(data.slug),
modified: lastCommit(path.dirname(file)),
html: fixInternalLinks(marked.parse(content))
});
}