import { NextResponse } from "next/server"
import fs from "fs"
import path from "path"
interface ChangelogEntry {
version: string
date: string
content: string
url: string
title: string
}
function formatContentForRSS(content: string): string {
return (
content
.replace(/https:\/\/macrimi\.github\.io\/ProxMenux/g, "https://proxmenux.com")
.replace(/`([^`]+)`/g, "$1")
.replace(/!\[([^\]]*)\]$$([^)]+)$$/g, (match, alt, url) => {
// Convert relative URLs to absolute
let absoluteUrl = url
if (url.startsWith("/")) {
absoluteUrl = `https://proxmenux.com${url}`
} else if (!url.startsWith("http://") && !url.startsWith("https://")) {
// Relative path, make it absolute
absoluteUrl = `https://proxmenux.com/${url}`
}
return ``
})
// Convert markdown links to HTML
.replace(/\[([^\]]+)\]$$([^)]+)$$/g, '$1')
// Convert ### headers to
tags
.replace(/```[\s\S]*?```/g, (match) => {
const code = match.replace(/```/g, "").trim()
return `${code}
`
})
// Convert - bullet points to tags
.replace(/^- (.+)$/gm, " $1 ")
// Wrap consecutive tags in
.replace(/(- .*?<\/li>\s*)+/g, (match) => `
${match}
`)
.replace(/\n/g, "
")
// Clean up extra spaces
.replace(/\s+/g, " ")
.trim()
)
}
async function parseChangelog(): Promise {
try {
const changelogPath = path.join(process.cwd(), "..", "CHANGELOG.md")
if (!fs.existsSync(changelogPath)) {
return []
}
const fileContents = fs.readFileSync(changelogPath, "utf8")
const entries: ChangelogEntry[] = []
// Split by ## headers (both versions and dates)
const lines = fileContents.split("\n")
let currentEntry: Partial | null = null
let contentLines: string[] = []
for (const line of lines) {
// Check for version header: ## [1.1.1] - 2025-03-21
const versionMatch = line.match(/^##\s+\[([^\]]+)\]\s*-\s*(\d{4}-\d{2}-\d{2})/)
// Check for date-only header: ## 2025-05-13
const dateMatch = line.match(/^##\s+(\d{4}-\d{2}-\d{2})$/)
if (versionMatch || dateMatch) {
// Save previous entry if exists
if (currentEntry && contentLines.length > 0) {
const rawContent = contentLines.join("\n").trim()
currentEntry.content = formatContentForRSS(rawContent)
if (currentEntry.version && currentEntry.date && currentEntry.title) {
entries.push(currentEntry as ChangelogEntry)
}
}
// Start new entry
if (versionMatch) {
const version = versionMatch[1]
const date = versionMatch[2]
currentEntry = {
version,
date,
url: `https://proxmenux.com/changelog#${version}`,
title: `ProxMenux ${version}`,
}
} else if (dateMatch) {
const date = dateMatch[1]
currentEntry = {
version: date,
date,
url: `https://proxmenux.com/changelog#${date}`,
title: `ProxMenux Update ${date}`,
}
}
contentLines = []
} else if (currentEntry && line.trim()) {
// Add content lines (skip empty lines at the beginning)
if (contentLines.length > 0 || line.trim() !== "") {
contentLines.push(line)
}
}
}
// Don't forget the last entry
if (currentEntry && contentLines.length > 0) {
const rawContent = contentLines.join("\n").trim()
currentEntry.content = formatContentForRSS(rawContent)
if (currentEntry.version && currentEntry.date && currentEntry.title) {
entries.push(currentEntry as ChangelogEntry)
}
}
return entries.slice(0, 20) // Latest 20 entries
} catch (error) {
console.error("Error parsing changelog:", error)
return []
}
}
export async function GET() {
const entries = await parseChangelog()
const siteUrl = "https://proxmenux.com"
const rssXml = `
ProxMenux Changelog
Latest updates and changes in ProxMenux - An Interactive Menu for Proxmox VE Management
${siteUrl}/changelog
en-US
${new Date().toUTCString()}
ProxMenux RSS Generator
60
${entries
.map(
(entry) => `
-
${entry.title}
${entry.url}
${entry.url}
${new Date(entry.date).toUTCString()}
Changelog
`,
)
.join("")}
`
return new NextResponse(rssXml, {
headers: {
"Content-Type": "application/rss+xml; charset=utf-8",
"Cache-Control": "public, max-age=3600, s-maxage=3600",
},
})
}