feat: add windows desktop release support
This commit is contained in:
@@ -1,87 +1,118 @@
|
||||
import {
|
||||
buildStableDownloadRedirectPage,
|
||||
extractStableDmgUrl,
|
||||
extractStableDmgUrlFromReleases,
|
||||
resolveStableDmgUrl,
|
||||
extractStableDownloadTargets,
|
||||
extractStableDownloadTargetsFromReleases,
|
||||
resolveStableDownloadTargets,
|
||||
} from './releaseDownloadPage'
|
||||
|
||||
describe('extractStableDmgUrl', () => {
|
||||
it('returns the stable dmg url when present', () => {
|
||||
describe('extractStableDownloadTargets', () => {
|
||||
it('returns stable downloads for each supported desktop platform when present', () => {
|
||||
expect(
|
||||
extractStableDmgUrl({
|
||||
extractStableDownloadTargets({
|
||||
platforms: {
|
||||
'darwin-aarch64': {
|
||||
dmg_url: 'https://example.com/Tolaria.dmg',
|
||||
download_url: 'https://example.com/Tolaria.dmg',
|
||||
},
|
||||
'linux-x86_64': {
|
||||
download_url: 'https://example.com/Tolaria.AppImage',
|
||||
},
|
||||
'windows-x86_64': {
|
||||
url: 'https://example.com/Tolaria-setup.exe',
|
||||
},
|
||||
},
|
||||
}),
|
||||
).toBe('https://example.com/Tolaria.dmg')
|
||||
})
|
||||
|
||||
it('returns null when the stable dmg url is missing', () => {
|
||||
expect(
|
||||
extractStableDmgUrl({
|
||||
platforms: {
|
||||
'darwin-aarch64': {
|
||||
url: 'https://example.com/Tolaria.app.tar.gz',
|
||||
},
|
||||
},
|
||||
}),
|
||||
).toBeNull()
|
||||
).toMatchObject({
|
||||
'darwin-aarch64': {
|
||||
label: 'macOS',
|
||||
url: 'https://example.com/Tolaria.dmg',
|
||||
},
|
||||
'linux-x86_64': {
|
||||
label: 'Linux',
|
||||
url: 'https://example.com/Tolaria.AppImage',
|
||||
},
|
||||
'windows-x86_64': {
|
||||
label: 'Windows',
|
||||
url: 'https://example.com/Tolaria-setup.exe',
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('buildStableDownloadRedirectPage', () => {
|
||||
it('builds a redirect page when a stable dmg exists', () => {
|
||||
const html = buildStableDownloadRedirectPage('https://example.com/Tolaria.dmg')
|
||||
it('builds a redirect page with platform-specific download links', () => {
|
||||
const html = buildStableDownloadRedirectPage({
|
||||
'darwin-aarch64': {
|
||||
buttonLabel: 'Download Tolaria for macOS',
|
||||
label: 'macOS',
|
||||
url: 'https://example.com/Tolaria.dmg',
|
||||
},
|
||||
'windows-x86_64': {
|
||||
buttonLabel: 'Download Tolaria for Windows',
|
||||
label: 'Windows',
|
||||
url: 'https://example.com/Tolaria-setup.exe',
|
||||
},
|
||||
})
|
||||
|
||||
expect(html).toContain('Tolaria Stable Download')
|
||||
expect(html).toContain('window.location.replace("https://example.com/Tolaria.dmg");')
|
||||
expect(html).toContain('Download latest stable DMG')
|
||||
expect(html).toContain('meta http-equiv="refresh"')
|
||||
expect(html).toContain('DOWNLOAD_TARGETS')
|
||||
expect(html).toContain('Download Tolaria for Windows')
|
||||
expect(html).toContain('Download Tolaria for macOS')
|
||||
expect(html).toContain('window.location.replace')
|
||||
})
|
||||
|
||||
it('builds a fallback page when no stable dmg exists yet', () => {
|
||||
const html = buildStableDownloadRedirectPage(null)
|
||||
it('builds a fallback page when no stable downloads exist yet', () => {
|
||||
const html = buildStableDownloadRedirectPage({})
|
||||
|
||||
expect(html).toContain('Tolaria Stable Download Unavailable')
|
||||
expect(html).toContain('View release history')
|
||||
expect(html).toContain('https://refactoringhq.github.io/tolaria/')
|
||||
expect(html).not.toContain('window.location.replace(')
|
||||
expect(html).not.toContain('DOWNLOAD_TARGETS')
|
||||
})
|
||||
})
|
||||
|
||||
describe('resolveStableDmgUrl', () => {
|
||||
it('falls back to the latest stable github release dmg when latest.json has no dmg url', () => {
|
||||
describe('resolveStableDownloadTargets', () => {
|
||||
it('falls back to stable release assets when latest.json is incomplete', () => {
|
||||
const latestPayload = {
|
||||
platforms: {
|
||||
'darwin-aarch64': {
|
||||
url: 'https://example.com/Tolaria.app.tar.gz',
|
||||
download_url: 'https://example.com/Tolaria.dmg',
|
||||
},
|
||||
},
|
||||
}
|
||||
const releasesPayload = [
|
||||
{
|
||||
prerelease: true,
|
||||
assets: [
|
||||
{
|
||||
name: 'Tolaria-alpha.dmg',
|
||||
browser_download_url: 'https://example.com/alpha.dmg',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
prerelease: false,
|
||||
assets: [
|
||||
{
|
||||
name: 'Tolaria.dmg',
|
||||
browser_download_url: 'https://example.com/stable.dmg',
|
||||
name: 'Tolaria-setup.exe',
|
||||
browser_download_url: 'https://example.com/Tolaria-setup.exe',
|
||||
},
|
||||
{
|
||||
name: 'Tolaria.AppImage',
|
||||
browser_download_url: 'https://example.com/Tolaria.AppImage',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
expect(extractStableDmgUrlFromReleases(releasesPayload)).toBe('https://example.com/stable.dmg')
|
||||
expect(resolveStableDmgUrl(latestPayload, releasesPayload)).toBe('https://example.com/stable.dmg')
|
||||
expect(extractStableDownloadTargetsFromReleases(releasesPayload)).toMatchObject({
|
||||
'linux-x86_64': {
|
||||
url: 'https://example.com/Tolaria.AppImage',
|
||||
},
|
||||
'windows-x86_64': {
|
||||
url: 'https://example.com/Tolaria-setup.exe',
|
||||
},
|
||||
})
|
||||
expect(resolveStableDownloadTargets(latestPayload, releasesPayload)).toMatchObject({
|
||||
'darwin-aarch64': {
|
||||
url: 'https://example.com/Tolaria.dmg',
|
||||
},
|
||||
'linux-x86_64': {
|
||||
url: 'https://example.com/Tolaria.AppImage',
|
||||
},
|
||||
'windows-x86_64': {
|
||||
url: 'https://example.com/Tolaria-setup.exe',
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
const RELEASE_HISTORY_URL = 'https://refactoringhq.github.io/tolaria/'
|
||||
|
||||
type StablePlatformKey = 'darwin-aarch64' | 'linux-x86_64' | 'windows-x86_64'
|
||||
|
||||
type PlatformPayload = {
|
||||
dmg_url?: unknown
|
||||
download_url?: unknown
|
||||
installer_url?: unknown
|
||||
url?: unknown
|
||||
}
|
||||
|
||||
type LatestReleasePayload = {
|
||||
@@ -19,15 +24,42 @@ type GitHubReleasePayload = {
|
||||
prerelease?: unknown
|
||||
}
|
||||
|
||||
type DownloadPageContent = {
|
||||
export type StableDownloadTarget = {
|
||||
buttonLabel: string
|
||||
destinationUrl: string
|
||||
label: string
|
||||
url: string
|
||||
}
|
||||
|
||||
export type StableDownloadTargets = Partial<Record<StablePlatformKey, StableDownloadTarget>>
|
||||
|
||||
type DownloadPageContent = {
|
||||
helperText: string
|
||||
message: string
|
||||
shouldRedirect: boolean
|
||||
title: string
|
||||
}
|
||||
|
||||
const PLATFORM_METADATA: Record<StablePlatformKey, { buttonLabel: string; label: string }> = {
|
||||
'darwin-aarch64': {
|
||||
buttonLabel: 'Download Tolaria for macOS',
|
||||
label: 'macOS',
|
||||
},
|
||||
'linux-x86_64': {
|
||||
buttonLabel: 'Download Tolaria for Linux',
|
||||
label: 'Linux',
|
||||
},
|
||||
'windows-x86_64': {
|
||||
buttonLabel: 'Download Tolaria for Windows',
|
||||
label: 'Windows',
|
||||
},
|
||||
}
|
||||
|
||||
const PLATFORM_ORDER: StablePlatformKey[] = [
|
||||
'darwin-aarch64',
|
||||
'windows-x86_64',
|
||||
'linux-x86_64',
|
||||
]
|
||||
|
||||
const REDIRECT_PAGE_STYLES = `
|
||||
:root {
|
||||
color-scheme: light;
|
||||
@@ -49,7 +81,7 @@ const REDIRECT_PAGE_STYLES = `
|
||||
}
|
||||
|
||||
main {
|
||||
width: min(100%, 460px);
|
||||
width: min(100%, 520px);
|
||||
background: #ffffff;
|
||||
border: 1px solid #e9e9e7;
|
||||
border-radius: 16px;
|
||||
@@ -68,6 +100,13 @@ const REDIRECT_PAGE_STYLES = `
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.button-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
a {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -81,10 +120,20 @@ const REDIRECT_PAGE_STYLES = `
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
a[data-secondary="true"] {
|
||||
background: #eef2ff;
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
a:hover,
|
||||
a:focus-visible {
|
||||
background: #1248cc;
|
||||
}
|
||||
|
||||
a[data-secondary="true"]:hover,
|
||||
a[data-secondary="true"]:focus-visible {
|
||||
background: #dbe4ff;
|
||||
}
|
||||
`
|
||||
|
||||
function escapeHtml(value: string): string {
|
||||
@@ -95,115 +144,281 @@ function escapeHtml(value: string): string {
|
||||
.replaceAll('"', '"')
|
||||
}
|
||||
|
||||
export function extractStableDmgUrl(payload: unknown): string | null {
|
||||
function normalizeUrl(value: unknown): string | null {
|
||||
if (typeof value !== 'string') return null
|
||||
|
||||
const trimmed = value.trim()
|
||||
return trimmed.length > 0 ? trimmed : null
|
||||
}
|
||||
|
||||
function buildStableDownloadTarget(
|
||||
platform: StablePlatformKey,
|
||||
url: string,
|
||||
): StableDownloadTarget {
|
||||
return {
|
||||
...PLATFORM_METADATA[platform],
|
||||
url,
|
||||
}
|
||||
}
|
||||
|
||||
function extractPlatformDownloadUrl(
|
||||
platform: StablePlatformKey,
|
||||
payload: PlatformPayload | undefined,
|
||||
): string | null {
|
||||
if (!payload || typeof payload !== 'object') return null
|
||||
|
||||
switch (platform) {
|
||||
case 'darwin-aarch64':
|
||||
return (
|
||||
normalizeUrl(payload.download_url)
|
||||
?? normalizeUrl(payload.dmg_url)
|
||||
?? normalizeUrl(payload.url)
|
||||
)
|
||||
case 'windows-x86_64':
|
||||
return (
|
||||
normalizeUrl(payload.download_url)
|
||||
?? normalizeUrl(payload.installer_url)
|
||||
?? normalizeUrl(payload.url)
|
||||
)
|
||||
case 'linux-x86_64':
|
||||
return normalizeUrl(payload.download_url) ?? normalizeUrl(payload.url)
|
||||
}
|
||||
}
|
||||
|
||||
export function extractStableDownloadTargets(payload: unknown): StableDownloadTargets {
|
||||
if (!payload || typeof payload !== 'object') return {}
|
||||
|
||||
const { platforms } = payload as LatestReleasePayload
|
||||
if (!platforms || typeof platforms !== 'object') return null
|
||||
if (!platforms || typeof platforms !== 'object') return {}
|
||||
|
||||
const dmgUrl = platforms['darwin-aarch64']?.dmg_url
|
||||
if (typeof dmgUrl !== 'string') return null
|
||||
const downloads: StableDownloadTargets = {}
|
||||
for (const platform of PLATFORM_ORDER) {
|
||||
const url = extractPlatformDownloadUrl(platform, platforms[platform])
|
||||
if (url) downloads[platform] = buildStableDownloadTarget(platform, url)
|
||||
}
|
||||
|
||||
const trimmedUrl = dmgUrl.trim()
|
||||
return trimmedUrl.length > 0 ? trimmedUrl : null
|
||||
return downloads
|
||||
}
|
||||
|
||||
function isPublicStableRelease(release: GitHubReleasePayload): boolean {
|
||||
return release.draft !== true && release.prerelease !== true
|
||||
}
|
||||
|
||||
function extractAssetDmgUrl(asset: ReleaseAssetPayload): string | null {
|
||||
if (typeof asset.name !== 'string' || !asset.name.endsWith('.dmg')) return null
|
||||
if (typeof asset.browser_download_url !== 'string') return null
|
||||
|
||||
const trimmedUrl = asset.browser_download_url.trim()
|
||||
return trimmedUrl.length > 0 ? trimmedUrl : null
|
||||
}
|
||||
|
||||
function findReleaseDmgUrl(release: GitHubReleasePayload): string | null {
|
||||
if (!isPublicStableRelease(release)) return null
|
||||
if (!Array.isArray(release.assets)) return null
|
||||
|
||||
for (const asset of release.assets) {
|
||||
const dmgUrl = extractAssetDmgUrl(asset)
|
||||
if (dmgUrl !== null) return dmgUrl
|
||||
function classifyReleaseAsset(name: string): {
|
||||
platform: StablePlatformKey
|
||||
preference: number
|
||||
} | null {
|
||||
if (name.endsWith('.dmg')) {
|
||||
return { platform: 'darwin-aarch64', preference: 2 }
|
||||
}
|
||||
if (name.endsWith('.app.tar.gz')) {
|
||||
return { platform: 'darwin-aarch64', preference: 1 }
|
||||
}
|
||||
if (name.endsWith('-setup.exe')) {
|
||||
return { platform: 'windows-x86_64', preference: 2 }
|
||||
}
|
||||
if (name.endsWith('.msi')) {
|
||||
return { platform: 'windows-x86_64', preference: 1 }
|
||||
}
|
||||
if (name.endsWith('.AppImage')) {
|
||||
return { platform: 'linux-x86_64', preference: 2 }
|
||||
}
|
||||
if (name.endsWith('.deb')) {
|
||||
return { platform: 'linux-x86_64', preference: 1 }
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export function extractStableDmgUrlFromReleases(payload: unknown): string | null {
|
||||
if (!Array.isArray(payload)) return null
|
||||
type ReleaseAssetSelectionState = {
|
||||
downloads: StableDownloadTargets
|
||||
preferences: Partial<Record<StablePlatformKey, number>>
|
||||
}
|
||||
|
||||
type ReleaseAssetSelection = {
|
||||
platform: StablePlatformKey
|
||||
preference: number
|
||||
url: string
|
||||
}
|
||||
|
||||
function updateDownloadPreference(
|
||||
state: ReleaseAssetSelectionState,
|
||||
selection: ReleaseAssetSelection,
|
||||
) {
|
||||
const currentPreference = state.preferences[selection.platform] ?? Number.NEGATIVE_INFINITY
|
||||
if (selection.preference < currentPreference) return
|
||||
|
||||
state.preferences[selection.platform] = selection.preference
|
||||
state.downloads[selection.platform] = buildStableDownloadTarget(selection.platform, selection.url)
|
||||
}
|
||||
|
||||
function selectReleaseAsset(asset: ReleaseAssetPayload): ReleaseAssetSelection | null {
|
||||
const name = typeof asset.name === 'string' ? asset.name.trim() : ''
|
||||
const url = normalizeUrl(asset.browser_download_url)
|
||||
const classification = classifyReleaseAsset(name)
|
||||
if (!classification || !url) return null
|
||||
|
||||
return {
|
||||
...classification,
|
||||
url,
|
||||
}
|
||||
}
|
||||
|
||||
function extractStableDownloadTargetsFromAssets(
|
||||
assets: ReleaseAssetPayload[],
|
||||
): StableDownloadTargets {
|
||||
const state: ReleaseAssetSelectionState = {
|
||||
downloads: {},
|
||||
preferences: {},
|
||||
}
|
||||
|
||||
for (const asset of assets) {
|
||||
const selection = selectReleaseAsset(asset)
|
||||
if (!selection) continue
|
||||
updateDownloadPreference(state, selection)
|
||||
}
|
||||
|
||||
return state.downloads
|
||||
}
|
||||
|
||||
function findPublicStableRelease(
|
||||
payload: unknown[],
|
||||
): GitHubReleasePayload | null {
|
||||
for (const release of payload) {
|
||||
if (!release || typeof release !== 'object') continue
|
||||
|
||||
const dmgUrl = findReleaseDmgUrl(release as GitHubReleasePayload)
|
||||
if (dmgUrl !== null) return dmgUrl
|
||||
const typedRelease = release as GitHubReleasePayload
|
||||
if (!isPublicStableRelease(typedRelease) || !Array.isArray(typedRelease.assets)) continue
|
||||
return typedRelease
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export function resolveStableDmgUrl(
|
||||
latestPayload: unknown,
|
||||
releasesPayload: unknown,
|
||||
): string | null {
|
||||
return extractStableDmgUrl(latestPayload) ?? extractStableDmgUrlFromReleases(releasesPayload)
|
||||
export function extractStableDownloadTargetsFromReleases(
|
||||
payload: unknown,
|
||||
): StableDownloadTargets {
|
||||
if (!Array.isArray(payload)) return {}
|
||||
|
||||
const stableRelease = findPublicStableRelease(payload)
|
||||
return stableRelease && Array.isArray(stableRelease.assets)
|
||||
? extractStableDownloadTargetsFromAssets(stableRelease.assets)
|
||||
: {}
|
||||
}
|
||||
|
||||
function buildStableDownloadPageContent(dmgUrl: string | null): DownloadPageContent {
|
||||
if (dmgUrl !== null) {
|
||||
export function resolveStableDownloadTargets(
|
||||
latestPayload: unknown,
|
||||
releasesPayload: unknown,
|
||||
): StableDownloadTargets {
|
||||
return {
|
||||
...extractStableDownloadTargetsFromReleases(releasesPayload),
|
||||
...extractStableDownloadTargets(latestPayload),
|
||||
}
|
||||
}
|
||||
|
||||
function buildStableDownloadPageContent(
|
||||
downloads: StableDownloadTargets,
|
||||
): DownloadPageContent {
|
||||
if (Object.keys(downloads).length > 0) {
|
||||
return {
|
||||
buttonLabel: 'Download latest stable DMG',
|
||||
destinationUrl: dmgUrl,
|
||||
helperText: 'If the download does not start automatically, use the button below.',
|
||||
message: 'Redirecting to the latest stable Tolaria DMG.',
|
||||
helperText: 'If the download does not start automatically, use one of the platform links below.',
|
||||
message: 'Preparing the latest stable Tolaria download for your platform.',
|
||||
shouldRedirect: true,
|
||||
title: 'Tolaria Stable Download',
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
buttonLabel: 'View release history',
|
||||
destinationUrl: RELEASE_HISTORY_URL,
|
||||
helperText: 'Use the button below to check the latest release history.',
|
||||
message: 'No stable Tolaria DMG is available yet.',
|
||||
message: 'No stable Tolaria downloads are available yet.',
|
||||
shouldRedirect: false,
|
||||
title: 'Tolaria Stable Download Unavailable',
|
||||
}
|
||||
}
|
||||
|
||||
function buildRedirectMarkup(destinationUrl: string, shouldRedirect: boolean): string {
|
||||
if (!shouldRedirect) return ''
|
||||
function buildDownloadsMarkup(downloads: StableDownloadTargets): string {
|
||||
const targets = PLATFORM_ORDER
|
||||
.map((platform) => downloads[platform])
|
||||
.filter((target): target is StableDownloadTarget => Boolean(target))
|
||||
|
||||
if (targets.length === 0) {
|
||||
return `<div class="button-list"><a id="download-link" href="${RELEASE_HISTORY_URL}" data-secondary="true">View release history</a></div>`
|
||||
}
|
||||
|
||||
const primaryTarget = targets[0]
|
||||
const secondaryLinks = targets
|
||||
.map((target) => (
|
||||
`<a href="${escapeHtml(target.url)}" data-secondary="true">${escapeHtml(target.label)}</a>`
|
||||
))
|
||||
.join('')
|
||||
|
||||
return `
|
||||
<div class="button-list">
|
||||
<a id="download-link" href="${escapeHtml(primaryTarget.url)}">${escapeHtml(primaryTarget.buttonLabel)}</a>
|
||||
</div>
|
||||
<div class="button-list">${secondaryLinks}</div>
|
||||
<div class="button-list">
|
||||
<a href="${RELEASE_HISTORY_URL}" data-secondary="true">View release history</a>
|
||||
</div>`
|
||||
}
|
||||
|
||||
function buildRedirectMarkup(downloads: StableDownloadTargets): string {
|
||||
if (Object.keys(downloads).length === 0) return ''
|
||||
|
||||
const serializedTargets = JSON.stringify(downloads)
|
||||
|
||||
const escapedDestinationUrl = escapeHtml(destinationUrl)
|
||||
return `
|
||||
<meta http-equiv="refresh" content="0; url=${escapedDestinationUrl}">
|
||||
<script>
|
||||
window.location.replace(${JSON.stringify(destinationUrl)});
|
||||
const DOWNLOAD_TARGETS = ${serializedTargets};
|
||||
const PLATFORM_ORDER = ${JSON.stringify(PLATFORM_ORDER)};
|
||||
|
||||
function detectPlatform(userAgent) {
|
||||
if (/Windows/i.test(userAgent)) return 'windows-x86_64';
|
||||
if (/Mac OS X|Macintosh/i.test(userAgent)) return 'darwin-aarch64';
|
||||
if (/Linux/i.test(userAgent) && !/Android/i.test(userAgent)) return 'linux-x86_64';
|
||||
return null;
|
||||
}
|
||||
|
||||
const detectedPlatform = detectPlatform(navigator.userAgent);
|
||||
const resolvedTarget = (
|
||||
detectedPlatform && DOWNLOAD_TARGETS[detectedPlatform]
|
||||
) || DOWNLOAD_TARGETS[PLATFORM_ORDER.find((platform) => DOWNLOAD_TARGETS[platform])] || null;
|
||||
|
||||
if (resolvedTarget) {
|
||||
const link = document.getElementById('download-link');
|
||||
const message = document.getElementById('download-message');
|
||||
if (link) {
|
||||
link.href = resolvedTarget.url;
|
||||
link.textContent = resolvedTarget.buttonLabel;
|
||||
}
|
||||
if (message) {
|
||||
message.textContent = 'Redirecting to the latest stable Tolaria download for ' + resolvedTarget.label + '.';
|
||||
}
|
||||
window.location.replace(resolvedTarget.url);
|
||||
}
|
||||
</script>`
|
||||
}
|
||||
|
||||
export function buildStableDownloadRedirectPage(dmgUrl: string | null): string {
|
||||
const page = buildStableDownloadPageContent(dmgUrl)
|
||||
const escapedDestinationUrl = escapeHtml(page.destinationUrl)
|
||||
const redirectMarkup = buildRedirectMarkup(page.destinationUrl, page.shouldRedirect)
|
||||
export function buildStableDownloadRedirectPage(
|
||||
downloads: StableDownloadTargets,
|
||||
): string {
|
||||
const page = buildStableDownloadPageContent(downloads)
|
||||
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>${page.title}</title>${redirectMarkup}
|
||||
<title>${page.title}</title>${page.shouldRedirect ? buildRedirectMarkup(downloads) : ''}
|
||||
<style>${REDIRECT_PAGE_STYLES}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h1>${page.title}</h1>
|
||||
<p>${page.message}</p>
|
||||
<p id="download-message">${page.message}</p>
|
||||
<p>${page.helperText}</p>
|
||||
<a id="download-link" href="${escapedDestinationUrl}">${page.buttonLabel}</a>
|
||||
${buildDownloadsMarkup(downloads)}
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -21,8 +21,8 @@ describe('buildReleaseHistoryPage', () => {
|
||||
{
|
||||
assets: [
|
||||
{
|
||||
browser_download_url: 'https://example.com/Tolaria.app.tar.gz',
|
||||
name: 'Tolaria.app.tar.gz',
|
||||
browser_download_url: 'https://example.com/Tolaria-setup.exe',
|
||||
name: 'Tolaria-setup.exe',
|
||||
},
|
||||
],
|
||||
body: '**Alpha** notes with [details](https://example.com/details).',
|
||||
@@ -42,7 +42,7 @@ describe('buildReleaseHistoryPage', () => {
|
||||
expect(html).toContain('<h2>Highlights</h2>')
|
||||
expect(html).toContain('<li>Faster startup</li>')
|
||||
expect(html).toContain('<strong>Alpha</strong> notes')
|
||||
expect(html).toContain('Tolaria.app.tar.gz')
|
||||
expect(html).toContain('Tolaria-setup.exe')
|
||||
expect(html).toContain('View on GitHub')
|
||||
})
|
||||
|
||||
|
||||
@@ -430,7 +430,14 @@ function parsePublishedTimestamp(value: unknown): number {
|
||||
}
|
||||
|
||||
function isDownloadableAsset(name: string): boolean {
|
||||
return name.endsWith('.dmg') || name.endsWith('.app.tar.gz') || name.endsWith('.zip')
|
||||
return (
|
||||
name.endsWith('.dmg')
|
||||
|| name.endsWith('.app.tar.gz')
|
||||
|| name.endsWith('-setup.exe')
|
||||
|| name.endsWith('.msi')
|
||||
|| name.endsWith('.AppImage')
|
||||
|| name.endsWith('.deb')
|
||||
)
|
||||
}
|
||||
|
||||
function normalizeDownloads(assets: ReleaseAssetPayload[] | undefined): ReleaseDownload[] {
|
||||
|
||||
Reference in New Issue
Block a user