Files
aia-website/www/tools/extract-blog-footers.mjs
DeCentN2Madness 0b276e7b32 feat(blog): recreate article footer navigation and related posts
- add reusable live-site-faithful blog footer component
- extract previous, next, and related post data from article content
- remove duplicated footer fragments from multilingual articles
- document the repeatable footer extraction workflow
2026-06-08 20:08:21 -07:00

59 lines
2.2 KiB
JavaScript

import fs from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import fg from 'fast-glob';
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const contentRoot = path.join(root, 'src/content/blog');
const output = path.join(root, 'src/data/blog-footers.json');
const footerHeading = /^### Similar Blog Posts\s*$/m;
const navPattern = /^ ?(Previous Post|Next Post) ?\n\n###### \[([^\]]+)\]\(([^)]+)\)\s*$/gm;
const relatedPattern = /!\[([^\]]*)\]\(([^)]+)\)\s*\n\n#### \[([^\]]+)\]\(([^)]+)\)/g;
const routeFor = (lang, href) => {
if (/^(?:https?:|mailto:|tel:|#|\/)/.test(href)) return href;
const slug = href.replace(/^\.\.\//, '').replace(/^library\//, '');
return `${lang === 'en' ? '' : `/${lang}`}/library/${slug}`;
};
const records = {};
for (const file of await fg('**/*.{md,mdx}', { cwd: contentRoot, onlyFiles: true })) {
const source = await fs.readFile(path.join(contentRoot, file), 'utf8');
const heading = footerHeading.exec(source);
if (!heading) continue;
const [lang, filename] = file.split('/');
const slug = filename.replace(/\.(?:md|mdx)$/, '');
const before = source.slice(0, heading.index);
const footer = source.slice(heading.index);
const navigation = {};
let firstNavIndex = heading.index;
for (const match of before.matchAll(navPattern)) {
firstNavIndex = Math.min(firstNavIndex, match.index);
navigation[match[1] === 'Previous Post' ? 'previous' : 'next'] = {
title: match[2],
href: routeFor(lang, match[3])
};
}
const related = [...footer.matchAll(relatedPattern)].map((match) => ({
image: match[2],
alt: match[1],
title: match[3],
href: routeFor(lang, match[4])
}));
records[`${lang}/${slug}`] = { ...navigation, related };
await fs.writeFile(path.join(contentRoot, file), `${source.slice(0, firstNavIndex).trimEnd()}\n`);
}
const count = Object.keys(records).length;
if (count === 0) {
throw new Error('No blog footer fragments found; existing footer data was left unchanged.');
}
await fs.mkdir(path.dirname(output), { recursive: true });
await fs.writeFile(output, `${JSON.stringify(records, null, 2)}\n`);
console.log(`Extracted and removed footer fragments from ${count} blog posts.`);