MCPcopy
hub / github.com/npmx-dev/npmx.dev / loadBlogPosts

Function loadBlogPosts

modules/blog.ts:90–151  ·  view source on GitHub ↗

* Scans the blog directory for .md files and extracts validated frontmatter. * Returns all posts (including drafts) sorted by date descending. * Resolves Bluesky avatars at build time.

(
  blogDir: string,
  options: {
    imagesDir: string
    resolveAvatars: boolean
  },
)

Source from the content-addressed store, hash-verified

88 * Resolves Bluesky avatars at build time.
89 */
90async function loadBlogPosts(
91 blogDir: string,
92 options: {
93 imagesDir: string
94 resolveAvatars: boolean
95 },
96): Promise<BlogPostFrontmatter[]> {
97 const { imagesDir, resolveAvatars } = options
98 const files = await Array.fromAsync(glob(join(blogDir, '**/*.md').replace(/\\/g, '/')))
99
100 // First pass: extract raw frontmatter and collect all Bluesky handles
101 const rawPosts: Array<{ frontmatter: Record<string, unknown> }> = []
102 const allHandles = new Set<string>()
103
104 for (const file of files) {
105 const { data: frontmatter } = read(file)
106
107 // Normalise slug → path (same logic as standard-site-sync)
108 if (typeof frontmatter.slug === 'string' && !frontmatter.path) {
109 frontmatter.path = `/blog/${frontmatter.slug}`
110 }
111 // Normalise date to ISO string
112 if (frontmatter.date) {
113 const raw = frontmatter.date
114 frontmatter.date = new Date(raw instanceof Date ? raw : String(raw)).toISOString()
115 }
116
117 // Validate authors before resolving so we can extract handles
118 const authorsResult = safeParse(array(AuthorSchema), frontmatter.authors)
119 if (authorsResult.success) {
120 for (const author of authorsResult.output) {
121 if (author.blueskyHandle) {
122 allHandles.add(author.blueskyHandle)
123 }
124 }
125 }
126
127 rawPosts.push({ frontmatter })
128 }
129
130 // Batch-fetch all Bluesky avatars in a single request when avatar resolution is enabled.
131 const avatarMap = resolveAvatars
132 ? await fetchBlueskyAvatars(imagesDir, [...allHandles])
133 : new Map<string, string>()
134
135 // Second pass: validate with raw schema, then enrich authors with avatars
136 const posts: BlogPostFrontmatter[] = []
137
138 for (const { frontmatter } of rawPosts) {
139 const result = safeParse(RawBlogPostSchema, frontmatter)
140 if (!result.success) continue
141
142 posts.push({
143 ...result.output,
144 authors: resolveAuthors(result.output.authors, avatarMap),
145 })
146 }
147

Callers 1

setupFunction · 0.85

Calls 4

readFunction · 0.85
safeParseFunction · 0.85
fetchBlueskyAvatarsFunction · 0.85
resolveAuthorsFunction · 0.85

Tested by

no test coverage detected