From f012dfa850f032cd9fffc81dd031a22b75784397 Mon Sep 17 00:00:00 2001 From: Pamela Chia Date: Sat, 12 Sep 2026 02:11:25 +0800 Subject: [PATCH] fix(www): sitemap lists all changelog slugs (#50275) The www sitemap generator reads changelog URLs from the build-generated RSS feed but only accepted links whose slug starts with a number, the shape `computeChangelogEntrySlug` produces solely for entries carrying `legacy_gh_discussion`. Every changelog entry authored since that migration has a plain text slug and was silently missing from `sitemap_www.xml`. I widened the link match to any non-empty slug; the RSS builder is the only producer of that file and emits exactly one link per item, so no other filter is needed. I added a text-slug RSS item to the fixture-driven sitemap test and asserted that the changelog URL list equals the RSS item list, so a future filter that drops entries fails the suite. **Note:** text-slug entries now pass through the same fail-the-build pubDate check that numeric-prefixed entries already did after #50198. A changelog entry with no `publish_date` and no date-prefixed filename would produce an unparseable pubDate and stop the www build. The alternative, shipping the URL without lastmod, is a one-line change. I kept the gate because every current changelog entry carries a date-prefixed filename, the changelog repo documents that convention, and the build error names the entry URL. ## To test Tested on Vercel preview: - [x] Count `` blocks in `/changelog-rss.xml`, then count `/changelog/` locs in `/sitemap_www.xml`, expect the two counts to match - [x] Search the preview sitemap for `/changelog/pipelines`, expect one `` entry with a `` date - [x] Search the preview sitemap for a numeric-prefixed entry such as `/changelog/47796-developer-update-july-2026`, expect it still present ## Linear - fixes GROWTH-1212 ## Summary by CodeRabbit - **Bug Fixes** - Changelog pages with text-based slugs are now correctly recognized in the sitemap. - Sitemap entries for these changelog pages now use their RSS publication dates. - RSS links are matched more reliably, ensuring all valid changelog URLs are included. - Invalid publication dates are rejected instead of producing incorrect sitemap metadata. --- apps/www/generate-sitemap.test.ts | 32 ++++++++++++++++++++++--- apps/www/internals/generate-sitemap.mjs | 2 +- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/apps/www/generate-sitemap.test.ts b/apps/www/generate-sitemap.test.ts index 52d39ab6186..7778886cf17 100644 --- a/apps/www/generate-sitemap.test.ts +++ b/apps/www/generate-sitemap.test.ts @@ -10,6 +10,7 @@ import { blogPostingSchema, serializeJsonLd } from './lib/json-ld' const GENERATOR = path.join(process.cwd(), 'internals', 'generate-sitemap.mjs') const LEGACY_LINK = 'https://supabase.com/changelog/12345-legacy-entry' const TIMED_LINK = 'https://supabase.com/changelog/23456-timed-entry' +const TEXT_SLUG_LINK = 'https://supabase.com/changelog/text-slug-entry' const SPAWN_TIMEOUT_MS = 30_000 const createdDirs: string[] = [] @@ -57,7 +58,15 @@ function rssItem(link: string, pubDate: string): string { } function rss(items: string[]): string { - return `\n\n${items.join('\n')}\n\n` + return [ + '', + '', + 'https://supabase.com/changelog', + '', + ...items, + '', + '', + ].join('\n') } function urlEntries(xml: string): UrlEntry[] { @@ -91,8 +100,9 @@ describe('generate-sitemap lastmod', () => { '_events/2026-02-01-webinar.mdx': mdx("date: '2026-02-01T19:00:00.000-07:00'"), 'pages/company.tsx': '', 'public/changelog-rss.xml': rss([ - rssItem(LEGACY_LINK, 'Tue, 03 Feb 2026 00:00:00 +0000'), rssItem(TIMED_LINK, 'Wed, 04 Feb 2026 20:15:00 -0700'), + rssItem(TEXT_SLUG_LINK, 'Thu, 05 Feb 2026 00:00:00 +0000'), + rssItem(LEGACY_LINK, 'Tue, 03 Feb 2026 00:00:00 +0000'), ]), }) result = runGenerator(fixtureDir) @@ -155,9 +165,20 @@ describe('generate-sitemap lastmod', () => { expect(entryFor(TIMED_LINK)?.lastmod).toBe('2026-02-05') }) + it('includes changelog entries whose slug has no numeric prefix', () => { + expect(entryFor(TEXT_SLUG_LINK)?.lastmod).toBe('2026-02-05') + }) + + it('emits exactly the RSS item links as changelog URLs', () => { + const changelogLocs = entries + .filter((entry) => entry.loc.startsWith('https://supabase.com/changelog')) + .map((entry) => entry.loc) + expect(changelogLocs).toEqual([TIMED_LINK, TEXT_SLUG_LINK, LEGACY_LINK]) + }) + it('emits only day-precision lastmod values, one per dated source', () => { const lastmods = entries.map((entry) => entry.lastmod).filter(Boolean) - expect(lastmods).toHaveLength(9) + expect(lastmods).toHaveLength(10) for (const lastmod of lastmods) expect(lastmod).toMatch(/^\d{4}-\d{2}-\d{2}$/) }) @@ -267,6 +288,11 @@ describe('generate-sitemap rejects dates it cannot trust', () => { files: { 'public/changelog-rss.xml': rss([rssItem(LEGACY_LINK, 'Invalid Date +0000')]) }, stderrIncludes: [`changelog-rss ${LEGACY_LINK}`, '"Invalid Date +0000"'], }, + { + name: 'an unparseable changelog pubDate on a text-slug entry', + files: { 'public/changelog-rss.xml': rss([rssItem(TEXT_SLUG_LINK, 'Invalid Date +0000')]) }, + stderrIncludes: [`changelog-rss ${TEXT_SLUG_LINK}`, '"Invalid Date +0000"'], + }, ] for (const testCase of cases) { diff --git a/apps/www/internals/generate-sitemap.mjs b/apps/www/internals/generate-sitemap.mjs index 60f7ecba619..2a879479f1c 100644 --- a/apps/www/internals/generate-sitemap.mjs +++ b/apps/www/internals/generate-sitemap.mjs @@ -180,7 +180,7 @@ async function generate() { const lastmodByUrl = new Map() for (const [, item] of rss.matchAll(/([\s\S]*?)<\/item>/g)) { - const link = item.match(/(https:\/\/supabase\.com\/changelog\/\d+[^<]*)<\/link>/)?.[1] + const link = item.match(/(https:\/\/supabase\.com\/changelog\/[^<]+)<\/link>/)?.[1] if (!link || lastmodByUrl.has(link)) continue const pubDate = item.match(/([^<]*)<\/pubDate>/)?.[1] lastmodByUrl.set(link, pubDate ? changelogLastmod(pubDate, link) : undefined)