mirror of
https://github.com/zen-browser/www.git
synced 2025-07-08 01:10:02 +02:00
feat(checksum): add checksum support to download components and update workflow
- Enhanced ButtonCard component to accept and display SHA-256 checksums. - Updated PlatformDownload and release-data to include checksum information for each release. - Modified DownloadScript to apply twilight mode to new checksum buttons. - Adjusted download page to retrieve and display checksums dynamically.
This commit is contained in:
parent
59974742e9
commit
3816206f6b
7 changed files with 234 additions and 76 deletions
2
.github/workflows/prbuildcheck.yml
vendored
2
.github/workflows/prbuildcheck.yml
vendored
|
@ -23,3 +23,5 @@ jobs:
|
|||
|
||||
- name: Build project
|
||||
run: npm run build
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
|
@ -3,44 +3,151 @@ interface Props {
|
|||
label: string
|
||||
href: string
|
||||
variant?: string
|
||||
checksum?: string
|
||||
}
|
||||
|
||||
const { label, href } = Astro.props
|
||||
const { label, href, checksum } = Astro.props
|
||||
---
|
||||
|
||||
<a
|
||||
href={href}
|
||||
class="download-link group relative flex items-center justify-between overflow-hidden rounded-2xl border border-subtle p-4 transition-all duration-200 hover:border-coral hover:shadow-sm data-[twilight='true']:hover:border-zen-blue dark:hover:shadow-md"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<div>
|
||||
<p class="text-lg font-medium">{label}</p>
|
||||
</div>
|
||||
<div class="ml-4 flex flex-col items-end">
|
||||
<div class="flex items-center">
|
||||
<span
|
||||
class="release-type-tag mr-2 rounded-full bg-coral/10 px-2 py-1 text-xs font-medium text-coral transition-colors duration-200 group-hover:bg-coral/20 data-[twilight='true']:bg-zen-blue/10 data-[twilight='true']:text-zen-blue data-[twilight='true']:group-hover:bg-zen-blue/20"
|
||||
>
|
||||
Beta
|
||||
</span>
|
||||
<div
|
||||
class="download-arrow-icon text-muted-foreground rounded-xl border border-subtle p-2 transition-colors duration-200 group-hover:border-coral group-hover:text-coral data-[twilight='true']:group-hover:border-zen-blue data-[twilight='true']:group-hover:text-zen-blue"
|
||||
>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-arrow-up-right"
|
||||
<div class="relative flex flex-col">
|
||||
<a
|
||||
href={href}
|
||||
class="download-link group flex flex-1 items-center justify-between rounded-2xl border border-subtle p-4 transition-all duration-200 hover:border-coral hover:shadow-sm data-[twilight='true']:hover:border-zen-blue dark:hover:shadow-md"
|
||||
rel="noopener noreferrer"
|
||||
tabindex="0"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<p class="text-lg font-medium">{label}</p>
|
||||
{
|
||||
checksum && (
|
||||
<span class="group/checksum relative hidden items-center md:flex">
|
||||
<button
|
||||
type="button"
|
||||
class="checksum-icon-btn text-muted-foreground flex items-center justify-center rounded-full p-1 hover:text-coral focus:outline-none focus:ring-2 focus:ring-coral data-[twilight='true']:hover:text-zen-blue data-[twilight='true']:focus:ring-zen-blue"
|
||||
aria-label="Show SHA-256 checksum"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path d="M12 3l8 4v5c0 5.25-3.5 9.74-8 11-4.5-1.26-8-5.75-8-11V7l8-4z" />
|
||||
<path d="M9 12l2 2 4-4" />
|
||||
</svg>
|
||||
</button>
|
||||
<span class="absolute -top-10 left-1/2 z-50 hidden min-w-[120px] -translate-x-1/2 select-none whitespace-nowrap rounded-md border border-subtle bg-[rgba(255,255,255,0.98)] px-3 py-2 text-xs text-gray-700 shadow group-focus-within/checksum:hidden group-hover/checksum:flex group-focus-within/checksum:group-hover/checksum:hidden dark:bg-[rgba(24,24,27,0.98)] dark:text-gray-100">
|
||||
Show SHA-256
|
||||
</span>
|
||||
<span class="checksum-tooltip popover absolute -left-14 -top-12 z-50 hidden min-w-[220px] items-center gap-2 whitespace-nowrap rounded-md border border-subtle bg-[rgba(255,255,255,0.98)] px-3 py-2 text-xs text-gray-700 shadow group-focus-within/checksum:flex dark:bg-[rgba(24,24,27,0.98)] dark:text-gray-100">
|
||||
<span class="flex-1 truncate font-mono text-xs">{checksum}</span>
|
||||
<button
|
||||
type="button"
|
||||
class="copy-btn rounded bg-coral px-2 py-1 text-xs text-white hover:bg-coral/80 data-[twilight='true']:bg-zen-blue data-[twilight='true']:hover:bg-zen-blue/80"
|
||||
>
|
||||
Copy
|
||||
</button>
|
||||
</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div class="flex flex-col items-end gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<span
|
||||
class="release-type-tag rounded-full bg-coral/10 px-2 py-1 text-xs font-medium text-coral transition-colors duration-200 group-hover:bg-coral/20 data-[twilight='true']:bg-zen-blue/10 data-[twilight='true']:text-zen-blue data-[twilight='true']:group-hover:bg-zen-blue/20"
|
||||
>
|
||||
<path d="M7 17 17 7"></path>
|
||||
<path d="M7 7h10v10"></path>
|
||||
</svg>
|
||||
Beta
|
||||
</span>
|
||||
<div
|
||||
class="download-arrow-icon text-muted-foreground rounded-xl border border-subtle p-2 transition-colors duration-200 group-hover:border-coral group-hover:text-coral data-[twilight='true']:group-hover:border-zen-blue data-[twilight='true']:group-hover:text-zen-blue"
|
||||
>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-arrow-up-right"
|
||||
>
|
||||
<path d="M7 17 17 7"></path>
|
||||
<path d="M7 7h10v10"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.checksum-expand {
|
||||
transition: all 0.2s;
|
||||
z-index: 100;
|
||||
}
|
||||
.checksum-icon-btn {
|
||||
transition:
|
||||
color 0.15s,
|
||||
background 0.15s;
|
||||
}
|
||||
.checksum-tooltip {
|
||||
transition: opacity 0.15s;
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const checksumButtons = document.querySelectorAll(
|
||||
'.checksum-icon-btn',
|
||||
) as NodeListOf<HTMLButtonElement>
|
||||
const checksumTooltips = document.querySelectorAll(
|
||||
'.checksum-tooltip',
|
||||
) as NodeListOf<HTMLDivElement>
|
||||
const copyButtons = document.querySelectorAll(
|
||||
'.copy-btn',
|
||||
) as NodeListOf<HTMLButtonElement>
|
||||
|
||||
function stopEvent(e: Event) {
|
||||
e.preventDefault?.()
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
function copyChecksum(e: Event, checksum: string) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
navigator.clipboard.writeText(checksum)
|
||||
const btn = e.currentTarget as HTMLButtonElement
|
||||
const original = btn.innerText
|
||||
btn.innerText = 'Copied!'
|
||||
setTimeout(() => (btn.innerText = original), 1200)
|
||||
}
|
||||
|
||||
// Attach listeners after DOM is ready
|
||||
checksumButtons.forEach((btn) => {
|
||||
btn.addEventListener('click', stopEvent)
|
||||
})
|
||||
checksumTooltips.forEach((tooltip) => {
|
||||
tooltip.addEventListener('mousedown', stopEvent)
|
||||
tooltip.addEventListener('click', stopEvent)
|
||||
})
|
||||
copyButtons.forEach((btn) => {
|
||||
btn.addEventListener('click', (e) =>
|
||||
copyChecksum(
|
||||
e,
|
||||
(
|
||||
btn
|
||||
.closest('.checksum-tooltip')
|
||||
?.querySelector('.font-mono') as HTMLSpanElement
|
||||
)?.innerText,
|
||||
),
|
||||
)
|
||||
btn.addEventListener('mousedown', stopEvent)
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -76,7 +76,7 @@
|
|||
|
||||
// Apply twilight mode to all relevant elements
|
||||
const coralElements = document.querySelectorAll(
|
||||
'.download-browser-logo, .release-type-tag, .decorative-gradient, .download-link, .download-arrow-icon, .download-card__icon',
|
||||
'.download-browser-logo, .release-type-tag, .decorative-gradient, .download-link, .download-arrow-icon, .download-card__icon, .checksum-icon-btn, .copy-btn',
|
||||
)
|
||||
coralElements.forEach((element) => {
|
||||
element.setAttribute('data-twilight', 'true')
|
||||
|
|
|
@ -49,11 +49,13 @@ import DownloadCard from './ButtonCard.astro'
|
|||
label="x86_64"
|
||||
href={releases.x86_64.tarball.link}
|
||||
variant="x86_64"
|
||||
checksum={releases.x86_64.tarball.checksum}
|
||||
/>
|
||||
<DownloadCard
|
||||
label="ARM64"
|
||||
href={releases.aarch64.tarball.link}
|
||||
variant="aarch64"
|
||||
checksum={releases.aarch64.tarball.checksum}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -65,6 +67,7 @@ import DownloadCard from './ButtonCard.astro'
|
|||
label={releaseNote.label}
|
||||
href={releaseNote.link}
|
||||
variant={variant}
|
||||
checksum={releaseNote.checksum}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
@ -1,50 +1,61 @@
|
|||
---
|
||||
import { releaseNotes, releaseNotesTwilight } from '../../release-notes'
|
||||
|
||||
export const releases = {
|
||||
macos: {
|
||||
universal: {
|
||||
link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen.macos-universal.dmg',
|
||||
label: `Universal`,
|
||||
},
|
||||
},
|
||||
windows: {
|
||||
x86_64: {
|
||||
link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen.installer.exe',
|
||||
label: `64-bit (Recommended)`,
|
||||
},
|
||||
arm64: {
|
||||
link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen.installer-arm64.exe',
|
||||
label: `ARM64`,
|
||||
},
|
||||
},
|
||||
linux: {
|
||||
x86_64: {
|
||||
tarball: {
|
||||
link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen.linux-x86_64.tar.xz',
|
||||
label: `Tarball x86_64`,
|
||||
},
|
||||
appImage: {
|
||||
link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen-x86_64.AppImage',
|
||||
label: `AppImage x86_64`,
|
||||
/**
|
||||
* Returns the releases object, injecting checksums dynamically.
|
||||
* @param checksums Record<string, string> mapping filenames to SHA-256 hashes
|
||||
*/
|
||||
export function getReleasesWithChecksums(checksums: Record<string, string>) {
|
||||
return {
|
||||
macos: {
|
||||
universal: {
|
||||
link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen.macos-universal.dmg',
|
||||
label: `Universal`,
|
||||
checksum: checksums['zen.macos-universal.dmg'],
|
||||
},
|
||||
},
|
||||
aarch64: {
|
||||
tarball: {
|
||||
link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen.linux-aarch64.tar.xz',
|
||||
label: `Tarball aarch64`,
|
||||
windows: {
|
||||
x86_64: {
|
||||
link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen.installer.exe',
|
||||
label: `64-bit (Recommended)`,
|
||||
checksum: checksums['zen.installer.exe'],
|
||||
},
|
||||
appImage: {
|
||||
link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen-aarch64.AppImage',
|
||||
label: `AppImage aarch64`,
|
||||
arm64: {
|
||||
link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen.installer-arm64.exe',
|
||||
label: `ARM64`,
|
||||
checksum: checksums['zen.installer-arm64.exe'],
|
||||
},
|
||||
},
|
||||
flathub: {
|
||||
all: {
|
||||
link: 'https://flathub.org/apps/app.zen_browser.zen',
|
||||
label: `Flathub`,
|
||||
linux: {
|
||||
x86_64: {
|
||||
tarball: {
|
||||
link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen.linux-x86_64.tar.xz',
|
||||
label: `Tarball x86_64`,
|
||||
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`,
|
||||
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`,
|
||||
checksum: checksums['zen.linux-aarch64.tar.xz'],
|
||||
},
|
||||
appImage: {
|
||||
link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen-aarch64.AppImage',
|
||||
label: `AppImage aarch64`,
|
||||
checksum: checksums['zen-aarch64.AppImage'],
|
||||
},
|
||||
},
|
||||
flathub: {
|
||||
all: {
|
||||
link: 'https://flathub.org/apps/app.zen_browser.zen',
|
||||
label: `Flathub`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
---
|
||||
|
|
|
@ -2,9 +2,10 @@
|
|||
import Description from '../components/Description.astro'
|
||||
import Title from '../components/Title.astro'
|
||||
import Layout from '../layouts/Layout.astro'
|
||||
import { releases } from '../components/download/release-data.astro'
|
||||
import { getReleasesWithChecksums } from '../components/download/release-data.astro'
|
||||
import PlatformDownload from '../components/download/PlatformDownload.astro'
|
||||
import DownloadScript from '../components/download/DownloadScript.astro'
|
||||
import { getChecksums } from '../utils/githubChecksums'
|
||||
|
||||
import { library, icon } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
|
@ -20,6 +21,9 @@ 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)
|
||||
---
|
||||
|
||||
<DownloadScript />
|
||||
|
|
31
src/utils/githubChecksums.ts
Normal file
31
src/utils/githubChecksums.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
/**
|
||||
* Fetches the latest release notes from GitHub and parses the SHA-256 checksums.
|
||||
* Returns a mapping from filename to checksum.
|
||||
*/
|
||||
export async function getChecksums() {
|
||||
const token = import.meta.env.GITHUB_TOKEN;
|
||||
if (!token) throw new Error('GITHUB_TOKEN is not set in environment variables');
|
||||
|
||||
const res = await fetch('https://api.github.com/repos/zen-browser/desktop/releases/latest', {
|
||||
headers: {
|
||||
'Accept': 'application/vnd.github+json',
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
'User-Agent': 'zen-browser-checksum-fetcher',
|
||||
},
|
||||
});
|
||||
if (!res.ok) throw new Error('Failed to fetch GitHub release: ' + res.statusText);
|
||||
const data = await res.json();
|
||||
const body = data.body as string;
|
||||
|
||||
// Extract the checksum block
|
||||
const match = body.match(/File Checksums \(SHA-256\)[\s\S]*?```([\s\S]*?)```/);
|
||||
const checksums: Record<string, string> = {};
|
||||
if (match && match[1]) {
|
||||
match[1].split('\n').forEach(line => {
|
||||
const [hash, filename] = line.trim().split(/\s+/, 2);
|
||||
if (hash && filename) checksums[filename] = hash;
|
||||
});
|
||||
}
|
||||
return checksums;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue