// scripts/fetch-posts.mjs: the CMS's posts as Markdown files in site/news/
import { writeFile } from 'node:fs/promises';
const response = await fetch('https://cms.example.com/api/posts', {
headers: { Authorization: `Bearer ${process.env.CMS_TOKEN}` },
});
if (!response.ok) throw new Error(`The CMS answered ${response.status}`);
const posts = await response.json();
for (const post of posts) {
// The file name becomes the URL as it is, so allow only what belongs in one
if (!/^[a-z0-9-]+$/.test(post.slug)) throw new Error(`Unusable slug: ${post.slug}`);
// JSON is valid YAML: JSON.stringify quotes and escapes every value
const markdown = `---
title: ${JSON.stringify(post.title)}
description: ${JSON.stringify(post.summary)}
publishedAt: ${JSON.stringify(post.publishedAt)}
tags: ${JSON.stringify(post.tags)}
---
${post.body}
`;
await writeFile(`site/news/${post.slug}.md`, markdown);
}
console.log(`Wrote ${posts.length} posts`);
{
"scripts": {
"prebuild": "node scripts/fetch-posts.mjs",
"build": "stati build"
}
}
npm runs prebuild before every npm run build. It can't be a hook: Stati finds the pages before any hook runs, so a Markdown file written in beforeAll only becomes a page in the build after.
Keep the generated files out of git, with site/news/* and !site/news/index.md (the listing) in .gitignore. A build that starts from a clean checkout then never shows a post the CMS has deleted. The token only lives in this script and CI's secrets, never in the built site.
In the Stati docs: Integration with a CMS and Hook lifecycle.