feat(download): enhance download page with checksum integration and platform-specific releases

- Added a new `release-data.ts` file to manage release information with dynamic checksums.
- Introduced `CHECKSUMS` constant for easy access to file checksums.
- Updated `PlatformDownload.astro` to support additional release types and improved type safety.
- Enhanced the download page to correctly display platform-specific download links and checksums.
- Refactored tests to validate the new download functionality and checksum integration.
This commit is contained in:
taroj1205 2025-05-16 12:28:14 +12:00
parent 466c829a8a
commit 6a7bd311c0
No known key found for this signature in database
GPG key ID: 0FCB6CFFE0981AB7
9 changed files with 231 additions and 103 deletions

View file

@ -13,7 +13,8 @@
"format": "biome format ./src",
"prepare": "husky",
"test": "npx vitest run",
"test:coverage": "npx vitest --coverage"
"test:coverage": "npx vitest --coverage",
"test:playwright": "npx playwright test"
},
"dependencies": {
"@astrojs/check": "^0.9.4",

View file

@ -9,7 +9,16 @@ interface PlatformReleases {
universal?: ReleaseInfo
all?: ReleaseInfo
tarball?: ReleaseInfo
x86_64?: { tarball: ReleaseInfo } | ReleaseInfo
x86_64?:
| {
tarball?: ReleaseInfo
appImage?: ReleaseInfo
}
| ReleaseInfo
aarch64?: {
tarball?: ReleaseInfo
appImage?: ReleaseInfo
}
arm64?: ReleaseInfo
flathub?: { all: ReleaseInfo }
}
@ -27,11 +36,15 @@ import { Image } from 'astro:assets'
import AppIconDark from '../../assets/app-icon-dark.png'
import AppIconLight from '../../assets/app-icon-light.png'
import DownloadCard from './ButtonCard.astro'
function isFlatReleaseInfo(obj: unknown): obj is ReleaseInfo {
return !!obj && typeof obj === 'object' && 'link' in obj
}
---
<div
id={`${platform}-downloads`}
data-active={platform === 'mac'}
data-active={platform === "mac"}
class="platform-section data-[active='false']:hidden"
>
<div class="items-center gap-8 md:flex">
@ -45,9 +58,10 @@ import DownloadCard from './ButtonCard.astro'
<p class="text-muted-foreground mb-6" set:html={description} />
<div class="space-y-6">
{
platform === 'linux' ? (
platform === "linux" ? (
<>
{releases.flathub && releases.flathub.all.label && <div>
{releases.flathub && releases.flathub.all.label && (
<div>
<h4 class="mb-3 text-lg font-medium">Package Managers</h4>
<div class="space-y-3">
<DownloadCard
@ -56,28 +70,96 @@ import DownloadCard from './ButtonCard.astro'
variant="flathub"
/>
</div>
</div>}
{releases.x86_64 && 'tarball' in releases.x86_64 && <div>
<h4 class="mb-3 text-lg font-medium">Tarball</h4>
</div>
)}
{releases.x86_64 &&
typeof releases.x86_64 === "object" &&
"tarball" in releases.x86_64 &&
(releases.x86_64.tarball || releases.x86_64.appImage) && (
<div>
<h4 class="mb-3 text-lg font-medium">x86_64</h4>
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2">
{releases.x86_64.tarball && (
<DownloadCard
label="x86_64"
href={releases.x86_64.tarball.link}
label={
releases.x86_64.tarball.label
? releases.x86_64.tarball.label
: ""
}
href={
releases.x86_64.tarball.link
? releases.x86_64.tarball.link
: ""
}
variant="x86_64"
checksum={releases.x86_64.tarball.checksum}
/>
)}
{releases.x86_64.appImage && (
<DownloadCard
label="ARM64"
href={releases.x86_64.tarball.link}
variant="aarch64"
checksum={releases.x86_64.tarball.checksum}
label={
releases.x86_64.appImage.label
? releases.x86_64.appImage.label
: ""
}
href={
releases.x86_64.appImage.link
? releases.x86_64.appImage.link
: ""
}
variant="x86_64"
checksum={releases.x86_64.appImage.checksum}
/>
)}
</div>
</div>}
</div>
)}
{releases.aarch64 &&
typeof releases.aarch64 === "object" &&
"tarball" in releases.aarch64 &&
(releases.aarch64.tarball || releases.aarch64.appImage) && (
<div>
<h4 class="mb-3 text-lg font-medium">ARM64</h4>
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2">
{releases.aarch64.tarball && (
<DownloadCard
label={
releases.aarch64.tarball.label
? releases.aarch64.tarball.label
: ""
}
href={
releases.aarch64.tarball.link
? releases.aarch64.tarball.link
: ""
}
variant="aarch64"
checksum={releases.aarch64.tarball.checksum}
/>
)}
{releases.aarch64.appImage && (
<DownloadCard
label={
releases.aarch64.appImage.label
? releases.aarch64.appImage.label
: ""
}
href={
releases.aarch64.appImage.link
? releases.aarch64.appImage.link
: ""
}
variant="aarch64"
checksum={releases.aarch64.appImage.checksum}
/>
)}
</div>
</div>
)}
</>
) : (
<div class="space-y-4">
<div class="space-y-3">
<div class="flex flex-col gap-4">
<div class="flex flex-col gap-3">
{releases.universal && releases.universal.label && (
<DownloadCard
label={releases.universal.label}
@ -85,22 +167,14 @@ import DownloadCard from './ButtonCard.astro'
checksum={releases.universal.checksum}
/>
)}
{releases.x86_64 && (
'tarball' in releases.x86_64
? releases.x86_64.tarball.label && (
<DownloadCard
label={releases.x86_64.tarball.label}
href={releases.x86_64.tarball.link}
checksum={releases.x86_64.tarball.checksum}
/>
)
: releases.x86_64.label && (
{releases.x86_64 &&
isFlatReleaseInfo(releases.x86_64) &&
releases.x86_64.label && (
<DownloadCard
label={releases.x86_64.label}
href={releases.x86_64.link}
checksum={releases.x86_64.checksum}
/>
)
)}
{releases.arm64 && releases.arm64.label && (
<DownloadCard

View file

@ -1,4 +1,3 @@
---
/**
* Returns the releases object, injecting checksums dynamically.
* @param checksums Record<string, string> mapping filenames to SHA-256 hashes
@ -28,24 +27,24 @@ export function getReleasesWithChecksums(checksums: Record<string, string>) {
x86_64: {
tarball: {
link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen.linux-x86_64.tar.xz',
label: 'Tarball x86_64',
label: 'Tarball',
checksum: checksums['zen.linux-x86_64.tar.xz'],
},
appImage: {
link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen-x86_64.AppImage',
label: 'AppImage x86_64',
label: 'AppImage',
checksum: checksums['zen-x86_64.AppImage'],
},
},
aarch64: {
tarball: {
link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen.linux-aarch64.tar.xz',
label: 'Tarball aarch64',
label: 'Tarball',
checksum: checksums['zen.linux-aarch64.tar.xz'],
},
appImage: {
link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen-aarch64.AppImage',
label: 'AppImage aarch64',
label: 'AppImage',
checksum: checksums['zen-aarch64.AppImage'],
},
},
@ -58,4 +57,3 @@ export function getReleasesWithChecksums(checksums: Record<string, string>) {
},
}
}
---

View file

@ -0,0 +1,9 @@
export const CHECKSUMS = {
'zen.macos-universal.dmg': 'macsum',
'zen.installer.exe': 'winsum',
'zen.installer-arm64.exe': 'winarmsum',
'zen.linux-x86_64.tar.xz': 'linuxsum',
'zen-x86_64.AppImage': 'linuxappsum',
'zen.linux-aarch64.tar.xz': 'linuxarmsum',
'zen-aarch64.AppImage': 'linuxarmappsum',
}

View file

@ -1,5 +1,7 @@
import { CHECKSUMS } from './checksum'
import { I18N } from './i18n'
export const CONSTANT = {
I18N,
CHECKSUMS,
}

View file

@ -1,36 +1,41 @@
---
import Description from '~/components/Description.astro'
import DownloadScript from '~/components/download/DownloadScript.astro'
import PlatformDownload from '~/components/download/PlatformDownload.astro'
import { getReleasesWithChecksums } from '~/components/download/release-data.astro'
import Layout from '~/layouts/Layout.astro'
import { getChecksums } from '~/utils/githubChecksums'
import { getLocale, getUI } from '~/utils/i18n'
import Description from "~/components/Description.astro";
import DownloadScript from "~/components/download/DownloadScript.astro";
import PlatformDownload from "~/components/download/PlatformDownload.astro";
import { getReleasesWithChecksums } from "~/components/download/release-data";
import Layout from "~/layouts/Layout.astro";
import { getChecksums } from "~/utils/githubChecksums";
import { getLocale, getUI } from "~/utils/i18n";
import { icon, library } from '@fortawesome/fontawesome-svg-core'
import { faApple, faGithub, faLinux, faWindows } from '@fortawesome/free-brands-svg-icons'
import ExternalLinkIcon from '~/icons/ExternalLink.astro'
import LockIcon from '~/icons/LockIcon.astro'
import { icon, library } from "@fortawesome/fontawesome-svg-core";
import {
faApple,
faGithub,
faLinux,
faWindows,
} from "@fortawesome/free-brands-svg-icons";
import ExternalLinkIcon from "~/icons/ExternalLink.astro";
import LockIcon from "~/icons/LockIcon.astro";
export { getStaticPaths } from '~/utils/i18n'
export { getStaticPaths } from "~/utils/i18n";
const locale = getLocale(Astro)
const locale = getLocale(Astro);
const {
routes: { download },
layout,
} = getUI(locale)
} = getUI(locale);
library.add(faWindows, faLinux, faApple, faGithub)
const windowsIcon = icon({ prefix: 'fab', iconName: 'windows' })
const linuxIcon = icon({ prefix: 'fab', iconName: 'linux' })
const appleIcon = icon({ prefix: 'fab', iconName: 'apple' })
const githubIcon = icon({ prefix: 'fab', iconName: 'github' })
library.add(faWindows, faLinux, faApple, faGithub);
const windowsIcon = icon({ prefix: "fab", iconName: "windows" });
const linuxIcon = icon({ prefix: "fab", iconName: "linux" });
const appleIcon = icon({ prefix: "fab", iconName: "apple" });
const githubIcon = icon({ prefix: "fab", iconName: "github" });
const checksums = await getChecksums()
const releases = getReleasesWithChecksums(checksums)
const checksums = await getChecksums();
const releases = getReleasesWithChecksums(checksums);
const platformNames = download.platformNames
const platformDescriptions = download.platformDescriptions
const platformNames = download.platformNames;
const platformDescriptions = download.platformDescriptions;
---
<DownloadScript />

View file

@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'
import { getReleasesWithChecksums } from '~/components/download/release-data.astro'
import { getReleasesWithChecksums } from '~/components/download/release-data'
describe('getReleasesWithChecksums', () => {
it('returns correct structure with checksums', () => {

View file

@ -1,5 +1,7 @@
import { expect, test } from '@playwright/test'
import type { BrowserContextOptions, Page } from '@playwright/test'
import { getReleasesWithChecksums } from '~/components/download/release-data'
import { CONSTANT } from '~/constants'
// Helper to get the platform section by id
const getPlatformSection = (page: Page, platform: string) =>
@ -9,6 +11,10 @@ const getPlatformSection = (page: Page, platform: string) =>
const getPlatformButton = (page: Page, platform: string) =>
page.locator(`button.platform-selector[data-platform='${platform}']`)
// Helper to get the platform download link
const getPlatformDownloadLink = (page: Page, platform: string, label: string) =>
page.locator(`#${platform}-downloads .download-link:has-text('${label}')`)
const platformConfigs: { name: string; userAgent: string; platform: string }[] = [
{
name: 'windows',
@ -29,6 +35,7 @@ const platformConfigs: { name: string; userAgent: string; platform: string }[] =
},
]
test.describe('Download page default tab per platform', () => {
for (const { name, userAgent, platform } of platformConfigs) {
test(`shows correct default tab for ${name} platform`, async ({ browser }) => {
const context = await browser.newContext({
@ -48,6 +55,7 @@ for (const { name, userAgent, platform } of platformConfigs) {
await context.close()
})
}
})
test.describe('Download page platform detection and tab switching', () => {
test('shows correct platform section and tab when switching platforms', async ({ page }) => {
@ -65,3 +73,36 @@ test.describe('Download page platform detection and tab switching', () => {
}
})
})
test.describe('Download page download links', () => {
const releases = getReleasesWithChecksums(CONSTANT.CHECKSUMS)
function getPlatformLinks(releases: ReturnType<typeof getReleasesWithChecksums>) {
return {
mac: [releases.macos.universal],
windows: [releases.windows.x86_64, releases.windows.arm64],
linux: [
releases.linux.x86_64.tarball,
releases.linux.x86_64.appImage,
releases.linux.aarch64.tarball,
releases.linux.aarch64.appImage,
releases.linux.flathub.all,
],
}
}
test('all platform download links are correct', async ({ page }) => {
const platforms = ['windows', 'mac', 'linux']
const platformLinkSelectors = getPlatformLinks(releases)
await page.goto('/download')
await page.waitForLoadState('domcontentloaded')
for (const platform of platforms) {
await getPlatformButton(page, platform).click()
for (const { label, link } of platformLinkSelectors[platform as keyof typeof platformLinkSelectors]) {
const downloadLink = page.locator(`#${platform}-downloads .download-link[href="${link}"]`)
await expect(downloadLink).toContainText(label)
await expect(downloadLink).toHaveAttribute('href', link)
}
}
})
})

View file

@ -1,14 +1,12 @@
import { CONSTANT } from '~/constants'
/**
* Fetches the latest release notes from GitHub and parses the SHA-256 checksums.
* Returns a mapping from filename to checksum.
*/
export async function getChecksums() {
if (import.meta.env.DEV) {
return {
'zen.macos-universal.dmg': 'macsum',
'zen.installer.exe': 'winsum',
'zen.installer-arm64.exe': 'winarmsum',
}
return CONSTANT.CHECKSUMS
}
const res = await fetch('https://api.github.com/repos/zen-browser/desktop/releases/latest', {
headers: {