import fs from 'fs' import process from 'process' import { Feed } from 'feed' process.loadEnvFile() 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 | null { const content = fs.readFileSync(path, { encoding: 'utf8' }) const match: RegExpExecArray | null = METADATA_BLOCK_REGEX.exec(content) if (!match || !match[1]) { console.warn(`No metadata for: ${path}`) return null } let subMatch: RegExpExecArray | null = null const metadata: Record = { path: path.replaceAll('/index.md', ''), } const dateMatch = path.match(/(\d{4}\/\d{2}\/\d{2})/) if (dateMatch && dateMatch[1]) { metadata['date'] = dateMatch[1].replaceAll('/', '-') } do { subMatch = METADATA_REGEX.exec(match[1]) if (subMatch && subMatch[1] && subMatch[2]) { metadata[subMatch[1]] = subMatch[2].trim() } } while (subMatch) return metadata } function formatArticlePage(metadata: Record, baseHtml: string): string { let outHtml = baseHtml outHtml = outHtml.replace(/<.*?property="og:url".*?>/gm, '') outHtml = outHtml.replace( /<\/head>/gm, `\n`, ) const blog_title = process.env.VITE_APP_TITLE?.replace(/(<([^>]+)>)/gi, '').trim() if (metadata.title) { const title = metadata.title.replace(/(<([^>]+)>)/gi, '').trim() outHtml = outHtml.replace(/.*?<\/title>/gm, `<title>${blog_title} — ${title}`) outHtml = outHtml.replace(/<.*?property="og:title".*?>/gm, '') outHtml = outHtml.replace( /<\/head>/gm, `\n`, ) outHtml = outHtml.replace(/<.*?property="og:description".*?>/gm, '') outHtml = outHtml.replace( /<\/head>/gm, `\n`, ) } if (metadata.thumbnail) { outHtml = outHtml.replace(/<.*?property="og:image".*?>/gm, '') outHtml = outHtml.replace( /<\/head>/gm, `\n`, ) } return outHtml } function addFeedArticle(metadata: Record, feed: Feed) { if (metadata.draft !== 'true') { feed.addItem({ title: metadata.title.replace(/(<([^>]+)>)/gi, '').trim(), id: metadata.path, link: `${process.env.VITE_BASE_URL}${metadata.path}/`, date: new Date(Date.parse(metadata.date)), image: metadata.thumbnail.replace('./', process.env.VITE_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) => path.match(/\d{4}\/\d{2}\/\d{2}\/index.md$/)) .map((path) => readArticleMetadata(path)) .filter((metadata) => !!metadata) const feed = new Feed({ title: process.env.VITE_APP_TITLE?.replace(/(<([^>]+)>)/gi, '').trim() ?? '', id: process.env.VITE_BASE_URL, link: process.env.VITE_BASE_URL, language: process.env.VITE_APP_LANG, favicon: process.env.VITE_BASE_URL + '/articles/favicon.ico', generator: 'md-blog', feedLinks: { json: process.env.VITE_BASE_URL + 'feed.json', atom: process.env.VITE_BASE_URL + 'atom.xml', rss: process.env.VITE_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()) fs.writeFileSync('dist/atom.xml', feed.atom1()) fs.writeFileSync('dist/rss', feed.rss2())