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 && (
-
- )}
- {
}
-
- );
- } else {
- description = marked.parse(entry.description as string);
- return (
- <>
-
-
- {page.data.changelog_product_area_name && (
-
- )}
- {entry.title &&
{entry.title}}
- {
}
-
- >
- );
+ if (entry.title.startsWith(`${entry.product.name} - `)) {
+ shouldRenderTitle = false;
}
+
+ return (
+
+
+ {page.data.changelog_product_area_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..e828ad6f654970 100644
--- a/src/pages/changelog/index.astro
+++ b/src/pages/changelog/index.astro
@@ -1,172 +1,23 @@
---
+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";
-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.
-
-
- Subscribe to all Changelog posts via .
-
-
- 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 (
-
-
- {title && (
-
- {title}
-
- )}
-
-
- );
- })}
-
-
- ))
- }
+
+
-
-
-
-
diff --git a/src/pages/changelog/index.xml.ts b/src/pages/changelog/index.xml.ts
index 978856888cbd1b..a4c263ffe35e4f 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.product.name} - ${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) => (
-
- ))}
-
- ))
- }
-
diff --git a/src/util/changelogs.ts b/src/util/changelogs.ts
index 21cc14bf00c63f..6dab9fe5173eae 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 } 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,
};