Files
paseo/packages/website/src/changelog.ts
T
Mohamed Boudra 1c43e4eea1 feat(website): offer the beta channel on the download page
Beta testers had to find prerelease assets on GitHub by hand. The
download page now carries a Stable/Beta switch, with stable staying the
default and the canonical URL.

The channel lives in the URL (?channel=beta) rather than client state so
maintainers can link someone straight to it. That link outlives the beta
it was shared for, so the switch only renders while the newest
prerelease leads stable on its core version, and a stale ?channel=beta
falls back to stable instead of serving a promoted version twice.

"What's new" deep-links the minor group anchor (/changelog#release-0.3),
not the exact entry: promotion rewrites the -beta.N entry in place, so
that anchor dies while the group survives — and the group is the
stable-to-beta diff a beta downloader came to read. A version missing
from the bundled changelog links plain /changelog rather than a dead
anchor, which covers the window between a tag going public and the site
redeploying.

Homebrew, the app stores, and the web app have no beta, so the Beta view
drops those rows instead of showing inert placeholders.
2026-08-05 16:39:59 +02:00

90 lines
2.6 KiB
TypeScript

import changelogMarkdown from "../../../CHANGELOG.md?raw";
export interface ChangelogRelease {
version: string;
date: string;
markdown: string;
}
export interface ChangelogReleaseGroup {
version: string;
releases: ChangelogRelease[];
}
const releaseHeadingPattern = /^## (.+?) - (\d{4}-\d{2}-\d{2})$/;
export function releaseAnchor(version: string): string {
return `release-${version}`;
}
function minorVersion(version: string): string {
return version.split(".").slice(0, 2).join(".");
}
function parseChangelog(markdown: string): ChangelogRelease[] {
const releases: ChangelogRelease[] = [];
const versions = new Set<string>();
let currentRelease: ChangelogRelease | null = null;
for (const line of markdown.split("\n")) {
const heading = line.match(releaseHeadingPattern);
if (heading) {
const version = heading[1];
if (versions.has(version)) {
throw new Error(`Duplicate changelog version: ${version}`);
}
versions.add(version);
if (currentRelease) releases.push(currentRelease);
currentRelease = {
version,
date: heading[2],
markdown: "",
};
continue;
}
if (currentRelease) currentRelease.markdown += `${line}\n`;
}
if (currentRelease) releases.push(currentRelease);
return releases;
}
function groupChangelogReleases(releases: ChangelogRelease[]): ChangelogReleaseGroup[] {
const groups: ChangelogReleaseGroup[] = [];
for (const release of releases) {
const version = minorVersion(release.version);
const currentGroup = groups.at(-1);
if (currentGroup?.version === version) {
currentGroup.releases.push(release);
} else {
groups.push({ version, releases: [release] });
}
}
return groups;
}
const changelogReleases = parseChangelog(changelogMarkdown);
export const changelogReleaseGroups = groupChangelogReleases(changelogReleases);
/**
* Deep link from a downloadable version to its notes.
*
* Links the minor group rather than the exact entry: a `-beta.N` entry is
* rewritten in place on promotion, so its anchor disappears while the group
* anchor survives — and the group is the stable-to-beta diff a beta downloader
* came to read. Versions the bundled changelog doesn't know about (a tag whose
* changelog commit hasn't been deployed yet) get the plain changelog, never a
* dead anchor.
*/
export function changelogLink(version: string): { to: "/changelog"; hash?: string } {
const known = changelogReleases.some((release) => release.version === version);
return known
? { to: "/changelog", hash: releaseAnchor(minorVersion(version)) }
: { to: "/changelog" };
}