feat: migrate blog FAQs to reusable MDX accordion
- add Astro MDX support and convert FAQ-bearing posts to MDX - centralize FAQ markup, scoped styling, behavior, and JSON-LD - preserve FAQ content and links across English and Spanish posts - extend blog audits and document the MDX editing workflow - remove obsolete global FAQ styles - ignore Front Matter CMS generated state
This commit is contained in:
@@ -7,25 +7,59 @@ import matter from 'gray-matter';
|
||||
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const failures = [];
|
||||
const files = await fg('src/content/blog/**/*.{md,mdx}', { cwd: root, absolute: true });
|
||||
let faqPosts = 0;
|
||||
let faqQuestions = 0;
|
||||
|
||||
for (const file of files) {
|
||||
const { content, data } = matter(await fs.readFile(file, 'utf8'));
|
||||
const source = await fs.readFile(file, 'utf8');
|
||||
const { content, data } = matter(source);
|
||||
const body = content.trimStart();
|
||||
if (body.startsWith('# ')) failures.push(`${path.relative(root, file)}: body begins with a duplicate article title`);
|
||||
if (body.includes(data.featuredImage) && body.indexOf(data.featuredImage) < 500) {
|
||||
failures.push(`${path.relative(root, file)}: body begins with a duplicate featured image`);
|
||||
}
|
||||
if (/^\s*(Frequently Asked Questions|Preguntas [Ff]recuentes)\s*$/m.test(content)) {
|
||||
failures.push(`${path.relative(root, file)}: FAQ section remains plain Markdown instead of using FAQAccordion`);
|
||||
}
|
||||
const componentMatch = content.match(/<FAQAccordion[\s\S]*?items=\{(\[[\s\S]*?\])\}\s*\/>/);
|
||||
if (componentMatch) {
|
||||
let faqItems;
|
||||
try {
|
||||
faqItems = JSON.parse(componentMatch[1]);
|
||||
} catch {
|
||||
failures.push(`${path.relative(root, file)}: FAQAccordion items are not valid JSON data`);
|
||||
continue;
|
||||
}
|
||||
faqPosts++;
|
||||
faqQuestions += faqItems.length;
|
||||
if (!file.endsWith('.mdx') || !content.includes("import FAQAccordion from '../../../components/FAQAccordion.astro';")) {
|
||||
failures.push(`${path.relative(root, file)}: FAQAccordion must be used from an MDX post`);
|
||||
}
|
||||
if (!/label="(?:Frequently Asked Questions|Preguntas [Ff]recuentes)"/.test(componentMatch[0]) || !/heading="[^"]+"/.test(componentMatch[0])) {
|
||||
failures.push(`${path.relative(root, file)}: FAQAccordion is missing its label or topic heading`);
|
||||
}
|
||||
if (!componentMatch[0].includes(`canonical="${data.canonical}"`)) {
|
||||
failures.push(`${path.relative(root, file)}: FAQAccordion canonical does not match frontmatter`);
|
||||
}
|
||||
for (const item of faqItems) {
|
||||
if (!item.question || !item.answer || !item.answerHtml) {
|
||||
failures.push(`${path.relative(root, file)}: FAQAccordion item is incomplete`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const report = [
|
||||
'# Blog Content Audit',
|
||||
'',
|
||||
`Checked ${files.length} blog post bodies for layout content duplicated in Markdown.`,
|
||||
`Verified ${faqQuestions} FAQ questions across ${faqPosts} FAQ-bearing posts.`,
|
||||
'',
|
||||
failures.length ? failures.map((failure) => `- ${failure}`).join('\n') : 'No duplicated article headers were detected.',
|
||||
failures.length ? failures.map((failure) => `- ${failure}`).join('\n') : 'No duplicated article headers or invalid MDX FAQ components were detected.',
|
||||
''
|
||||
].join('\n');
|
||||
|
||||
await fs.writeFile(path.join(root, 'reports/blog-content-audit.md'), report);
|
||||
console.log(`${failures.length} duplicated blog header elements.`);
|
||||
console.log(`${failures.length} blog content audit failures.`);
|
||||
if (failures.length) process.exitCode = 1;
|
||||
|
||||
Reference in New Issue
Block a user