Răsfoiți Sursa

chore: gen changelog page off changelog json

adamelmore 3 săptămâni în urmă
părinte
comite
3dce6a6608

+ 9 - 5
packages/console/app/src/routes/changelog.json.ts

@@ -98,19 +98,23 @@ export async function GET() {
       cacheTtl: 60 * 5,
       cacheEverything: true,
     },
-  } as any)
+  } as any).catch(() => undefined)
 
-  if (!response.ok) {
-    return new Response(JSON.stringify({ releases: [] }), {
+  const fail = () =>
+    new Response(JSON.stringify({ releases: [] }), {
       status: 503,
       headers: {
         "Content-Type": "application/json",
         "Cache-Control": error,
       },
     })
-  }
 
-  const releases = (await response.json()) as Release[]
+  if (!response?.ok) return fail()
+
+  const data = await response.json().catch(() => undefined)
+  if (!Array.isArray(data)) return fail()
+
+  const releases = data as Release[]
 
   return new Response(
     JSON.stringify({

+ 33 - 101
packages/console/app/src/routes/changelog/index.tsx

@@ -1,44 +1,12 @@
 import "./index.css"
 import { Title, Meta, Link } from "@solidjs/meta"
-import { createAsync, query } from "@solidjs/router"
+import { createAsync } from "@solidjs/router"
 import { Header } from "~/component/header"
 import { Footer } from "~/component/footer"
 import { Legal } from "~/component/legal"
 import { config } from "~/config"
 import { For, Show, createSignal } from "solid-js"
-
-type Release = {
-  tag_name: string
-  name: string
-  body: string
-  published_at: string
-  html_url: string
-}
-
-const getReleases = query(async () => {
-  "use server"
-  const response = await fetch("https://api.github.com/repos/anomalyco/opencode/releases?per_page=20", {
-    headers: {
-      Accept: "application/vnd.github.v3+json",
-      "User-Agent": "OpenCode-Console",
-    },
-    cf: {
-      cacheTtl: 60 * 5,
-      cacheEverything: true,
-    },
-  } as any)
-  if (!response.ok) return []
-  return response.json() as Promise<Release[]>
-}, "releases.get")
-
-function formatDate(dateString: string) {
-  const date = new Date(dateString)
-  return date.toLocaleDateString("en-US", {
-    year: "numeric",
-    month: "short",
-    day: "numeric",
-  })
-}
+import { getRequestEvent } from "solid-js/web"
 
 type HighlightMedia = { type: "video"; src: string } | { type: "image"; src: string; width: string; height: string }
 
@@ -54,68 +22,33 @@ type HighlightGroup = {
   items: HighlightItem[]
 }
 
-function parseHighlights(body: string): HighlightGroup[] {
-  const groups = new Map<string, HighlightItem[]>()
-  const regex = /<highlight\s+source="([^"]+)">([\s\S]*?)<\/highlight>/g
-  let match
-
-  while ((match = regex.exec(body)) !== null) {
-    const source = match[1]
-    const content = match[2]
-
-    const titleMatch = content.match(/<h2>([^<]+)<\/h2>/)
-    const pMatch = content.match(/<p(?:\s+short="([^"]*)")?>([^<]+)<\/p>/)
-    const imgMatch = content.match(/<img\s+width="([^"]+)"\s+height="([^"]+)"\s+alt="[^"]*"\s+src="([^"]+)"/)
-    const videoMatch = content.match(/^\s*(https:\/\/github\.com\/user-attachments\/assets\/[a-f0-9-]+)\s*$/m)
-
-    let media: HighlightMedia | undefined
-    if (videoMatch) {
-      media = { type: "video", src: videoMatch[1] }
-    } else if (imgMatch) {
-      media = { type: "image", src: imgMatch[3], width: imgMatch[1], height: imgMatch[2] }
-    }
-
-    if (titleMatch && media) {
-      const item: HighlightItem = {
-        title: titleMatch[1],
-        description: pMatch?.[2] || "",
-        shortDescription: pMatch?.[1],
-        media,
-      }
-
-      if (!groups.has(source)) {
-        groups.set(source, [])
-      }
-      groups.get(source)!.push(item)
-    }
-  }
-
-  return Array.from(groups.entries()).map(([source, items]) => ({ source, items }))
+type ChangelogRelease = {
+  tag: string
+  name: string
+  date: string
+  url: string
+  highlights: HighlightGroup[]
+  sections: { title: string; items: string[] }[]
 }
 
-function parseMarkdown(body: string) {
-  const lines = body.split("\n")
-  const sections: { title: string; items: string[] }[] = []
-  let current: { title: string; items: string[] } | null = null
-  let skip = false
+async function getReleases() {
+  const event = getRequestEvent()
+  const url = event ? new URL("/changelog.json", event.request.url).toString() : "/changelog.json"
 
-  for (const line of lines) {
-    if (line.startsWith("## ")) {
-      if (current) sections.push(current)
-      const title = line.slice(3).trim()
-      current = { title, items: [] }
-      skip = false
-    } else if (line.startsWith("**Thank you")) {
-      skip = true
-    } else if (line.startsWith("- ") && !skip) {
-      current?.items.push(line.slice(2).trim())
-    }
-  }
-  if (current) sections.push(current)
+  const response = await fetch(url).catch(() => undefined)
+  if (!response?.ok) return []
 
-  const highlights = parseHighlights(body)
+  const json = await response.json().catch(() => undefined)
+  return Array.isArray(json?.releases) ? (json.releases as ChangelogRelease[]) : []
+}
 
-  return { sections, highlights }
+function formatDate(dateString: string) {
+  const date = new Date(dateString)
+  return date.toLocaleDateString("en-US", {
+    year: "numeric",
+    month: "short",
+    day: "numeric",
+  })
 }
 
 function ReleaseItem(props: { item: string }) {
@@ -217,28 +150,27 @@ export default function Changelog() {
           <section data-component="releases">
             <For each={releases()}>
               {(release) => {
-                const parsed = () => parseMarkdown(release.body || "")
                 return (
                   <article data-component="release">
                     <header>
                       <div data-slot="version">
-                        <a href={release.html_url} target="_blank" rel="noopener noreferrer">
-                          {release.tag_name}
+                        <a href={release.url} target="_blank" rel="noopener noreferrer">
+                          {release.tag}
                         </a>
                       </div>
-                      <time dateTime={release.published_at}>{formatDate(release.published_at)}</time>
+                      <time dateTime={release.date}>{formatDate(release.date)}</time>
                     </header>
                     <div data-slot="content">
-                      <Show when={parsed().highlights.length > 0}>
+                      <Show when={release.highlights.length > 0}>
                         <div data-component="highlights">
-                          <For each={parsed().highlights}>{(group) => <HighlightSection group={group} />}</For>
+                          <For each={release.highlights}>{(group) => <HighlightSection group={group} />}</For>
                         </div>
                       </Show>
-                      <Show when={parsed().highlights.length > 0 && parsed().sections.length > 0}>
-                        <CollapsibleSections sections={parsed().sections} />
+                      <Show when={release.highlights.length > 0 && release.sections.length > 0}>
+                        <CollapsibleSections sections={release.sections} />
                       </Show>
-                      <Show when={parsed().highlights.length === 0}>
-                        <For each={parsed().sections}>
+                      <Show when={release.highlights.length === 0}>
+                        <For each={release.sections}>
                           {(section) => (
                             <div data-component="section">
                               <h3>{section.title}</h3>