Skip to content

Commit 3dce6a6

Browse files
committed
chore: gen changelog page off changelog json
1 parent 39a73d4 commit 3dce6a6

2 files changed

Lines changed: 42 additions & 106 deletions

File tree

packages/console/app/src/routes/changelog.json.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,19 +98,23 @@ export async function GET() {
9898
cacheTtl: 60 * 5,
9999
cacheEverything: true,
100100
},
101-
} as any)
101+
} as any).catch(() => undefined)
102102

103-
if (!response.ok) {
104-
return new Response(JSON.stringify({ releases: [] }), {
103+
const fail = () =>
104+
new Response(JSON.stringify({ releases: [] }), {
105105
status: 503,
106106
headers: {
107107
"Content-Type": "application/json",
108108
"Cache-Control": error,
109109
},
110110
})
111-
}
112111

113-
const releases = (await response.json()) as Release[]
112+
if (!response?.ok) return fail()
113+
114+
const data = await response.json().catch(() => undefined)
115+
if (!Array.isArray(data)) return fail()
116+
117+
const releases = data as Release[]
114118

115119
return new Response(
116120
JSON.stringify({

packages/console/app/src/routes/changelog/index.tsx

Lines changed: 33 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,12 @@
11
import "./index.css"
22
import { Title, Meta, Link } from "@solidjs/meta"
3-
import { createAsync, query } from "@solidjs/router"
3+
import { createAsync } from "@solidjs/router"
44
import { Header } from "~/component/header"
55
import { Footer } from "~/component/footer"
66
import { Legal } from "~/component/legal"
77
import { config } from "~/config"
88
import { For, Show, createSignal } from "solid-js"
9-
10-
type Release = {
11-
tag_name: string
12-
name: string
13-
body: string
14-
published_at: string
15-
html_url: string
16-
}
17-
18-
const getReleases = query(async () => {
19-
"use server"
20-
const response = await fetch("https://api.github.com/repos/anomalyco/opencode/releases?per_page=20", {
21-
headers: {
22-
Accept: "application/vnd.github.v3+json",
23-
"User-Agent": "OpenCode-Console",
24-
},
25-
cf: {
26-
cacheTtl: 60 * 5,
27-
cacheEverything: true,
28-
},
29-
} as any)
30-
if (!response.ok) return []
31-
return response.json() as Promise<Release[]>
32-
}, "releases.get")
33-
34-
function formatDate(dateString: string) {
35-
const date = new Date(dateString)
36-
return date.toLocaleDateString("en-US", {
37-
year: "numeric",
38-
month: "short",
39-
day: "numeric",
40-
})
41-
}
9+
import { getRequestEvent } from "solid-js/web"
4210

4311
type HighlightMedia = { type: "video"; src: string } | { type: "image"; src: string; width: string; height: string }
4412

@@ -54,68 +22,33 @@ type HighlightGroup = {
5422
items: HighlightItem[]
5523
}
5624

57-
function parseHighlights(body: string): HighlightGroup[] {
58-
const groups = new Map<string, HighlightItem[]>()
59-
const regex = /<highlight\s+source="([^"]+)">([\s\S]*?)<\/highlight>/g
60-
let match
61-
62-
while ((match = regex.exec(body)) !== null) {
63-
const source = match[1]
64-
const content = match[2]
65-
66-
const titleMatch = content.match(/<h2>([^<]+)<\/h2>/)
67-
const pMatch = content.match(/<p(?:\s+short="([^"]*)")?>([^<]+)<\/p>/)
68-
const imgMatch = content.match(/<img\s+width="([^"]+)"\s+height="([^"]+)"\s+alt="[^"]*"\s+src="([^"]+)"/)
69-
const videoMatch = content.match(/^\s*(https:\/\/github\.com\/user-attachments\/assets\/[a-f0-9-]+)\s*$/m)
70-
71-
let media: HighlightMedia | undefined
72-
if (videoMatch) {
73-
media = { type: "video", src: videoMatch[1] }
74-
} else if (imgMatch) {
75-
media = { type: "image", src: imgMatch[3], width: imgMatch[1], height: imgMatch[2] }
76-
}
77-
78-
if (titleMatch && media) {
79-
const item: HighlightItem = {
80-
title: titleMatch[1],
81-
description: pMatch?.[2] || "",
82-
shortDescription: pMatch?.[1],
83-
media,
84-
}
85-
86-
if (!groups.has(source)) {
87-
groups.set(source, [])
88-
}
89-
groups.get(source)!.push(item)
90-
}
91-
}
92-
93-
return Array.from(groups.entries()).map(([source, items]) => ({ source, items }))
25+
type ChangelogRelease = {
26+
tag: string
27+
name: string
28+
date: string
29+
url: string
30+
highlights: HighlightGroup[]
31+
sections: { title: string; items: string[] }[]
9432
}
9533

96-
function parseMarkdown(body: string) {
97-
const lines = body.split("\n")
98-
const sections: { title: string; items: string[] }[] = []
99-
let current: { title: string; items: string[] } | null = null
100-
let skip = false
34+
async function getReleases() {
35+
const event = getRequestEvent()
36+
const url = event ? new URL("/changelog.json", event.request.url).toString() : "/changelog.json"
10137

102-
for (const line of lines) {
103-
if (line.startsWith("## ")) {
104-
if (current) sections.push(current)
105-
const title = line.slice(3).trim()
106-
current = { title, items: [] }
107-
skip = false
108-
} else if (line.startsWith("**Thank you")) {
109-
skip = true
110-
} else if (line.startsWith("- ") && !skip) {
111-
current?.items.push(line.slice(2).trim())
112-
}
113-
}
114-
if (current) sections.push(current)
38+
const response = await fetch(url).catch(() => undefined)
39+
if (!response?.ok) return []
11540

116-
const highlights = parseHighlights(body)
41+
const json = await response.json().catch(() => undefined)
42+
return Array.isArray(json?.releases) ? (json.releases as ChangelogRelease[]) : []
43+
}
11744

118-
return { sections, highlights }
45+
function formatDate(dateString: string) {
46+
const date = new Date(dateString)
47+
return date.toLocaleDateString("en-US", {
48+
year: "numeric",
49+
month: "short",
50+
day: "numeric",
51+
})
11952
}
12053

12154
function ReleaseItem(props: { item: string }) {
@@ -217,28 +150,27 @@ export default function Changelog() {
217150
<section data-component="releases">
218151
<For each={releases()}>
219152
{(release) => {
220-
const parsed = () => parseMarkdown(release.body || "")
221153
return (
222154
<article data-component="release">
223155
<header>
224156
<div data-slot="version">
225-
<a href={release.html_url} target="_blank" rel="noopener noreferrer">
226-
{release.tag_name}
157+
<a href={release.url} target="_blank" rel="noopener noreferrer">
158+
{release.tag}
227159
</a>
228160
</div>
229-
<time dateTime={release.published_at}>{formatDate(release.published_at)}</time>
161+
<time dateTime={release.date}>{formatDate(release.date)}</time>
230162
</header>
231163
<div data-slot="content">
232-
<Show when={parsed().highlights.length > 0}>
164+
<Show when={release.highlights.length > 0}>
233165
<div data-component="highlights">
234-
<For each={parsed().highlights}>{(group) => <HighlightSection group={group} />}</For>
166+
<For each={release.highlights}>{(group) => <HighlightSection group={group} />}</For>
235167
</div>
236168
</Show>
237-
<Show when={parsed().highlights.length > 0 && parsed().sections.length > 0}>
238-
<CollapsibleSections sections={parsed().sections} />
169+
<Show when={release.highlights.length > 0 && release.sections.length > 0}>
170+
<CollapsibleSections sections={release.sections} />
239171
</Show>
240-
<Show when={parsed().highlights.length === 0}>
241-
<For each={parsed().sections}>
172+
<Show when={release.highlights.length === 0}>
173+
<For each={release.sections}>
242174
{(section) => (
243175
<div data-component="section">
244176
<h3>{section.title}</h3>

0 commit comments

Comments
 (0)