From 64861f42fc703f680c9c146971abc668b5d3e749 Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Thu, 27 Nov 2025 06:44:44 +0100 Subject: [PATCH] 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. --- scripts/data/enhance-feed.js | 96 +++++++++++++++++++++++++++++++++++ scripts/data/index.js | 11 ++-- src/_blog/ecosystemcontent.md | 2 +- 3 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 scripts/data/enhance-feed.js diff --git a/scripts/data/enhance-feed.js b/scripts/data/enhance-feed.js new file mode 100644 index 00000000..874b64a1 --- /dev/null +++ b/scripts/data/enhance-feed.js @@ -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 diff --git a/scripts/data/index.js b/scripts/data/index.js index a7861ce4..b99abfee 100755 --- a/scripts/data/index.js +++ b/scripts/data/index.js @@ -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') -generateIndexFile() -generateNewsFile() -generateVideosFile() +// Enhance RSS feed first (adds release notes, ecosystem content, etc.) +// then generate index.json from the enhanced feed +enhanceFeed().then(() => { + generateIndexFile() + generateNewsFile() + generateVideosFile() +}) diff --git a/src/_blog/ecosystemcontent.md b/src/_blog/ecosystemcontent.md index 0d346756..62911e9d 100644 --- a/src/_blog/ecosystemcontent.md +++ b/src/_blog/ecosystemcontent.md @@ -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/