mirror of
https://github.com/supabase/supabase.git
synced 2026-10-10 20:05:06 +03:00
changelog rss
This commit is contained in:
17 files changed
+1447
-35
No files matched your search
@@ -22,3 +22,7 @@ NEXT_PUBLIC_SUPABASE_URL=https://xguihxuzqibwxjnimxev.supabase.co
|
||||
NEXT_PUBLIC_UI_LIBRARY_URL=http://localhost:3004
|
||||
NEXT_PUBLIC_URL=http://localhost:3000
|
||||
SUPABASE_COM_SERVICE_ROLE_KEY=secret
|
||||
|
||||
# GITHUB_CHANGELOG_APP_ID=
|
||||
# GITHUB_CHANGELOG_APP_INSTALLATION_ID=
|
||||
# GITHUB_CHANGELOG_APP_PRIVATE_KEY=
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Rss } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { Button, cn } from 'ui'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function ChangelogRssButton({ className }: Props) {
|
||||
return (
|
||||
<Button
|
||||
asChild
|
||||
type="text"
|
||||
className={cn(className)}
|
||||
icon={<Rss className="h-4 w-4" strokeWidth={2} aria-hidden />}
|
||||
>
|
||||
<Link href="/changelog-rss.xml">Changelog RSS</Link>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { ChangelogRssButton } from '~/components/Changelog/ChangelogRssButton'
|
||||
import type { ChangelogTimelineIndexItem } from '~/lib/changelog-github'
|
||||
import { githubChangelogLabelFilterUrl } from '~/lib/changelog.utils'
|
||||
import mdxComponents from '~/lib/mdx/mdxComponents'
|
||||
@@ -313,7 +314,10 @@ export function ChangelogV5Explorer({ items }: Props) {
|
||||
<>
|
||||
<div className="pb-4 container mx-auto max-w-xl">
|
||||
<h1 className="h1">Changelog</h1>
|
||||
<p className="text-foreground-lighter text-lg">New updates and product improvements</p>
|
||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||
<p className="text-foreground-lighter text-lg">New updates and product improvements</p>
|
||||
<ChangelogRssButton />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section className="relative flex w-full flex-col">
|
||||
@@ -324,7 +328,7 @@ export function ChangelogV5Explorer({ items }: Props) {
|
||||
<div className="flex flex-col gap-6 pb-12">
|
||||
<div
|
||||
className={cn(
|
||||
'sticky top-16 z-20 md:-mx-4 md:w-[calc(100%+4rem)] flex flex-col gap-1 border-default border-b md:px-4 py-3',
|
||||
'sticky top-16 z-20 md:-mx-4 md:w-[calc(100%+2rem)] flex flex-col gap-1 border-default border-b md:px-4 py-3',
|
||||
'bg-background/95 backdrop-blur-sm supports-[backdrop-filter]:bg-background/95'
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -7,9 +7,9 @@ import dayjs from 'dayjs'
|
||||
import { ChevronsUpDown } from 'lucide-react'
|
||||
import type { MDXRemoteSerializeResult } from 'next-mdx-remote'
|
||||
import { MDXRemote } from 'next-mdx-remote'
|
||||
import Link from 'next/link'
|
||||
import { parseAsInteger, useQueryState } from 'nuqs'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { ChangelogRssButton } from '~/components/Changelog/ChangelogRssButton'
|
||||
import {
|
||||
Badge,
|
||||
cn,
|
||||
@@ -174,7 +174,10 @@ export function ChangelogV6Explorer({ items }: Props) {
|
||||
<>
|
||||
<div className="pb-4">
|
||||
<h1 className="h1">Changelog</h1>
|
||||
<p className="text-foreground-lighter text-lg">New updates and product improvements</p>
|
||||
<div className="flex flex-wrap items-center gap-2 justify-between">
|
||||
<p className="text-foreground-lighter text-lg">New updates and product improvements</p>
|
||||
<ChangelogRssButton />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Sticky combobox navigator */}
|
||||
|
||||
@@ -124,6 +124,10 @@ const footerData = [
|
||||
text: 'RSS',
|
||||
url: '/rss.xml',
|
||||
},
|
||||
{
|
||||
text: 'Changelog RSS',
|
||||
url: '/changelog-rss.xml',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Pure ESM changelog RSS document builder (used by generateStaticContent.mjs and re-exported from rss.tsx).
|
||||
* @typedef {{ title: string; url: string; sortDate: string }} ChangelogRssItemInput
|
||||
*/
|
||||
import dayjs from 'dayjs'
|
||||
import utc from 'dayjs/plugin/utc.js'
|
||||
import advancedFormat from 'dayjs/plugin/advancedFormat.js'
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(advancedFormat)
|
||||
|
||||
function xmlEncodeRss(str) {
|
||||
if (str === undefined || str === null) return ''
|
||||
return String(str)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
}
|
||||
|
||||
function formatRssPubDate(isoOrDate) {
|
||||
return dayjs(isoOrDate)
|
||||
.utcOffset(0, true)
|
||||
.startOf('day')
|
||||
.format('ddd, DD MMM YYYY HH:mm:ss [-0700]')
|
||||
}
|
||||
|
||||
/** @param {ChangelogRssItemInput[]} entries */
|
||||
export function generateChangelogRssXml(entries) {
|
||||
const visible = entries.filter((e) => !e.title.includes('[d]'))
|
||||
const sorted = [...visible].sort(
|
||||
(a, b) => dayjs(b.sortDate).valueOf() - dayjs(a.sortDate).valueOf()
|
||||
)
|
||||
|
||||
const lastBuildDate = sorted[0]?.sortDate
|
||||
? formatRssPubDate(sorted[0].sortDate)
|
||||
: formatRssPubDate(dayjs().toISOString())
|
||||
|
||||
const itemsXml = sorted
|
||||
.map((e) => {
|
||||
const encodedTitle = xmlEncodeRss(e.title)
|
||||
const encodedLink = xmlEncodeRss(e.url)
|
||||
const pubDate = formatRssPubDate(e.sortDate)
|
||||
return `<item>
|
||||
<guid>${encodedLink}</guid>
|
||||
<title>${encodedTitle}</title>
|
||||
<link>${encodedLink}</link>
|
||||
<pubDate>${pubDate}</pubDate>
|
||||
</item>`
|
||||
})
|
||||
.join('\n')
|
||||
|
||||
return `
|
||||
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||
<channel>
|
||||
<title>Supabase Changelog</title>
|
||||
<link>https://supabase.com/changelog</link>
|
||||
<description>Product updates and improvements from Supabase</description>
|
||||
<language>en</language>
|
||||
<lastBuildDate>${lastBuildDate}</lastBuildDate>
|
||||
<atom:link href="https://supabase.com/changelog-rss.xml" rel="self" type="application/rss+xml"/>
|
||||
${itemsXml}
|
||||
</channel>
|
||||
</rss>
|
||||
`
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
// hackery to fix Terry accidentally deleting
|
||||
// a bunch of releases and their associted discussions in Dec 2023
|
||||
// checks if titles match and grabs this original createdAt timestamp
|
||||
//
|
||||
// When editing entries with `createdAt`, also sync `scripts/data/changelog-deleted-discussions.json`
|
||||
// (title + createdAt only) — used by generateStaticContent.mjs for changelog-rss.xml.
|
||||
export const deletedDiscussions = [
|
||||
{
|
||||
title: 'Platform updates: October 2023',
|
||||
|
||||
+24
-15
@@ -5,23 +5,22 @@ var advancedFormat = require('dayjs/plugin/advancedFormat')
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(advancedFormat)
|
||||
|
||||
const generateRssItem = (post: any): string => {
|
||||
const xmlEncode = (str: string) => {
|
||||
if (str === undefined || str === null) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return str
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
export function xmlEncodeRss(str: string | undefined | null): string {
|
||||
if (str === undefined || str === null) {
|
||||
return ''
|
||||
}
|
||||
return str
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
}
|
||||
|
||||
const encodedTitle = xmlEncode(post.title)
|
||||
const encodedPath = xmlEncode(post.path)
|
||||
const encodedDescription = xmlEncode(post.description)
|
||||
const generateRssItem = (post: any): string => {
|
||||
const encodedTitle = xmlEncodeRss(post.title)
|
||||
const encodedPath = xmlEncodeRss(post.path)
|
||||
const encodedDescription = xmlEncodeRss(post.description)
|
||||
const formattedDate = dayjs(post.date)
|
||||
.utcOffset(0, true)
|
||||
.startOf('day')
|
||||
@@ -37,9 +36,19 @@ const generateRssItem = (post: any): string => {
|
||||
`
|
||||
}
|
||||
|
||||
export type ChangelogRssItemInput = {
|
||||
title: string
|
||||
url: string
|
||||
sortDate: string
|
||||
}
|
||||
|
||||
/** Implemented in `./changelog-rss.mjs` (used by `scripts/generateStaticContent.mjs`). */
|
||||
export { generateChangelogRssXml } from './changelog-rss.mjs'
|
||||
|
||||
// This utility generates RSS feeds for specialized content:
|
||||
// 1. Customer stories RSS feed (customers-rss.xml) - used by pages/customers.tsx via getStaticProps
|
||||
// 2. Author-specific PlanetPG RSS feeds (planetpg-{authorID}-rss.xml) - filtered feeds for individual authors
|
||||
// 3. Changelog RSS (changelog-rss.xml) — built in generateStaticContent.mjs via ./changelog-rss.mjs
|
||||
//
|
||||
// Note: The main blog RSS feed (rss.xml) containing all blog posts is generated separately
|
||||
// in generateStaticContent.mjs during the build process. This file is NOT used for the main blog feed.
|
||||
|
||||
@@ -3,6 +3,7 @@ import { createAppAuth } from '@octokit/auth-app'
|
||||
import { Octokit } from '@octokit/core'
|
||||
import { paginateGraphql } from '@octokit/plugin-paginate-graphql'
|
||||
import { Octokit as OctokitRest } from '@octokit/rest'
|
||||
import { ChangelogRssButton } from '~/components/Changelog/ChangelogRssButton'
|
||||
import CTABanner from '~/components/CTABanner'
|
||||
import DefaultLayout from '~/components/Layouts/Default'
|
||||
import { discussionDisplayDate, githubChangelogLabelFilterUrl } from '~/lib/changelog.utils'
|
||||
@@ -385,7 +386,10 @@ function ChangelogV2Page({ changelogIndex, changelog, pageInfo, restPage }: Chan
|
||||
>
|
||||
<div className="pb-4">
|
||||
<h1 className="h1">Changelog</h1>
|
||||
<p className="text-foreground-lighter text-lg">New updates and product improvements</p>
|
||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||
<p className="text-foreground-lighter text-lg">New updates and product improvements</p>
|
||||
<ChangelogRssButton />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ChangelogRssButton } from '~/components/Changelog/ChangelogRssButton'
|
||||
import { ChangelogV3TimelineList } from '~/components/Changelog/ChangelogV3TimelineList'
|
||||
import CTABanner from '~/components/CTABanner'
|
||||
import DefaultLayout from '~/components/Layouts/Default'
|
||||
@@ -126,7 +127,10 @@ function ChangelogV3ModalContent({ changelogIndex }: PageProps) {
|
||||
<div className="container mx-auto max-w-3xl flex flex-col gap-6 px-4 py-10 sm:px-16 xl:px-20">
|
||||
<div>
|
||||
<h1 className="h1">Changelog</h1>
|
||||
<p className="text-foreground-lighter text-lg">New updates and product improvements</p>
|
||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||
<p className="text-foreground-lighter text-lg">New updates and product improvements</p>
|
||||
<ChangelogRssButton />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section aria-label="Changelog entries">
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ChangelogRssButton } from '~/components/Changelog/ChangelogRssButton'
|
||||
import { ChangelogV3TimelineList } from '~/components/Changelog/ChangelogV3TimelineList'
|
||||
import CTABanner from '~/components/CTABanner'
|
||||
import DefaultLayout from '~/components/Layouts/Default'
|
||||
@@ -5,7 +6,6 @@ import type { ChangelogTimelineIndexItem } from '~/lib/changelog-github'
|
||||
import { getChangelogTimelineSortedIndex } from '~/lib/changelog-github'
|
||||
import type { GetServerSideProps } from 'next'
|
||||
import { NextSeo } from 'next-seo'
|
||||
import Link from 'next/link'
|
||||
|
||||
type PageProps = {
|
||||
changelogIndex: ChangelogTimelineIndexItem[]
|
||||
@@ -40,7 +40,10 @@ function ChangelogV3Page({ changelogIndex }: PageProps) {
|
||||
<div className="container mx-auto max-w-3xl flex flex-col gap-6 px-4 py-10 sm:px-16 xl:px-20">
|
||||
<div>
|
||||
<h1 className="h1">Changelog</h1>
|
||||
<p className="text-foreground-lighter text-lg">New updates and product improvements</p>
|
||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||
<p className="text-foreground-lighter text-lg">New updates and product improvements</p>
|
||||
<ChangelogRssButton />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section aria-label="Changelog entries">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ArrowLeftIcon, ArrowRightIcon } from '@heroicons/react/outline'
|
||||
import { ChangelogRssButton } from '~/components/Changelog/ChangelogRssButton'
|
||||
import CTABanner from '~/components/CTABanner'
|
||||
import DefaultLayout from '~/components/Layouts/Default'
|
||||
import {
|
||||
@@ -113,14 +114,17 @@ function ChangelogV3DetailPage({
|
||||
/>
|
||||
<DefaultLayout>
|
||||
<div className="container mx-auto max-w-3xl flex flex-col gap-4 px-4 py-10 sm:px-16 xl:px-20">
|
||||
<nav
|
||||
aria-label="Breadcrumb"
|
||||
className="text-foreground-lighter flex flex-wrap items-center gap-x-2 gap-y-1 text-sm"
|
||||
>
|
||||
<Link href="/changelog-v3" className="text-foreground-lighter hover:underline">
|
||||
Changelog
|
||||
</Link>
|
||||
</nav>
|
||||
<div className="flex flex-wrap items-center justify-between gap-4">
|
||||
<nav
|
||||
aria-label="Breadcrumb"
|
||||
className="text-foreground-lighter flex flex-wrap items-center gap-x-2 gap-y-1 text-sm"
|
||||
>
|
||||
<Link href="/changelog-v3" className="text-foreground-lighter hover:underline">
|
||||
Changelog
|
||||
</Link>
|
||||
</nav>
|
||||
<ChangelogRssButton />
|
||||
</div>
|
||||
|
||||
<header className="border-default flex flex-col gap-2 border-b pb-6">
|
||||
<h1 className="h1 text-2xl sm:text-3xl">{title}</h1>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ChangelogRssButton } from '~/components/Changelog/ChangelogRssButton'
|
||||
import { ChangelogMonthTabExplorer } from '~/components/Changelog/ChangelogMonthTabExplorer'
|
||||
import CTABanner from '~/components/CTABanner'
|
||||
import DefaultLayout from '~/components/Layouts/Default'
|
||||
@@ -5,7 +6,6 @@ import type { ChangelogTimelineIndexItem } from '~/lib/changelog-github'
|
||||
import { getChangelogTimelineSortedIndex } from '~/lib/changelog-github'
|
||||
import type { GetServerSideProps } from 'next'
|
||||
import { NextSeo } from 'next-seo'
|
||||
import Link from 'next/link'
|
||||
import { NuqsAdapter } from 'nuqs/adapters/next/pages'
|
||||
|
||||
type PageProps = {
|
||||
@@ -40,7 +40,10 @@ export default function ChangelogV4MonthTabsPage({ changelogIndex }: PageProps)
|
||||
<div className="container mx-auto max-w-5xl flex flex-col gap-6 px-4 py-10 sm:px-16 xl:px-20">
|
||||
<div>
|
||||
<h1 className="h1">Changelog</h1>
|
||||
<p className="text-foreground-lighter text-lg">New updates and product improvements</p>
|
||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||
<p className="text-foreground-lighter text-lg">New updates and product improvements</p>
|
||||
<ChangelogRssButton />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ChangelogMonthTabExplorer items={changelogIndex} />
|
||||
|
||||
@@ -3,6 +3,7 @@ import { createAppAuth } from '@octokit/auth-app'
|
||||
import { Octokit } from '@octokit/core'
|
||||
import { paginateGraphql } from '@octokit/plugin-paginate-graphql'
|
||||
import { Octokit as OctokitRest } from '@octokit/rest'
|
||||
import { ChangelogRssButton } from '~/components/Changelog/ChangelogRssButton'
|
||||
import CTABanner from '~/components/CTABanner'
|
||||
import DefaultLayout from '~/components/Layouts/Default'
|
||||
import {
|
||||
@@ -311,7 +312,10 @@ function ChangelogPage({ changelog, pageInfo, restPage }: ChangelogPageProps) {
|
||||
>
|
||||
<div className="pb-4">
|
||||
<h1 className="h1">Changelog</h1>
|
||||
<p className="text-foreground-lighter text-lg">New updates and product improvements</p>
|
||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||
<p className="text-foreground-lighter text-lg">New updates and product improvements</p>
|
||||
<ChangelogRssButton />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,21 @@
|
||||
[
|
||||
{ "title": "Platform updates: October 2023", "createdAt": "2023-11-06T16:25:12Z" },
|
||||
{ "title": "Platform update September 2023", "createdAt": "2023-10-06T09:23:56Z" },
|
||||
{ "title": "Platform updates: August 2023", "createdAt": "2023-09-08T13:00:39Z" },
|
||||
{ "title": "Platform updates June 2023", "createdAt": "2023-07-07T16:09:32Z" },
|
||||
{ "title": "Platform updates: May 2023", "createdAt": "2023-06-09T16:40:16Z" },
|
||||
{ "title": "Platform updates: April 2023", "createdAt": "2023-05-10T18:40:02Z" },
|
||||
{ "title": "Platform Update February 2023", "createdAt": "2023-03-09T12:06:01Z" },
|
||||
{ "title": "Platform update January 2023", "createdAt": "2023-02-08T18:29:41Z" },
|
||||
{ "title": "Platform Updates November 2022", "createdAt": "2022-12-08T12:00:48Z" },
|
||||
{ "title": "Platform updates: October 2022", "createdAt": "2022-11-02T16:07:47Z" },
|
||||
{ "title": "Platform Update September 2022", "createdAt": "2022-10-07T10:18:21Z" },
|
||||
{ "title": "Platform updates: 30 Nov 2021", "createdAt": "2021-11-30T10:06:55Z" },
|
||||
{ "title": "October Beta 2021", "createdAt": "2021-11-08T13:14:35Z" },
|
||||
{ "title": "September Beta 2021", "createdAt": "2021-10-04T18:10:57Z" },
|
||||
{ "title": "August Beta 2021", "createdAt": "2021-09-13T09:47:18Z" },
|
||||
{ "title": "July Beta 2021", "createdAt": "2021-08-12T13:46:14Z" },
|
||||
{ "title": "June Beta 2021", "createdAt": "2021-07-04T13:23:35Z" },
|
||||
{ "title": "May Beta 2021", "createdAt": "2021-06-10T11:53:14Z" },
|
||||
{ "title": "April Beta 2021", "createdAt": "2021-05-05T05:17:27Z" }
|
||||
]
|
||||
@@ -8,6 +8,60 @@ import advancedFormat from 'dayjs/plugin/advancedFormat.js'
|
||||
import utc from 'dayjs/plugin/utc.js'
|
||||
import matter from 'gray-matter'
|
||||
|
||||
/**
|
||||
* Plain `node` does not read `.env` / `.env.local` (Next.js loads those when you run `next`).
|
||||
* Minimal parser: no extra dependency; `.env` first, then `.env.local` overrides.
|
||||
*/
|
||||
function loadLocalEnvFiles(rootDir) {
|
||||
const parseValue = (raw) => {
|
||||
const val = raw.trim()
|
||||
if (val.startsWith('"') && val.endsWith('"')) {
|
||||
return val
|
||||
.slice(1, -1)
|
||||
.replace(/\\n/g, '\n')
|
||||
.replace(/\\"/g, '"')
|
||||
.replace(/\\\\/g, '\\')
|
||||
}
|
||||
if (val.startsWith("'") && val.endsWith("'")) {
|
||||
return val
|
||||
.slice(1, -1)
|
||||
.replace(/\\n/g, '\n')
|
||||
.replace(/\\'/g, "'")
|
||||
.replace(/\\\\/g, '\\')
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
const applyLine = (line, override) => {
|
||||
const trimmed = line.trim()
|
||||
if (!trimmed || trimmed.startsWith('#')) return
|
||||
const eq = trimmed.indexOf('=')
|
||||
if (eq === -1) return
|
||||
const key = trimmed.slice(0, eq).trim().replace(/^export\s+/i, '')
|
||||
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) return
|
||||
const value = parseValue(trimmed.slice(eq + 1))
|
||||
if (override || process.env[key] === undefined) {
|
||||
process.env[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
for (const name of ['.env', '.env.local']) {
|
||||
try {
|
||||
const fp = path.join(rootDir, name)
|
||||
const raw = fsSync.readFileSync(fp, 'utf8')
|
||||
const override = name === '.env.local'
|
||||
for (const line of raw.split(/\r?\n/)) {
|
||||
applyLine(line, override)
|
||||
}
|
||||
} catch {
|
||||
/* file missing */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const wwwRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), '..')
|
||||
loadLocalEnvFiles(wwwRoot)
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(advancedFormat)
|
||||
|
||||
@@ -263,7 +317,7 @@ await fs.writeFile(
|
||||
|
||||
console.log(`✅ Generated static content with ${latestBlogPosts.length} blog posts`)
|
||||
|
||||
// Generate RSS feed
|
||||
// Generate blog and changelog RSS feed
|
||||
try {
|
||||
const allBlogPosts = await getAllBlogPosts()
|
||||
|
||||
@@ -328,3 +382,96 @@ try {
|
||||
} catch (error) {
|
||||
console.warn('Error generating RSS feed:', error)
|
||||
}
|
||||
|
||||
// Changelog RSS → public/changelog-rss.xml (same GitHub App + category as lib/changelog-github.ts)
|
||||
try {
|
||||
const appId = process.env.GITHUB_CHANGELOG_APP_ID
|
||||
const installationId = process.env.GITHUB_CHANGELOG_APP_INSTALLATION_ID
|
||||
const privateKey = process.env.GITHUB_CHANGELOG_APP_PRIVATE_KEY
|
||||
|
||||
if (!appId || !installationId || !privateKey) {
|
||||
console.warn('Skipping changelog RSS: missing GITHUB_CHANGELOG_APP_* env vars')
|
||||
} else {
|
||||
const { createAppAuth } = await import('@octokit/auth-app')
|
||||
const { Octokit } = await import('@octokit/core')
|
||||
const { paginateGraphql } = await import('@octokit/plugin-paginate-graphql')
|
||||
|
||||
const { generateChangelogRssXml } = await import('../lib/changelog-rss.mjs')
|
||||
const rewritesPath = path.join(__dirname, 'data/changelog-deleted-discussions.json')
|
||||
const rewrites = JSON.parse(await fs.readFile(rewritesPath, 'utf8'))
|
||||
const discussionDisplayDate = (item) => {
|
||||
const dateRewrite = rewrites.find(
|
||||
(r) => item.title && r.title && item.title.includes(r.title)
|
||||
)
|
||||
return dateRewrite ? dateRewrite.createdAt : item.createdAt
|
||||
}
|
||||
|
||||
const CHANGELOG_CATEGORY_ID = 'DIC_kwDODMpXOc4CAFUr'
|
||||
const changelogQuery = `
|
||||
query changelogDiscussionMetadata($cursor: String, $owner: String!, $repo: String!, $categoryId: ID!) {
|
||||
repository(owner: $owner, name: $repo) {
|
||||
discussions(
|
||||
first: 100
|
||||
after: $cursor
|
||||
categoryId: $categoryId
|
||||
orderBy: { field: CREATED_AT, direction: DESC }
|
||||
) {
|
||||
nodes {
|
||||
number
|
||||
title
|
||||
createdAt
|
||||
url
|
||||
}
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
endCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const ExtendedOctokit = Octokit.plugin(paginateGraphql)
|
||||
const octokit = new ExtendedOctokit({
|
||||
authStrategy: createAppAuth,
|
||||
auth: {
|
||||
appId,
|
||||
installationId,
|
||||
privateKey: privateKey.replace(/\\n/g, '\n'),
|
||||
},
|
||||
})
|
||||
|
||||
const collected = []
|
||||
let cursor = null
|
||||
let hasNextPage = true
|
||||
while (hasNextPage) {
|
||||
const {
|
||||
repository: {
|
||||
discussions: { nodes, pageInfo },
|
||||
},
|
||||
} = await octokit.graphql(changelogQuery, {
|
||||
owner: 'supabase',
|
||||
repo: 'supabase',
|
||||
categoryId: CHANGELOG_CATEGORY_ID,
|
||||
cursor,
|
||||
})
|
||||
collected.push(...nodes)
|
||||
hasNextPage = pageInfo.hasNextPage
|
||||
cursor = pageInfo.endCursor
|
||||
}
|
||||
|
||||
const entries = collected.map((item) => ({
|
||||
title: item.title,
|
||||
url: item.url,
|
||||
sortDate: discussionDisplayDate({ title: item.title, createdAt: item.createdAt }),
|
||||
}))
|
||||
|
||||
const changelogXml = generateChangelogRssXml(entries)
|
||||
const changelogRssPath = path.join(__dirname, '../public/changelog-rss.xml')
|
||||
await fs.writeFile(changelogRssPath, changelogXml.trim(), 'utf8')
|
||||
const visibleCount = entries.filter((e) => !e.title.includes('[d]')).length
|
||||
console.log(`✅ Generated changelog RSS with ${visibleCount} entries`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Error generating changelog RSS:', error)
|
||||
}
|
||||
Reference in new issue
Block a user