()
| 41 | } |
| 42 | |
| 43 | async function scanFrontmatters(): Promise<BlogMeta[]> { |
| 44 | if (cachedMeta) { |
| 45 | return cachedMeta |
| 46 | } |
| 47 | await ensureContentDirs() |
| 48 | const entries = await fs.readdir(BLOG_DIR).catch(() => []) |
| 49 | const authorsMap = await loadAuthors() |
| 50 | const results = await Promise.all( |
| 51 | entries.map(async (slug): Promise<BlogMeta | null> => { |
| 52 | const postDir = path.join(BLOG_DIR, slug) |
| 53 | const stat = await fs.stat(postDir).catch(() => null) |
| 54 | if (!stat || !stat.isDirectory()) return null |
| 55 | const mdxPath = path.join(postDir, 'index.mdx') |
| 56 | const hasMdx = await fs |
| 57 | .stat(mdxPath) |
| 58 | .then((s) => s.isFile()) |
| 59 | .catch(() => false) |
| 60 | if (!hasMdx) return null |
| 61 | const raw = await fs.readFile(mdxPath, 'utf-8') |
| 62 | const { data, content: mdxContent } = matter(raw) |
| 63 | const fm = BlogFrontmatterSchema.parse(data) |
| 64 | const wordCount = mdxContent |
| 65 | .replace(/```[\s\S]*?```/g, '') |
| 66 | .replace(/import\s+.*?from\s+['"].*?['"]/g, '') |
| 67 | .replace(/<[^>]+>/g, '') |
| 68 | .replace(/[#*_~`[\]()!|>-]/g, '') |
| 69 | .split(/\s+/) |
| 70 | .filter((w) => w.length > 0).length |
| 71 | const authors = fm.authors.map((id) => authorsMap[id]).filter(Boolean) |
| 72 | if (authors.length === 0) throw new Error(`Authors not found for "${slug}"`) |
| 73 | return { |
| 74 | slug: fm.slug, |
| 75 | title: fm.title, |
| 76 | description: fm.description, |
| 77 | date: toIsoDate(fm.date), |
| 78 | updated: fm.updated ? toIsoDate(fm.updated) : undefined, |
| 79 | author: authors[0], |
| 80 | authors, |
| 81 | readingTime: fm.readingTime, |
| 82 | tags: fm.tags, |
| 83 | ogImage: fm.ogImage, |
| 84 | canonical: fm.canonical, |
| 85 | ogAlt: fm.ogAlt, |
| 86 | about: fm.about, |
| 87 | timeRequired: fm.timeRequired, |
| 88 | faq: fm.faq, |
| 89 | wordCount, |
| 90 | draft: fm.draft, |
| 91 | featured: fm.featured ?? false, |
| 92 | } |
| 93 | }) |
| 94 | ) |
| 95 | cachedMeta = results.filter((result): result is BlogMeta => result !== null).sort(byDateDesc) |
| 96 | return cachedMeta |
| 97 | } |
| 98 | |
| 99 | export async function getAllPostMeta(): Promise<BlogMeta[]> { |
| 100 | return (await scanFrontmatters()).filter((p) => !p.draft) |
no test coverage detected