chore: include release notes and ecosystem content in RSS feed

add postbuild script that enhances the VuePress-generated RSS feed
with items from special content pages (release notes, ecosystem,
news coverage, videos, tutorials, events) that are stored as YAML
arrays in frontmatter rather than individual markdown files.

only items published after 2025-11-25 are included to avoid
backfilling old content into subscribers' feeds.
This commit is contained in:
Marcin Rataj
2025-11-27 06:44:44 +01:00
parent 398b5cb18a
commit 64861f42fc
3 changed files with 105 additions and 4 deletions

View File

@@ -0,0 +1,96 @@
'use strict'
/**
* Enhances the RSS feed to include items from special content pages
* (release notes, ecosystem content, etc.) that are stored as YAML
* arrays in frontmatter rather than individual markdown files.
*/
const fs = require('fs')
const path = require('path')
const xml2js = require('xml2js')
const matter = require('gray-matter')
const dayjs = require('dayjs')
const xmlFilePath = 'dist/index.xml'
// Only include items published after this date (to avoid backfilling old content)
const CUTOFF_DATE = dayjs('2025-11-25')
// Content types to include in the unified feed
// Each item in the data array should have: title, date, path (URL)
const CONTENT_SOURCES = [
{ file: 'releasenotes.md', category: 'Release Notes' },
{ file: 'ecosystemcontent.md', category: 'Ecosystem' },
{ file: 'newscoverage.md', category: 'News Coverage' },
{ file: 'videos.md', category: 'Videos' },
{ file: 'tutorials.md', category: 'Tutorials' },
{ file: 'events.md', category: 'Events' },
]
function parseContentFile(filename) {
const filepath = path.resolve('src/_blog', filename)
try {
const content = fs.readFileSync(filepath, 'utf8')
const { data } = matter(content)
const now = dayjs()
return (data.data || []).filter((item) => {
if (item.hidden) return false
if (item.publish_date && dayjs(item.publish_date).isAfter(now)) return false
return dayjs(item.publish_date || item.date).isAfter(CUTOFF_DATE)
})
} catch (err) {
console.error(`Warning: Could not read ${filename}:`, err.message)
return []
}
}
function itemToRssEntry(item, category) {
return {
title: [item.title],
link: [item.path],
pubDate: [dayjs(item.date).toDate().toUTCString()],
description: [item.title],
category: [category],
guid: [{ _: item.path, $: { isPermaLink: 'true' } }],
}
}
async function enhanceFeed() {
let xmlData, parsed
try {
xmlData = fs.readFileSync(xmlFilePath, 'utf8')
parsed = await xml2js.parseStringPromise(xmlData)
} catch (err) {
console.error('Could not read/parse RSS feed:', err.message)
process.exit(1)
}
// Blog posts link to the blog domain, special content links externally
const blogDomain = parsed.rss.channel[0].link[0]
const existingItems = (parsed.rss.channel[0].item || []).filter(
(item) => item.link[0].startsWith(blogDomain)
)
// Parse special content and convert to RSS items
const additionalItems = CONTENT_SOURCES.flatMap((source) =>
parseContentFile(source.file).map((i) => itemToRssEntry(i, source.category))
)
// Deduplicate by guid
const seen = new Set()
const allItems = [...existingItems, ...additionalItems]
.filter((item) => {
const guid = item.guid?.[0]?._ || item.guid?.[0] || item.link[0]
return !seen.has(guid) && seen.add(guid)
})
.sort((a, b) => new Date(b.pubDate[0]) - new Date(a.pubDate[0]))
parsed.rss.channel[0].item = allItems
const builder = new xml2js.Builder({ xmldec: { version: '1.0', encoding: 'UTF-8' } })
fs.writeFileSync(xmlFilePath, builder.buildObject(parsed))
console.log(`Enhanced RSS feed: ${allItems.length} items (${existingItems.length} posts + ${additionalItems.length} special)`)
}
exports.enhanceFeed = enhanceFeed

View File

@@ -2,10 +2,15 @@
'use strict'
const { enhanceFeed } = require('./enhance-feed')
const { generateIndexFile } = require('./latest-posts')
const { generateNewsFile } = require('./latest-news')
const { generateVideosFile } = require('./latest-videos')
// Enhance RSS feed first (adds release notes, ecosystem content, etc.)
// then generate index.json from the enhanced feed
enhanceFeed().then(() => {
generateIndexFile()
generateNewsFile()
generateVideosFile()
})

View File

@@ -5,7 +5,7 @@ sitemap:
exclude: true
data:
- title: 'Provide Sweep: Solving the DHT Bottleneck for Self-Hosting IPFS at Scale'
date: 2025-11-27
date: 2025-11-26
publish_date:
card_image: /blog-post-placeholder.png
path: https://ipshipyard.com/blog/2025-dht-provide-sweep/