From 219c18138cb3c697af3a872907ed3d8d8e0f092c Mon Sep 17 00:00:00 2001 From: svemagie <869694+svemagie@users.noreply.github.com> Date: Tue, 31 Mar 2026 14:04:34 +0200 Subject: [PATCH] fix(og): match plain URL slugs; fix Funkwhale GC wipe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit og-fix transform was matching date-based URL segments (/type/yyyy/MM/dd/slug/) that this site never uses — posts live at /type/slug/. Every post therefore fell through to the default OG image. Fixed by updating the regex to /type/slug/index.html and deriving the OG slug as the bare last URL segment, which matches the filename og.js already generates. The ogSlug filter is simplified accordingly. Funkwhale GC bug: gcFunkwhaleImages() deleted the entire image cache whenever _activeFilenames was empty — which happens if the API returns valid stats but no listenings with cover URLs. Guard added: GC is skipped when no images were referenced this build. Co-Authored-By: Claude Sonnet 4.6 --- eleventy.config.js | 27 ++++++++++----------------- lib/cache-funkwhale-image.js | 4 ++++ lib/og.js | 3 ++- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/eleventy.config.js b/eleventy.config.js index 65da6d4..1314186 100644 --- a/eleventy.config.js +++ b/eleventy.config.js @@ -403,19 +403,18 @@ export default function (eleventyConfig) { if (!outputPath || !outputPath.endsWith(".html")) return content; // Derive correct page URL and OG slug from outputPath (immune to race condition) - // Content pages match: .../type/yyyy/MM/dd/slug/index.html - const dateMatch = outputPath.match( - /\/([\w-]+)\/(\d{4})\/(\d{2})\/(\d{2})\/([\w-]+)\/index\.html$/ + // Content pages match: .../type/slug/index.html (plain URLs, no date segments) + const postMatch = outputPath.match( + /\/([\w-]+)\/([\w-]+)\/index\.html$/ ); - if (dateMatch) { - const [, type, year, month, day, slug] = dateMatch; - const pageUrlPath = `/${type}/${year}/${month}/${day}/${slug}/`; + if (postMatch) { + const [, type, slug] = postMatch; + const pageUrlPath = `/${type}/${slug}/`; const correctFullUrl = `${siteUrl}${pageUrlPath}`; - const ogSlug = `${year}-${month}-${day}-${slug}`; - const hasOg = hasOgImage(ogSlug); + const hasOg = hasOgImage(slug); const ogImageUrl = hasOg - ? `${siteUrl}/og/${ogSlug}.png` + ? `${siteUrl}/og/${slug}.png` : `${siteUrl}/images/og-default.png`; const twitterCard = hasOg ? "summary_large_image" : "summary"; @@ -433,7 +432,7 @@ export default function (eleventyConfig) { content = content.replace(/__OG_IMAGE_PLACEHOLDER__/g, ogImageUrl); content = content.replace(/__TWITTER_CARD_PLACEHOLDER__/g, twitterCard); } else { - // Non-date pages (homepage, about, etc.): use defaults + // Non-post pages (homepage, archives, etc.): use defaults content = content.replace( /__OG_IMAGE_PLACEHOLDER__/g, `${siteUrl}/images/og-default.png` @@ -916,16 +915,10 @@ export default function (eleventyConfig) { // Derive OG slug from page.url (reliable) instead of page.fileSlug // (which suffers from Nunjucks race conditions in Eleventy 3.x parallel rendering). - // OG images are named with the full date prefix to match URL segments exactly. + // URLs are plain (/type/slug/), so the slug is the last path segment. eleventyConfig.addFilter("ogSlug", (url) => { if (!url) return ""; const segments = url.split("/").filter(Boolean); - // Date-based URL: /type/yyyy/MM/dd/slug/ → 5 segments → "yyyy-MM-dd-slug" - if (segments.length === 5) { - const [, year, month, day, slug] = segments; - return `${year}-${month}-${day}-${slug}`; - } - // Fallback: last segment (for pages, legacy URLs) return segments[segments.length - 1] || ""; }); diff --git a/lib/cache-funkwhale-image.js b/lib/cache-funkwhale-image.js index f6397e8..d299236 100644 --- a/lib/cache-funkwhale-image.js +++ b/lib/cache-funkwhale-image.js @@ -76,6 +76,10 @@ export async function cacheFunkwhaleImage(url) { */ export function gcFunkwhaleImages() { if (!existsSync(CACHE_DIR)) return; + // If no images were referenced this build, skip GC — likely the API returned no + // cover URLs (empty listenings, null covers, or stats-only response). Deleting + // everything from an empty _activeFilenames set would wipe a valid cache. + if (_activeFilenames.size === 0) return; let deleted = 0; for (const file of readdirSync(CACHE_DIR)) { if (!_activeFilenames.has(file)) { diff --git a/lib/og.js b/lib/og.js index b7ae03d..bfd4af6 100644 --- a/lib/og.js +++ b/lib/og.js @@ -126,7 +126,8 @@ function formatDate(dateStr) { } /** - * Use the full filename (with date prefix) as the OG image slug. + * Use the filename (without extension) as the OG image slug. + * Matches the last URL path segment, which Eleventy derives from the filename. */ function toOgSlug(filename) { return filename;