ProxMenux 1.2.8: release notes, changelog, image and version

This commit is contained in:
MacRimi
2026-11-01 10:55:07 +01:00
parent 2327f96479
commit ac2bd28d25
18 changed files with 306 additions and 69 deletions
+16 -4
View File
@@ -27,6 +27,17 @@ function resolveChangelogPath(locale: string): string {
return path.join(repoRoot, "CHANGELOG.md")
}
// The image of the latest release, which is what a link to this page shows
// in RSS readers and social networks.
function latestReleaseImage(locale: string): string | null {
try {
const content = fs.readFileSync(resolveChangelogPath(locale), "utf8")
return content.match(/!\[[^\]]*\]\((https:[^)]+)\)/)?.[1] ?? null
} catch {
return null
}
}
// Surface the latest changelog entry in the metadata so social previews and
// SERP snippets reflect what was actually shipped. The CHANGELOG.md mixes two
// formats — `## [x.y.z] - YYYY-MM-DD` (older releases) and `## YYYY-MM-DD`
@@ -76,6 +87,7 @@ export async function generateMetadata({
}): Promise<Metadata> {
const { locale } = await params
const t = await getTranslations({ locale, namespace: "changelog.meta" })
const releaseImage = latestReleaseImage(locale)
const latest = readLatestChangelogVersion(locale)
const versionTag = latest?.version ?? ""
const dateTag = latest?.date ?? ""
@@ -114,9 +126,9 @@ export async function generateMetadata({
siteName: "ProxMenux",
images: [
{
url: "https://raw.githubusercontent.com/MacRimi/ProxMenux/main/web/public/main.png",
width: 1363,
height: 735,
url: releaseImage ?? "https://raw.githubusercontent.com/MacRimi/ProxMenux/main/web/public/main.png",
width: releaseImage ? 1280 : 1363,
height: releaseImage ? 640 : 735,
alt: "ProxMenux — Interactive Menu and Web Dashboard for Proxmox VE",
},
],
@@ -126,7 +138,7 @@ export async function generateMetadata({
title: `${t("title")}${titleSuffix}`,
description: `${t("ogDescription")} ${descriptionSuffix}`.trim(),
images: [
"https://raw.githubusercontent.com/MacRimi/ProxMenux/main/web/public/main.png",
releaseImage ?? "https://raw.githubusercontent.com/MacRimi/ProxMenux/main/web/public/main.png",
],
},
}
+16 -5
View File
@@ -21,7 +21,7 @@ interface ChangelogEntry {
// Per-locale RSS feed. Mirrors /app/rss.xml/route.ts (which stays the
// canonical English feed at the root for backwards compatibility with
// existing subscribers) but reads the localized CHANGELOG at
// <repo>/lang/<locale>/CHANGELOG.md. Falls back to the English source
// web/data/changelog/<locale>.md. Falls back to the English source
// when the localized file doesn't exist yet so partial translations
// still produce a valid feed.
@@ -61,7 +61,7 @@ const STRINGS: Record<string, LocaleStrings> = {
function resolveChangelogPath(locale: string): string {
const repoRoot = path.join(process.cwd(), "..")
if (locale && locale !== "en") {
const localized = path.join(repoRoot, "lang", locale, "CHANGELOG.md")
const localized = path.join(process.cwd(), "data", "changelog", `${locale}.md`)
if (fs.existsSync(localized)) return localized
}
return path.join(repoRoot, "CHANGELOG.md")
@@ -81,6 +81,17 @@ function extractFirstImage(rawContent: string): string | null {
return `https://proxmenux.com/${url}`
}
// Size of a release image of this repository, which an enclosure has to state.
function imageBytes(url: string): number | null {
const name = url.match(/\/images\/([^/]+)$/)?.[1]
if (!name) return null
try {
return fs.statSync(path.join(process.cwd(), "..", "images", name)).size
} catch {
return null
}
}
function escapeXml(text: string): string {
return text
.replace(/&/g, "&amp;")
@@ -225,15 +236,15 @@ export async function GET(
(entry) => `
<item>
<title>${escapeXml(entry.title)}</title>
<description>${escapeXml(entry.content.replace(/<[^>]*>/g, "").substring(0, 200))}...</description>
<description><![CDATA[${entry.image ? `<img src="${entry.image}" alt="${escapeXml(entry.title)}"/><br/>` : ""}${entry.content.replace(/<[^>]*>/g, "").substring(0, 200)}...]]></description>
<content:encoded><![CDATA[${entry.content}]]></content:encoded>
<link>${entry.url}</link>
<guid isPermaLink="true">${entry.url}</guid>
<pubDate>${new Date(entry.date).toUTCString()}</pubDate>
<category>${escapeXml(strings.category)}</category>${entry.image ? `
<media:thumbnail url="${escapeXml(entry.image)}"/>
<media:content url="${escapeXml(entry.image)}" medium="image"/>
<enclosure url="${escapeXml(entry.image)}" type="image/png" length="0"/>` : ""}
<media:content url="${escapeXml(entry.image)}" medium="image" type="image/png"/>${imageBytes(entry.image) ? `
<enclosure url="${escapeXml(entry.image)}" type="image/png" length="${imageBytes(entry.image)}"/>` : ""}` : ""}
</item>`,
)
.join("")}
+14 -3
View File
@@ -33,6 +33,17 @@ function extractFirstImage(rawContent: string): string | null {
return `https://proxmenux.com/${url}`
}
// Size of a release image of this repository, which an enclosure has to state.
function imageBytes(url: string): number | null {
const name = url.match(/\/images\/([^/]+)$/)?.[1]
if (!name) return null
try {
return fs.statSync(path.join(process.cwd(), "..", "images", name)).size
} catch {
return null
}
}
function escapeXml(text: string): string {
return text
.replace(/&/g, '&amp;')
@@ -180,15 +191,15 @@ export async function GET() {
(entry) => `
<item>
<title>${escapeXml(entry.title)}</title>
<description>${escapeXml(entry.content.replace(/<[^>]*>/g, '').substring(0, 200))}...</description>
<description><![CDATA[${entry.image ? `<img src="${entry.image}" alt="${escapeXml(entry.title)}"/><br/>` : ""}${entry.content.replace(/<[^>]*>/g, '').substring(0, 200)}...]]></description>
<content:encoded><![CDATA[${entry.content}]]></content:encoded>
<link>${entry.url}</link>
<guid isPermaLink="true">${entry.url}</guid>
<pubDate>${new Date(entry.date).toUTCString()}</pubDate>
<category>Changelog</category>${entry.image ? `
<media:thumbnail url="${escapeXml(entry.image)}"/>
<media:content url="${escapeXml(entry.image)}" medium="image"/>
<enclosure url="${escapeXml(entry.image)}" type="image/png" length="0"/>` : ""}
<media:content url="${escapeXml(entry.image)}" medium="image" type="image/png"/>${imageBytes(entry.image) ? `
<enclosure url="${escapeXml(entry.image)}" type="image/png" length="${imageBytes(entry.image)}"/>` : ""}` : ""}
</item>`,
)
.join("")}