From ee4660daca112a55e455ff3fca1c649f7fe11fea Mon Sep 17 00:00:00 2001 From: Kian Newman-Hazel Date: Fri, 31 Jan 2025 21:48:55 +0000 Subject: [PATCH 1/2] [Docs Site] Refactor changelog collection handling --- package-lock.json | 19 +- package.json | 2 +- public/_redirects | 1 + src/components/Changelogs.tsx | 238 ++++++++++++++++++ src/components/ProductChangelog.astro | 122 +++------ .../overrides/MarkdownContent.astro | 5 + src/pages/[...changelog].xml.ts | 108 ++------ src/pages/changelog/index.astro | 184 ++------------ src/pages/changelog/index.xml.ts | 84 +------ src/pages/deprecations/index.astro | 34 --- src/util/changelogs.ts | 136 ++++++---- 11 files changed, 429 insertions(+), 504 deletions(-) create mode 100644 src/components/Changelogs.tsx delete mode 100644 src/pages/deprecations/index.astro diff --git a/package-lock.json b/package-lock.json index 1b446030b94060..fdc86b919542be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,6 @@ "@cloudflare/puppeteer": "0.0.14", "@cloudflare/vitest-pool-workers": "0.6.0", "@cloudflare/workers-types": "4.20250121.0", - "@codingheads/sticky-header": "1.0.2", "@expressive-code/plugin-collapsible-sections": "0.40.1", "@iarna/toml": "2.2.5", "@iconify-json/material-symbols": "1.2.13", @@ -52,6 +51,7 @@ "jsonc-parser": "3.3.1", "lz-string": "1.5.0", "marked": "15.0.6", + "marked-base-url": "1.1.6", "mdast-util-mdx-expression": "2.0.1", "mermaid": "11.4.1", "node-html-parser": "7.0.1", @@ -2233,13 +2233,6 @@ "dev": true, "license": "MIT OR Apache-2.0" }, - "node_modules/@codingheads/sticky-header": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@codingheads/sticky-header/-/sticky-header-1.0.2.tgz", - "integrity": "sha512-pNaKWoDBN2393L++sRLfctsXpG6nDuy9+eq+NVv/Vf+z/23MUPqF4JpFHzZ9W0sQmFb3KGtwcclgDyUwt1klww==", - "dev": true, - "license": "GPL-3" - }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -13832,6 +13825,16 @@ "node": ">= 18" } }, + "node_modules/marked-base-url": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/marked-base-url/-/marked-base-url-1.1.6.tgz", + "integrity": "sha512-STFGZN1kBZIWaw5RGwY63mSCwUXUmUDOAPRMAuPkg2Iou4ZWUsd7nFqvh0ifMYekqKuHHdmqJUqCl0HUakBbMQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "marked": ">= 4 < 16" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", diff --git a/package.json b/package.json index 4de981223a3ed5..318b4eddfcd0f8 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,6 @@ "@cloudflare/puppeteer": "0.0.14", "@cloudflare/vitest-pool-workers": "0.6.0", "@cloudflare/workers-types": "4.20250121.0", - "@codingheads/sticky-header": "1.0.2", "@expressive-code/plugin-collapsible-sections": "0.40.1", "@iarna/toml": "2.2.5", "@iconify-json/material-symbols": "1.2.13", @@ -70,6 +69,7 @@ "jsonc-parser": "3.3.1", "lz-string": "1.5.0", "marked": "15.0.6", + "marked-base-url": "1.1.6", "mdast-util-mdx-expression": "2.0.1", "mermaid": "11.4.1", "node-html-parser": "7.0.1", diff --git a/public/_redirects b/public/_redirects index 6a998490ad72ba..f02aa628fbe8b2 100644 --- a/public/_redirects +++ b/public/_redirects @@ -4,6 +4,7 @@ /dashboard-landing/ / 301 /tutorials/ /search/?content_type%5B0%5D=Tutorial 301 /sitemap.xml /sitemap-index.xml +/deprecations/ /fundamentals/api/reference/deprecations/ 301 # 1dot1_redirect /1.1.1.1/1.1.1.1-for-families/ /1.1.1.1/setup/ 301 diff --git a/src/components/Changelogs.tsx b/src/components/Changelogs.tsx new file mode 100644 index 00000000000000..8e1a622982aa52 --- /dev/null +++ b/src/components/Changelogs.tsx @@ -0,0 +1,238 @@ +import { useState } from "react"; +import type { Changelogs } from "~/util/changelogs"; + +type Changelog = Changelogs[0]; +type Product = Changelog["product"]; +type Area = Product["area"]; + +function RSSFeed({ + selectedProduct, + selectedProductRssUrl, + selectedArea, + selectedAreaRssUrl, +}: { + selectedProduct?: Product; + selectedProductRssUrl: string; + selectedArea?: Area; + selectedAreaRssUrl?: string; +}) { + const name = selectedProduct?.name ?? "changelog"; + + return ( + <> +

+ Subscribe to all {name} posts via{" "} + RSS. +

+ {selectedArea && ( +

+ Subscribe to all {selectedArea.name} posts via{" "} + RSS. +

+ )} +

+ Unless otherwise noted, all dates refer to the release date of the + change. +

+ + ); +} + +function Filters({ + changelogs, + selectedProduct, + updateSelectedProduct, + selectedArea, + updateSelectedArea, +}: { + changelogs: Changelogs; + selectedProduct?: Product; + updateSelectedProduct: (product: string) => void; + selectedArea?: Area; + updateSelectedArea: (area: string) => void; +}) { + const products = [...new Set(changelogs.flatMap((c) => c.product.name))]; + + const areas = [...new Set(changelogs.flatMap((c) => c.product.area.name))]; + + return ( +
+ + +
+ ); +} + +function Badge({ product }: { product: Product }) { + return ( + + {product.name} + + ); +} + +function Content({ changelog }: { changelog: Changelog }) { + if (changelog.individual_page) { + return ( +

+ For more details, refer to the dedicated page for{" "} + {changelog.product.name} +

+ ); + } else { + return ( +
+ ); + } +} + +export default function Changelogs({ changelogs }: { changelogs: Changelogs }) { + const [selectedProduct, setSelectedProduct] = useState(); + const [selectedArea, setSelectedArea] = useState(); + + const [selectedProductRssUrl, setSelectedProductRssUrl] = useState( + "/changelog/index.xml", + ); + const [selectedAreaRssUrl, setSelectedAreaRssUrl] = useState(); + + const filtered = changelogs.filter((changelog) => { + if (selectedArea || selectedProduct) { + return ( + changelog.product.area.name === selectedArea?.name || + changelog.product.name === selectedProduct?.name + ); + } + + return true; + }); + + const grouped = Object.entries( + Object.groupBy(filtered, (entry) => entry.date), + ) + .sort() + .reverse(); + + function updateSelectedProduct(product: string) { + if (product === "all") { + setSelectedProduct(undefined); + return; + } + + const data = changelogs.find((c) => c.product.name === product)!.product; + + setSelectedProduct(data); + setSelectedProductRssUrl(data.changelog + "index.xml"); + + if (selectedArea && selectedArea.name !== data.area.name) { + setSelectedArea(undefined); + } + } + + function updateSelectedArea(area: string) { + if (area === "all") { + setSelectedArea(undefined); + return; + } + + const data = changelogs.find((c) => c.product.area.name === area)!.product + .area; + + setSelectedArea(data); + setSelectedAreaRssUrl(data.changelog + "index.xml"); + + if (selectedProduct && selectedProduct.name !== data.name) { + setSelectedProduct(undefined); + } + } + + return ( + <> + +

+ Looking for API deprecations? They can be found on our{" "} + + dedicated deprecations page + + . +

+
+ + {grouped.map(([date, entries], idx, arr) => ( +
+
+
+

+ {date} +

+
+
+ {entries?.map((entry, idx, arr) => ( +
+
+

+ {entry.title} +

+ {!selectedProduct && } +
+ +
+
+ {idx !== arr.length - 1 &&
} +
+ ))} +
+
+ {idx !== arr.length - 1 &&
} +
+ ))} + + ); +} diff --git a/src/components/ProductChangelog.astro b/src/components/ProductChangelog.astro index 2c2274400ea3e9..1855b33da203cb 100644 --- a/src/components/ProductChangelog.astro +++ b/src/components/ProductChangelog.astro @@ -1,9 +1,8 @@ --- -import { getEntry, type CollectionEntry } from "astro:content"; -import { marked } from "marked"; -import { getChangelogs } from "~/util/changelogs"; +import { getEntry } from "astro:content"; + import AnchorHeading from "~/components/AnchorHeading.astro"; -import { entryToString } from "~/util/container"; +import { getChangelogs, type Filter } from "~/util/changelogs"; const page = await getEntry("docs", Astro.params.slug!); @@ -15,7 +14,7 @@ if (!page) { if (!page.data.changelog_file_name && !page.data.changelog_product_area_name) { throw new Error( - `[ProductChangelog] ${Astro.params.slug} does not have a 'changelog_file_name' or 'changaelog_product_area_name' frontmatter property.`, + `[ProductChangelog] ${Astro.params.slug} does not have a 'changelog_file_name' or 'changelog_product_area_name' frontmatter property.`, ); } @@ -25,44 +24,34 @@ if (page.data.changelog_file_name && page.data.changelog_file_name.length > 1) { ); } -const name = - page.data.changelog_product_area_name ?? page.data.changelog_file_name?.[0]; - -let changelogs; +let filter: Filter; if (page.data.changelog_product_area_name) { - const opts = { - filter: (entry: CollectionEntry<"changelogs">) => { - return entry.data.productArea === name; - }, + filter = (entry) => { + return entry.data.productArea === page.data.changelog_product_area_name; }; - ({ changelogs } = await getChangelogs(opts)); } else { - if (name === "wrangler") { - const opts = { - wranglerOnly: true, - }; - ({ changelogs } = await getChangelogs(opts)); - } else if (name === "api-deprecations") { - const opts = { - deprecationsOnly: true, - }; - ({ changelogs } = await getChangelogs(opts)); - } else { - const opts = { - filter: (entry: CollectionEntry<"changelogs">) => { - return entry.id === name; - }, - }; - ({ changelogs } = await getChangelogs(opts)); - } + filter = (entry) => { + return entry.id === page.data.changelog_file_name![0]; + }; } +const changelogs = await getChangelogs({ + filter, + locals: Astro.locals, +}); + if (!changelogs) { throw new Error( - `[ProductChangelog] Failed to find changelog called ${name}.`, + `[ProductChangelog] Failed to find changelogs for ${page.data.changelog_product_area_name} or ${page.data.changelog_file_name}.`, ); } + +const grouped = Object.entries( + Object.groupBy(changelogs, (entry) => entry.date), +) + .sort() + .reverse(); --- { @@ -75,58 +64,27 @@ if (!changelogs) { ) } { - changelogs.map(([date, entries]) => ( -
+ grouped.map(([date, entries]) => ( +
{(entries ?? []).map(async (entry) => { - let description; - if (entry.individual_page) { - const link = entry.individual_page; + let shouldRenderTitle = true; - if (!link) - throw new Error( - `Changelog entry points to individual page but no link is provided`, - ); - - const page = await getEntry("docs", link.slice(1, -1)); - - if (!page) - throw new Error( - `Changelog entry points to ${link.slice(1, -1)} but unable to find entry with that slug`, - ); - - description = (await entryToString(page, Astro.locals)) ?? page.body; - - return ( -
- - - -

{entry.date}

- {page.data.changelog_product_area_name && ( -

- {entry.product} -

- )} - {} -
- ); - } else { - description = marked.parse(entry.description as string); - return ( - <> - -
- {page.data.changelog_product_area_name && ( -

- {entry.product} -

- )} - {entry.title && {entry.title}} - {} -
- - ); + if (entry.title.startsWith(`${entry.product.name} - `)) { + shouldRenderTitle = false; } + + return ( +
+ + {page.data.changelog_product_area_name && ( +

+ {entry.product.name} +

+ )} + {shouldRenderTitle && {entry.title}} + +
+ ); })}
)) diff --git a/src/components/overrides/MarkdownContent.astro b/src/components/overrides/MarkdownContent.astro index 2faf0870518391..b7be196483f8f7 100644 --- a/src/components/overrides/MarkdownContent.astro +++ b/src/components/overrides/MarkdownContent.astro @@ -72,6 +72,11 @@ const { tableOfContents } = Astro.props.entry.data; } } + .sl-markdown-content :global(pre) { + overflow-wrap: anywhere; + white-space: pre-wrap; + } + /* Custom styles for heading anchor links. */ .sl-markdown-content :global(.heading-wrapper) { --icon-size: 0.75em; diff --git a/src/pages/[...changelog].xml.ts b/src/pages/[...changelog].xml.ts index 531a2888c7eeed..fda8f05be18afc 100644 --- a/src/pages/[...changelog].xml.ts +++ b/src/pages/[...changelog].xml.ts @@ -1,10 +1,7 @@ import rss from "@astrojs/rss"; -import { getCollection, getEntry } from "astro:content"; +import { getCollection } from "astro:content"; import type { APIRoute } from "astro"; -import { marked, type Token } from "marked"; -import { getWranglerChangelog } from "~/util/changelogs"; -import { slug } from "github-slugger"; -import { entryToString } from "~/util/container"; +import { getChangelogs } from "~/util/changelogs"; export async function getStaticPaths() { const changelogs = await getCollection("docs", (entry) => { @@ -28,16 +25,6 @@ export async function getStaticPaths() { } export const GET: APIRoute = async (context) => { - function walkTokens(token: Token) { - if (token.type === "image" || token.type === "link") { - if (token.href.startsWith("/")) { - token.href = context.site + token.href.slice(1); - } - } - } - - marked.use({ walkTokens }); - const entry = context.props.entry; if ( @@ -49,94 +36,45 @@ export const GET: APIRoute = async (context) => { ); } - const changelogs = await getCollection("changelogs", (changelog) => { - return ( - entry.data.changelog_file_name?.includes(changelog.id) || - changelog.data.productArea === entry.data.changelog_product_area_name - ); + const changelogs = await getChangelogs({ + filter: (changelog) => { + return ( + entry.data.changelog_file_name?.includes(changelog.id) || + changelog.data.productArea === entry.data.changelog_product_area_name + ); + }, + locals: context.locals, + addBaseUrl: true, }); - if (entry.data.changelog_file_name?.includes("wrangler")) { - changelogs.push(await getWranglerChangelog()); + if (!changelogs) { + throw new Error( + `Filter for ${entry.data.changelog_file_name} or ${entry.data.changelog_product_area_name} removed all results.`, + ); } - const mapped = await Promise.all( - changelogs.flatMap((product) => { - return product.data.entries.map(async (entry) => { - let description; - if (entry.individual_page) { - const link = entry.link; - - if (!link) - throw new Error( - `Changelog entry points to individual page but no link is provided`, - ); - - const page = await getEntry("docs", link.slice(1, -1)); - - if (!page) - throw new Error( - `Changelog entry points to ${link.slice(1, -1)} but unable to find entry with that slug`, - ); - - description = - (await entryToString(page, context.locals)) ?? page.body; - } else { - description = entry.description; - } - - let link; - if (entry.link) { - link = entry.link; - } else { - const anchor = slug(entry.title ?? entry.publish_date); - link = product.data.link.concat(`#${anchor}`); - } - - let title; - if (entry.scheduled) { - title = `Scheduled for ${entry.scheduled_date}`; - } else { - title = entry.title; - } - - return { - product: product.data.productName, - link, - date: entry.publish_date, - description, - title, - }; - }); - }), - ); - - const entries = mapped.sort((a, b) => { + const entries = changelogs.sort((a, b) => { return a.date < b.date ? 1 : a.date > b.date ? -1 : 0; }); const rssName = - entry.data.changelog_product_area_name || changelogs[0].data.productName; - - const site = new URL(context.site ?? ""); - site.pathname = entry.id.concat("/"); - + entry.data.changelog_product_area_name || changelogs.at(0)?.product.name; const isArea = Boolean(entry.data.changelog_product_area_name); return rss({ title: `Changelog | ${rssName}`, description: `Updates to ${rssName}`, - site, + site: "https://developers.cloudflare.com/" + entry.id + "/", trailingSlash: false, items: entries.map((entry) => { return { - title: `${entry.product} - ${entry.title ?? entry.date}`, - description: marked.parse(entry.description ?? "", { - async: false, - }) as string, + title: `${entry.product.name} - ${entry.title}`, + description: entry.content, pubDate: new Date(entry.date), link: entry.link, - customData: isArea ? `${entry.product}` : undefined, + customData: isArea + ? `${entry.product.name}` + : undefined, }; }), }); diff --git a/src/pages/changelog/index.astro b/src/pages/changelog/index.astro index d9a83a25ef0d8b..64d1b1c0f26751 100644 --- a/src/pages/changelog/index.astro +++ b/src/pages/changelog/index.astro @@ -1,172 +1,24 @@ --- +import type { StarlightPageProps } from "@astrojs/starlight/props"; import StarlightPage from "@astrojs/starlight/components/StarlightPage.astro"; -import { Aside } from "~/components"; -import { marked } from "marked"; -import { format } from "date-fns"; -import { getChangelogs } from "~/util/changelogs"; -import { getEntry } from "astro:content"; +import { Aside } from "@astrojs/starlight/components"; -const { products, productAreas, changelogs } = await getChangelogs(); +import { getChangelogs } from "~/util/changelogs"; +import Changelogs from "~/components/Changelogs.tsx"; + +const changelogs = await getChangelogs({ + filter: (entry) => entry.id !== "api-deprecations", + locals: Astro.locals, +}); + +const props = { + frontmatter: { + title: "Cloudflare Product Changelogs", + template: "splash", + }, +} as StarlightPageProps; --- - - -

- Subscribe to all Changelog posts via RSS. -

- -

- Unless otherwise noted, all dates refer to the release date of the change. -

-
- - - - - { - changelogs.map(([date, entries]) => ( -
-
-

{format(date, "do MMMM yyyy")}

-
-
- {entries?.map(async (entry) => { - let title = entry.title; - let description = entry.description || ""; - - if (entry.individual_page) { - const page = await getEntry( - "docs", - entry.individual_page.slice(1, -1), - ); - - if (!page) { - throw new Error( - `[Changelog] Unable to load page ${entry.individual_page}.`, - ); - } - - title = `${entry.product} - ${page.data.title}`; - description = `For more details, refer to the dedicated page for [${title}](${entry.individual_page}).`; - } - - return ( -
-

- {entry.product} -

- {title && ( -

- {title} -

- )} - -
- ); - })} -
-
- )) - } + + - - - - diff --git a/src/pages/changelog/index.xml.ts b/src/pages/changelog/index.xml.ts index 978856888cbd1b..d3dfcb463cf579 100644 --- a/src/pages/changelog/index.xml.ts +++ b/src/pages/changelog/index.xml.ts @@ -1,78 +1,14 @@ import rss from "@astrojs/rss"; -import { getCollection, getEntry } from "astro:content"; import type { APIRoute } from "astro"; -import { marked, type Token } from "marked"; -import { getWranglerChangelog } from "~/util/changelogs"; -import { slug } from "github-slugger"; -import { entryToString } from "~/util/container"; +import { getChangelogs } from "~/util/changelogs"; export const GET: APIRoute = async (context) => { - function walkTokens(token: Token) { - if (token.type === "image" || token.type === "link") { - if (token.href.startsWith("/")) { - token.href = context.site + token.href.slice(1); - } - } - } - - marked.use({ walkTokens }); - - const changelogs = await getCollection("changelogs"); - - changelogs.push(await getWranglerChangelog()); - - const mapped = await Promise.all( - changelogs.flatMap((product) => { - return product.data.entries.map(async (entry) => { - let description; - if (entry.individual_page) { - const link = entry.link; - - if (!link) - throw new Error( - `Changelog entry points to individual page but no link is provided`, - ); - - const page = await getEntry("docs", link.slice(1, -1)); - - if (!page) - throw new Error( - `Changelog entry points to ${link.slice(1, -1)} but unable to find entry with that slug`, - ); - - description = - (await entryToString(page, context.locals)) ?? page.body; - } else { - description = entry.description; - } - - let link; - if (entry.link) { - link = entry.link; - } else { - const anchor = slug(entry.title ?? entry.publish_date); - link = product.data.link.concat(`#${anchor}`); - } - - let title; - if (entry.scheduled) { - title = `Scheduled for ${entry.scheduled_date}`; - } else { - title = entry.title; - } - - return { - product: product.data.productName, - link, - date: entry.publish_date, - description, - title, - }; - }); - }), - ); + const changelogs = await getChangelogs({ + locals: context.locals, + addBaseUrl: true, + }); - const entries = mapped.sort((a, b) => { + const entries = changelogs.sort((a, b) => { return a.date < b.date ? 1 : a.date > b.date ? -1 : 0; }); @@ -83,13 +19,11 @@ export const GET: APIRoute = async (context) => { trailingSlash: false, items: entries.map((entry) => { return { - title: `${entry.product} - ${entry.title ?? entry.date}`, - description: marked.parse(entry.description ?? "", { - async: false, - }) as string, + title: entry.title, + description: entry.content, pubDate: new Date(entry.date), link: entry.link, - customData: `${entry.product}`, + customData: `${entry.product.name}`, }; }), }); diff --git a/src/pages/deprecations/index.astro b/src/pages/deprecations/index.astro deleted file mode 100644 index 0aee2ae896ec57..00000000000000 --- a/src/pages/deprecations/index.astro +++ /dev/null @@ -1,34 +0,0 @@ ---- -import StarlightPage from "@astrojs/starlight/components/StarlightPage.astro"; -import { marked } from "marked"; -import { getChangelogs } from "~/util/changelogs"; - -const { changelogs } = await getChangelogs({ - deprecationsOnly: true, -}); ---- - - -

- Unless otherwise noted, all dates refer to the release date of the change. -

-
- { - changelogs.map(([date, entries]) => ( -
-

{date}

- {entries?.map((entry) => ( -
-

- {entry.product} -

-

-

- ))} -
- )) - } -
diff --git a/src/util/changelogs.ts b/src/util/changelogs.ts index 21cc14bf00c63f..3e9ea8442f1954 100644 --- a/src/util/changelogs.ts +++ b/src/util/changelogs.ts @@ -1,65 +1,95 @@ import { z } from "astro:schema"; -import { getCollection } from "astro:content"; +import { getCollection, getEntry } from "astro:content"; import { type CollectionEntry } from "astro:content"; +import { entryToString } from "./container"; +import { Marked, marked } from "marked"; +import { baseUrl } from "marked-base-url"; -export async function getChangelogs(opts?: { - filter?: Parameters>[1]; - wranglerOnly?: boolean; - deprecationsOnly?: boolean; +export type Changelogs = Awaited>; +export type Filter = (entry: CollectionEntry<"changelogs">) => boolean; + +export async function getChangelogs({ + filter, + locals, + addBaseUrl, +}: { + filter?: Filter; + locals: App.Locals; + addBaseUrl?: boolean; }) { - let changelogs; - - if (opts?.wranglerOnly) { - changelogs = [await getWranglerChangelog()]; - } else if (opts?.filter) { - changelogs = await getCollection("changelogs", opts.filter); - } else { - changelogs = await getCollection("changelogs"); - } + let changelogs = await getCollection("changelogs"); + changelogs.push(wranglerChangelogs); - if (!changelogs) { - throw new Error( - `[getChangelogs] Unable to find any changelogs with ${JSON.stringify(opts)}`, - ); + if (filter) { + changelogs = changelogs.filter((c) => filter(c)); } - if (opts?.deprecationsOnly) { - changelogs = changelogs.filter((x) => x.id === "api-deprecations"); - } else { - changelogs = changelogs.filter((x) => x.id !== "api-deprecations"); + const marked = new Marked(); + if (addBaseUrl) { + marked.use(baseUrl("https://developers.cloudflare.com/")); } - const products = [...new Set(changelogs.flatMap((x) => x.data.productName))]; - const productAreas = [ - ...new Set(changelogs.flatMap((x) => x.data.productArea)), - ]; - - const mapped = changelogs.flatMap((product) => { - return product.data.entries.map((entry) => { - return { - product: product.data.productName, - link: product.data.link, - date: entry.publish_date, - description: entry.description, - title: entry.title, - scheduled: entry.scheduled, - productLink: product.data.productLink, - productAreaName: product.data.productArea, - productAreaLink: product.data.productAreaLink, - individual_page: entry.individual_page && entry.link, - }; - }); - }); - - const grouped = Object.entries(Object.groupBy(mapped, (entry) => entry.date)); - const entries = grouped.sort().reverse(); - - return { products, productAreas, changelogs: entries }; + const data = changelogs.map((c) => c.data); + + return await Promise.all( + data.flatMap((changelog) => { + return changelog.entries.map(async (entry) => { + let title: string; + let link: string; + let content: string; + + if (!entry.title) { + if (entry.scheduled && entry.scheduled_date) { + title = `${changelog.productName} - Scheduled changes for ${entry.scheduled_date}`; + } else { + title = `${changelog.productName} - ${entry.publish_date}`; + } + } else { + title = entry.title; + } + + if (entry.individual_page) { + if (!entry.link) throw new Error(""); + + const page = await getEntry("docs", entry.link.slice(1, -1)); + if (!page) throw new Error(""); + + link = entry.link; + content = await entryToString(page, locals); + } else { + if (!entry.description) throw new Error(""); + + link = changelog.link + `#${entry.publish_date}`; + content = marked.parse(entry.description, { async: false }); + } + + return { + product: { + name: changelog.productName, + link: changelog.productLink, + changelog: changelog.link, + area: { + name: changelog.productArea, + changelog: changelog.productAreaLink, + }, + }, + title, + date: entry.publish_date, + individual_page: entry.individual_page, + scheduled: entry.scheduled, + scheduled_date: entry.scheduled_date, + link, + content, + }; + }); + }), + ); } -export async function getWranglerChangelog(): Promise< - CollectionEntry<"changelogs"> -> { +// Stored as a const so it is only run once. +const wranglerChangelogs = await getWranglerChangelog(); + +async function getWranglerChangelog(): Promise> { const response = await fetch( "https://api.github.com/repos/cloudflare/workers-sdk/releases?per_page=100", ); @@ -88,14 +118,14 @@ export async function getWranglerChangelog(): Promise< collection: "changelogs", data: { link: "/workers/platform/changelog/wrangler/", - productName: "wrangler", + productName: "Wrangler", productLink: "/workers/wrangler/", productArea: "Developer platform", productAreaLink: "/workers/platform/changelog/platform/", entries: releases.map((release) => { return { publish_date: release.published_at.toISOString().substring(0, 10), - title: release.name.split("@")[1], + title: `Wrangler - ${release.name.split("@")[1]}`, link: `https://github.com/cloudflare/workers-sdk/releases/tag/wrangler%40${release.name.split("@")[1]}`, description: release.body, }; From 3849f1c1e50a74133dc29faad453306e148ee2fc Mon Sep 17 00:00:00 2001 From: Kian Newman-Hazel Date: Fri, 31 Jan 2025 21:55:46 +0000 Subject: [PATCH 2/2] fix eslint errors --- src/pages/changelog/index.astro | 1 - src/pages/changelog/index.xml.ts | 2 +- src/util/changelogs.ts | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pages/changelog/index.astro b/src/pages/changelog/index.astro index 64d1b1c0f26751..e828ad6f654970 100644 --- a/src/pages/changelog/index.astro +++ b/src/pages/changelog/index.astro @@ -1,7 +1,6 @@ --- import type { StarlightPageProps } from "@astrojs/starlight/props"; import StarlightPage from "@astrojs/starlight/components/StarlightPage.astro"; -import { Aside } from "@astrojs/starlight/components"; import { getChangelogs } from "~/util/changelogs"; import Changelogs from "~/components/Changelogs.tsx"; diff --git a/src/pages/changelog/index.xml.ts b/src/pages/changelog/index.xml.ts index d3dfcb463cf579..a4c263ffe35e4f 100644 --- a/src/pages/changelog/index.xml.ts +++ b/src/pages/changelog/index.xml.ts @@ -19,7 +19,7 @@ export const GET: APIRoute = async (context) => { trailingSlash: false, items: entries.map((entry) => { return { - title: entry.title, + title: `${entry.product.name} - ${entry.title}`, description: entry.content, pubDate: new Date(entry.date), link: entry.link, diff --git a/src/util/changelogs.ts b/src/util/changelogs.ts index 3e9ea8442f1954..6dab9fe5173eae 100644 --- a/src/util/changelogs.ts +++ b/src/util/changelogs.ts @@ -2,7 +2,7 @@ import { z } from "astro:schema"; import { getCollection, getEntry } from "astro:content"; import { type CollectionEntry } from "astro:content"; import { entryToString } from "./container"; -import { Marked, marked } from "marked"; +import { Marked } from "marked"; import { baseUrl } from "marked-base-url"; export type Changelogs = Awaited>;