From 769b45f5be22c7a13f68ec5f93affdd09f980dbe Mon Sep 17 00:00:00 2001 From: anirudhprmar Date: Tue, 19 May 2026 11:46:32 +0530 Subject: [PATCH 1/3] feat(web): add changelog page --- .../_components/changelog-footer.tsx | 91 +++++++++++++++ .../_components/changelog-release.tsx | 49 ++++++++ .../_components/changelog-sidebar.tsx | 69 ++++++++++++ .../_components/release-contributors.tsx | 34 ++++++ apps/web/src/app/changelog/layout.tsx | 18 +++ apps/web/src/app/changelog/page.tsx | 51 +++++++++ apps/web/src/env.js | 2 + apps/web/src/lib/github.ts | 16 +++ apps/web/src/lib/html-parser.tsx | 106 ++++++++++++++++++ apps/web/src/lib/releases.ts | 22 ++++ bun.lock | 8 ++ package.json | 4 + 12 files changed, 470 insertions(+) create mode 100644 apps/web/src/app/changelog/_components/changelog-footer.tsx create mode 100644 apps/web/src/app/changelog/_components/changelog-release.tsx create mode 100644 apps/web/src/app/changelog/_components/changelog-sidebar.tsx create mode 100644 apps/web/src/app/changelog/_components/release-contributors.tsx create mode 100644 apps/web/src/app/changelog/layout.tsx create mode 100644 apps/web/src/app/changelog/page.tsx create mode 100644 apps/web/src/lib/github.ts create mode 100644 apps/web/src/lib/html-parser.tsx create mode 100644 apps/web/src/lib/releases.ts diff --git a/apps/web/src/app/changelog/_components/changelog-footer.tsx b/apps/web/src/app/changelog/_components/changelog-footer.tsx new file mode 100644 index 0000000..62a21fb --- /dev/null +++ b/apps/web/src/app/changelog/_components/changelog-footer.tsx @@ -0,0 +1,91 @@ +"use client"; + +import { ArrowRight, Github } from "lucide-react"; +import Link from "next/link"; + +import { Icons } from "@/components/icons"; +import { ThemeToggle } from "@/components/theme-toggle"; +import { URLs } from "@/lib/consts"; +import { cn } from "@/lib/utils"; + +type FooterLink = { label: string; href: string; external?: boolean }; + +const footerLinks: FooterLink[] = [ + { label: "Docs", href: "/docs" }, + { label: "Contact", href: "/contact" }, + { label: "Discord", href: URLs.discord, external: true }, + { label: "Changelog", href: "/changelog" }, +]; + +type ChangelogFooterProps = { + className?: string; +}; + +export function ChangelogFooter({ className }: ChangelogFooterProps) { + const year = new Date().getFullYear(); + + return ( + + ); +} diff --git a/apps/web/src/app/changelog/_components/changelog-release.tsx b/apps/web/src/app/changelog/_components/changelog-release.tsx new file mode 100644 index 0000000..7affeea --- /dev/null +++ b/apps/web/src/app/changelog/_components/changelog-release.tsx @@ -0,0 +1,49 @@ +import Link from "next/link"; + +import { Badge } from "@/components/ui/badge"; +import { ReleaseBody } from "@/lib/html-parser"; +import type { GitHubRelease } from "@/lib/releases"; +import { formatReleaseDate } from "@/lib/releases"; + +type ChangelogReleaseProps = { + release: GitHubRelease; +}; + +export function ChangelogRelease({ release }: ChangelogReleaseProps) { + return ( +
+
+
+
+ +

+ {release.tag_name} +

+ + +
+ +
+
+ {release.name && release.name !== release.tag_name ? ( +

{release.name}

+ ) : null} +
+
+ + {release.body ? ( +
+ +
+ ) : null} +
+ ); +} diff --git a/apps/web/src/app/changelog/_components/changelog-sidebar.tsx b/apps/web/src/app/changelog/_components/changelog-sidebar.tsx new file mode 100644 index 0000000..30d0ef2 --- /dev/null +++ b/apps/web/src/app/changelog/_components/changelog-sidebar.tsx @@ -0,0 +1,69 @@ +import { ExternalLink, Github, HistoryIcon } from "lucide-react"; +import Link from "next/link"; + +import { URLs } from "@/lib/consts"; +import type { GitHubRelease } from "@/lib/releases"; +import { cn } from "@/lib/utils"; + +type ChangelogSidebarProps = { + latestRelease: GitHubRelease | undefined; + releaseCount: number; +}; + +export function ChangelogSidebar({ latestRelease, releaseCount }: ChangelogSidebarProps) { + return ( + + ); +} diff --git a/apps/web/src/app/changelog/_components/release-contributors.tsx b/apps/web/src/app/changelog/_components/release-contributors.tsx new file mode 100644 index 0000000..1a9e87f --- /dev/null +++ b/apps/web/src/app/changelog/_components/release-contributors.tsx @@ -0,0 +1,34 @@ +import Link from "next/link"; + +type ReleaseContributorsProps = { + contributors: string[]; +}; + +export function ReleaseContributors({ contributors }: ReleaseContributorsProps) { + if (contributors.length === 0) return null; + + return ( +
+

Contributors

+

+ Thanks to everyone who contributed to this release. +

+ +
+ ); +} diff --git a/apps/web/src/app/changelog/layout.tsx b/apps/web/src/app/changelog/layout.tsx new file mode 100644 index 0000000..2ee2ac7 --- /dev/null +++ b/apps/web/src/app/changelog/layout.tsx @@ -0,0 +1,18 @@ +import type { ReactNode } from "react"; + +import { CommandMenuProvider } from "@/components/command-menu"; +import { NavigationBar } from "@/components/layout/navigation-bar"; +import { PageTransition } from "@/components/layout/page-transition"; + +export default function MarketingLayout({ children }: { children: ReactNode }) { + return ( + +
+ +
+ {children} +
+
+
+ ); +} diff --git a/apps/web/src/app/changelog/page.tsx b/apps/web/src/app/changelog/page.tsx new file mode 100644 index 0000000..b42a19a --- /dev/null +++ b/apps/web/src/app/changelog/page.tsx @@ -0,0 +1,51 @@ +import type { Metadata } from "next"; + +import { ChangelogFooter } from "@/app/changelog/_components/changelog-footer"; +import { ChangelogRelease } from "@/app/changelog/_components/changelog-release"; +import { ChangelogSidebar } from "@/app/changelog/_components/changelog-sidebar"; +import { SectionShell } from "@/components/layout/section"; +import { getReleases } from "@/lib/github"; +import type { GitHubRelease } from "@/lib/releases"; + +export const metadata: Metadata = { + title: "Changelog – PayKit", + description: "Stay up to date with the latest changes to PayKit.", +}; + +export default async function ChangelogPage() { + const releases = (await getReleases()) as GitHubRelease[]; + const latestRelease = releases[0]; + + return ( +
+ + + +
+
+
+
+

+ Changelog +

+
+
+ + {releases.length === 0 ? ( +

No releases found yet.

+ ) : ( +
+ {releases.map((release) => ( + + ))} +
+ )} +
+ + +
+
+ +
+ ); +} diff --git a/apps/web/src/env.js b/apps/web/src/env.js index 96eac3e..b94db99 100644 --- a/apps/web/src/env.js +++ b/apps/web/src/env.js @@ -7,6 +7,7 @@ export const env = createEnv({ RESEND_API_KEY: z.string().min(1), RESEND_FROM_EMAIL: z.string().email().default("contact@paykit.sh"), RESEND_TO_EMAIL: z.string().email().default("contact@paykit.sh"), + GITHUB_TOKEN: z.string().min(1).optional(), }, client: {}, runtimeEnv: { @@ -14,6 +15,7 @@ export const env = createEnv({ RESEND_API_KEY: process.env.RESEND_API_KEY, RESEND_FROM_EMAIL: process.env.RESEND_FROM_EMAIL, RESEND_TO_EMAIL: process.env.RESEND_TO_EMAIL, + GITHUB_TOKEN: process.env.GITHUB_TOKEN, }, skipValidation: !!process.env.SKIP_ENV_VALIDATION, emptyStringAsUndefined: true, diff --git a/apps/web/src/lib/github.ts b/apps/web/src/lib/github.ts new file mode 100644 index 0000000..11bff14 --- /dev/null +++ b/apps/web/src/lib/github.ts @@ -0,0 +1,16 @@ +import { env } from "@/env"; +import type { GitHubRelease } from "@/lib/releases"; + +export async function getReleases(): Promise { + const res = await fetch("https://api.github.com/repos/getpaykit/paykit/releases", { + next: { revalidate: 3600 }, + headers: { + ...(env.GITHUB_TOKEN && { Authorization: `Bearer ${env.GITHUB_TOKEN}` }), + Accept: "application/vnd.github.v3+json", + }, + }); + + if (!res.ok) throw new Error("Failed to fetch releases"); + + return res.json() as Promise; +} diff --git a/apps/web/src/lib/html-parser.tsx b/apps/web/src/lib/html-parser.tsx new file mode 100644 index 0000000..b704d9a --- /dev/null +++ b/apps/web/src/lib/html-parser.tsx @@ -0,0 +1,106 @@ +"use client"; + +import { ChevronDown, ChevronUp } from "lucide-react"; +import type { ReactNode } from "react"; +import { useState } from "react"; +import ReactMarkdown from "react-markdown"; +import rehypeRaw from "rehype-raw"; + +import { ReleaseContributors } from "@/app/changelog/_components/release-contributors"; +import { Button } from "@/components/ui/button"; +import { cn } from "@/lib/utils"; + +import { extractContributors } from "./releases"; + +const COLLAPSE_THRESHOLD = 720; + +const markdownComponents = { + h2: ({ children }: { children?: ReactNode }) => ( +

+ {children} +

+ ), + h3: ({ children }: { children?: ReactNode }) => ( +

{children}

+ ), + h4: ({ children }: { children?: ReactNode }) => ( +

+ {children} +

+ ), + p: ({ children }: { children?: ReactNode }) => ( +

{children}

+ ), + ul: ({ children }: { children?: ReactNode }) => ( +
    + {children} +
+ ), + ol: ({ children }: { children?: ReactNode }) => ( +
    + {children} +
+ ), + li: ({ children }: { children?: ReactNode }) =>
  • {children}
  • , + a: ({ href, children }: { href?: string; children?: ReactNode }) => ( + + {children} + + ), + strong: ({ children }: { children?: ReactNode }) => ( + {children} + ), + code: ({ children }: { children?: ReactNode }) => ( + + {children} + + ), + hr: () =>
    , +} as const; + +export function ReleaseBody({ body }: { body: string }) { + const [expanded, setExpanded] = useState(false); + const isCollapsible = body.length > COLLAPSE_THRESHOLD; + const contributors = extractContributors(body); + + return ( +
    +
    +
    + + {body} + + + {contributors.length >= 1 && } +
    + + {isCollapsible && !expanded ? ( + + + {isCollapsible ? ( + + ) : null} +
    + ); +} diff --git a/apps/web/src/lib/releases.ts b/apps/web/src/lib/releases.ts new file mode 100644 index 0000000..8baa4ce --- /dev/null +++ b/apps/web/src/lib/releases.ts @@ -0,0 +1,22 @@ +export type GitHubRelease = { + id: number; + tag_name: string; + name: string; + body: string; + published_at: string; + html_url: string; + prerelease: boolean; + mentions_count: number; +}; + +export function extractContributors(body: string): string[] { + return [...new Set([...body.matchAll(/@([a-zA-Z0-9-]+)/g)].map((match) => match[1]!))]; +} + +export function formatReleaseDate(iso: string): string { + return new Date(iso).toLocaleDateString("en-US", { + month: "long", + day: "numeric", + year: "numeric", + }); +} diff --git a/bun.lock b/bun.lock index 1e79151..2f34410 100644 --- a/bun.lock +++ b/bun.lock @@ -4,6 +4,10 @@ "workspaces": { "": { "name": "paykitjs-root", + "dependencies": { + "react-markdown": "^10.1.0", + "rehype-raw": "^7.0.0", + }, "devDependencies": { "@types/node": "catalog:", "bumpp": "^10.4.1", @@ -1609,6 +1613,8 @@ "hookable": ["hookable@6.1.1", "https://registry.better-npm.dev/hookable/-/hookable-6.1.1.tgz", {}, "sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ=="], + "html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="], + "html-void-elements": ["html-void-elements@3.0.0", "https://registry.better-npm.dev/html-void-elements/-/html-void-elements-3.0.0.tgz", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="], "http-errors": ["http-errors@2.0.1", "https://registry.better-npm.dev/http-errors/-/http-errors-2.0.1.tgz", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="], @@ -2163,6 +2169,8 @@ "react-is": ["react-is@16.13.1", "https://registry.better-npm.dev/react-is/-/react-is-16.13.1.tgz", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], + "react-markdown": ["react-markdown@10.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "html-url-attributes": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "unified": "^11.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" }, "peerDependencies": { "@types/react": ">=18", "react": ">=18" } }, "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ=="], + "react-reconciler": ["react-reconciler@0.33.0", "https://registry.better-npm.dev/react-reconciler/-/react-reconciler-0.33.0.tgz", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA=="], "react-redux": ["react-redux@9.2.0", "https://registry.better-npm.dev/react-redux/-/react-redux-9.2.0.tgz", { "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "@types/react": "^18.2.25 || ^19", "react": "^18.0 || ^19", "redux": "^5.0.0" }, "optionalPeers": ["@types/react", "redux"] }, "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g=="], diff --git a/package.json b/package.json index b17c5e4..6eb779d 100644 --- a/package.json +++ b/package.json @@ -74,5 +74,9 @@ "tailwindcss": "^4.1.18", "tailwindcss-animate": "^1.0.7" } + }, + "dependencies": { + "react-markdown": "^10.1.0", + "rehype-raw": "^7.0.0" } } From d0463cc4448e1a1cbe263d0318c2460afb816a72 Mon Sep 17 00:00:00 2001 From: anirudhprmar Date: Tue, 19 May 2026 12:00:35 +0530 Subject: [PATCH 2/3] chore(web): update navbar and footer with changelog --- apps/web/src/app/changelog/page.tsx | 6 +++--- apps/web/src/app/sitemap.ts | 6 ++++++ .../changelog}/changelog-footer.tsx | 0 .../changelog}/changelog-release.tsx | 0 .../changelog}/changelog-sidebar.tsx | 0 .../changelog}/release-contributors.tsx | 0 apps/web/src/components/layout/navigation-bar.tsx | 1 + apps/web/src/components/sections/footer-section.tsx | 1 + apps/web/src/lib/consts.ts | 1 + apps/web/src/lib/html-parser.tsx | 2 +- 10 files changed, 13 insertions(+), 4 deletions(-) rename apps/web/src/{app/changelog/_components => components/changelog}/changelog-footer.tsx (100%) rename apps/web/src/{app/changelog/_components => components/changelog}/changelog-release.tsx (100%) rename apps/web/src/{app/changelog/_components => components/changelog}/changelog-sidebar.tsx (100%) rename apps/web/src/{app/changelog/_components => components/changelog}/release-contributors.tsx (100%) diff --git a/apps/web/src/app/changelog/page.tsx b/apps/web/src/app/changelog/page.tsx index b42a19a..2a82af7 100644 --- a/apps/web/src/app/changelog/page.tsx +++ b/apps/web/src/app/changelog/page.tsx @@ -1,8 +1,8 @@ import type { Metadata } from "next"; -import { ChangelogFooter } from "@/app/changelog/_components/changelog-footer"; -import { ChangelogRelease } from "@/app/changelog/_components/changelog-release"; -import { ChangelogSidebar } from "@/app/changelog/_components/changelog-sidebar"; +import { ChangelogFooter } from "@/components/changelog/changelog-footer"; +import { ChangelogRelease } from "@/components/changelog/changelog-release"; +import { ChangelogSidebar } from "@/components/changelog/changelog-sidebar"; import { SectionShell } from "@/components/layout/section"; import { getReleases } from "@/lib/github"; import type { GitHubRelease } from "@/lib/releases"; diff --git a/apps/web/src/app/sitemap.ts b/apps/web/src/app/sitemap.ts index cf2b994..b537bd9 100644 --- a/apps/web/src/app/sitemap.ts +++ b/apps/web/src/app/sitemap.ts @@ -19,5 +19,11 @@ export default function sitemap(): MetadataRoute.Sitemap { priority: 1, }, ...docsRoutes, + { + url: URLs.changelog, + lastModified: new Date(), + changeFrequency: "weekly", + priority: 1, + }, ]; } diff --git a/apps/web/src/app/changelog/_components/changelog-footer.tsx b/apps/web/src/components/changelog/changelog-footer.tsx similarity index 100% rename from apps/web/src/app/changelog/_components/changelog-footer.tsx rename to apps/web/src/components/changelog/changelog-footer.tsx diff --git a/apps/web/src/app/changelog/_components/changelog-release.tsx b/apps/web/src/components/changelog/changelog-release.tsx similarity index 100% rename from apps/web/src/app/changelog/_components/changelog-release.tsx rename to apps/web/src/components/changelog/changelog-release.tsx diff --git a/apps/web/src/app/changelog/_components/changelog-sidebar.tsx b/apps/web/src/components/changelog/changelog-sidebar.tsx similarity index 100% rename from apps/web/src/app/changelog/_components/changelog-sidebar.tsx rename to apps/web/src/components/changelog/changelog-sidebar.tsx diff --git a/apps/web/src/app/changelog/_components/release-contributors.tsx b/apps/web/src/components/changelog/release-contributors.tsx similarity index 100% rename from apps/web/src/app/changelog/_components/release-contributors.tsx rename to apps/web/src/components/changelog/release-contributors.tsx diff --git a/apps/web/src/components/layout/navigation-bar.tsx b/apps/web/src/components/layout/navigation-bar.tsx index 152c0e8..a4bb727 100644 --- a/apps/web/src/components/layout/navigation-bar.tsx +++ b/apps/web/src/components/layout/navigation-bar.tsx @@ -51,6 +51,7 @@ function NavLink({ const navTabs: NavItem[] = [ { name: "readme", href: "/" }, { name: "docs", href: "/docs", path: "/docs" }, + { name: "changelog", href: "/changelog" }, ]; const dropdownLinks: NavItem[] = [ diff --git a/apps/web/src/components/sections/footer-section.tsx b/apps/web/src/components/sections/footer-section.tsx index 0541f98..e422cef 100644 --- a/apps/web/src/components/sections/footer-section.tsx +++ b/apps/web/src/components/sections/footer-section.tsx @@ -11,6 +11,7 @@ const navLinks = [ { label: "Docs", href: "/docs" }, { label: "Contact", href: "/contact" }, { label: "Author", href: URLs.authorX, external: true }, + { label: "Changelog", href: "/changelog" }, ]; const socialLinks = [ diff --git a/apps/web/src/lib/consts.ts b/apps/web/src/lib/consts.ts index 8065ac0..eca1602 100644 --- a/apps/web/src/lib/consts.ts +++ b/apps/web/src/lib/consts.ts @@ -21,6 +21,7 @@ export const URLs = { discord: "https://discord.gg/nzy9NPpFNU", authorGitHub: "https://github.com/maxktz", authorX: "https://x.com/maxktz", + changelog: "https://paykit.sh/changelog", } as const; export const VERSION_TEXT = "v0.1 beta"; diff --git a/apps/web/src/lib/html-parser.tsx b/apps/web/src/lib/html-parser.tsx index b704d9a..57b9c7a 100644 --- a/apps/web/src/lib/html-parser.tsx +++ b/apps/web/src/lib/html-parser.tsx @@ -6,7 +6,7 @@ import { useState } from "react"; import ReactMarkdown from "react-markdown"; import rehypeRaw from "rehype-raw"; -import { ReleaseContributors } from "@/app/changelog/_components/release-contributors"; +import { ReleaseContributors } from "@/components/changelog/release-contributors"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; From 5d3aec87b3070d7471d5f501b7057d4f6989b7c3 Mon Sep 17 00:00:00 2001 From: anirudhprmar Date: Tue, 19 May 2026 14:14:09 +0530 Subject: [PATCH 3/3] fix(web): resolve coderabbit issues --- apps/web/src/components/changelog/changelog-footer.tsx | 5 ----- apps/web/src/components/changelog/changelog-release.tsx | 1 - apps/web/src/lib/github.ts | 1 + apps/web/src/lib/html-parser.tsx | 7 +++++-- bun.lock | 5 +++++ package.json | 3 ++- 6 files changed, 13 insertions(+), 9 deletions(-) diff --git a/apps/web/src/components/changelog/changelog-footer.tsx b/apps/web/src/components/changelog/changelog-footer.tsx index 62a21fb..e85f040 100644 --- a/apps/web/src/components/changelog/changelog-footer.tsx +++ b/apps/web/src/components/changelog/changelog-footer.tsx @@ -4,7 +4,6 @@ import { ArrowRight, Github } from "lucide-react"; import Link from "next/link"; import { Icons } from "@/components/icons"; -import { ThemeToggle } from "@/components/theme-toggle"; import { URLs } from "@/lib/consts"; import { cn } from "@/lib/utils"; @@ -79,10 +78,6 @@ export function ChangelogFooter({ className }: ChangelogFooterProps) {
    - {/* -
    - -
    */}
    diff --git a/apps/web/src/components/changelog/changelog-release.tsx b/apps/web/src/components/changelog/changelog-release.tsx index 7affeea..0a5b0a6 100644 --- a/apps/web/src/components/changelog/changelog-release.tsx +++ b/apps/web/src/components/changelog/changelog-release.tsx @@ -1,6 +1,5 @@ import Link from "next/link"; -import { Badge } from "@/components/ui/badge"; import { ReleaseBody } from "@/lib/html-parser"; import type { GitHubRelease } from "@/lib/releases"; import { formatReleaseDate } from "@/lib/releases"; diff --git a/apps/web/src/lib/github.ts b/apps/web/src/lib/github.ts index 11bff14..8ec9f1b 100644 --- a/apps/web/src/lib/github.ts +++ b/apps/web/src/lib/github.ts @@ -4,6 +4,7 @@ import type { GitHubRelease } from "@/lib/releases"; export async function getReleases(): Promise { const res = await fetch("https://api.github.com/repos/getpaykit/paykit/releases", { next: { revalidate: 3600 }, + signal: AbortSignal.timeout(10_000), headers: { ...(env.GITHUB_TOKEN && { Authorization: `Bearer ${env.GITHUB_TOKEN}` }), Accept: "application/vnd.github.v3+json", diff --git a/apps/web/src/lib/html-parser.tsx b/apps/web/src/lib/html-parser.tsx index 57b9c7a..ea8a2ef 100644 --- a/apps/web/src/lib/html-parser.tsx +++ b/apps/web/src/lib/html-parser.tsx @@ -5,9 +5,9 @@ import type { ReactNode } from "react"; import { useState } from "react"; import ReactMarkdown from "react-markdown"; import rehypeRaw from "rehype-raw"; +import rehypeSanitize, { defaultSchema } from "rehype-sanitize"; import { ReleaseContributors } from "@/components/changelog/release-contributors"; -import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; import { extractContributors } from "./releases"; @@ -77,7 +77,10 @@ export function ReleaseBody({ body }: { body: string }) { isCollapsible && !expanded && "max-h-64", )} > - + {body} diff --git a/bun.lock b/bun.lock index 2f34410..badc4dc 100644 --- a/bun.lock +++ b/bun.lock @@ -7,6 +7,7 @@ "dependencies": { "react-markdown": "^10.1.0", "rehype-raw": "^7.0.0", + "rehype-sanitize": "^6.0.0", }, "devDependencies": { "@types/node": "catalog:", @@ -1593,6 +1594,8 @@ "hast-util-raw": ["hast-util-raw@9.1.0", "https://registry.better-npm.dev/hast-util-raw/-/hast-util-raw-9.1.0.tgz", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "hast-util-from-parse5": "^8.0.0", "hast-util-to-parse5": "^8.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "parse5": "^7.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw=="], + "hast-util-sanitize": ["hast-util-sanitize@5.0.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "unist-util-position": "^5.0.0" } }, "sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg=="], + "hast-util-to-estree": ["hast-util-to-estree@3.1.3", "https://registry.better-npm.dev/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-attach-comments": "^3.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-js": "^1.0.0", "unist-util-position": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w=="], "hast-util-to-html": ["hast-util-to-html@9.0.5", "https://registry.better-npm.dev/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="], @@ -2215,6 +2218,8 @@ "rehype-recma": ["rehype-recma@1.0.0", "https://registry.better-npm.dev/rehype-recma/-/rehype-recma-1.0.0.tgz", { "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", "hast-util-to-estree": "^3.0.0" } }, "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw=="], + "rehype-sanitize": ["rehype-sanitize@6.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-sanitize": "^5.0.0" } }, "sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg=="], + "remark": ["remark@15.0.1", "https://registry.better-npm.dev/remark/-/remark-15.0.1.tgz", { "dependencies": { "@types/mdast": "^4.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A=="], "remark-gfm": ["remark-gfm@4.0.1", "https://registry.better-npm.dev/remark-gfm/-/remark-gfm-4.0.1.tgz", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="], diff --git a/package.json b/package.json index 6eb779d..31041af 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ }, "dependencies": { "react-markdown": "^10.1.0", - "rehype-raw": "^7.0.0" + "rehype-raw": "^7.0.0", + "rehype-sanitize": "^6.0.0" } }