import type { Hono } from "hono"; import type { ReleaseFile } from "../bucket/repo.ts"; import { saveByPrefix } from "../mirror/queries.ts"; import { downloadName } from "../releases/feeds.ts"; import { downloadCounts, type Release, visibleReleases } from "../releases/releases.ts"; import { type Html, html } from "./html.ts"; import { cacheFor, formatDate, formatSize, notFound, openPublicRepo, type PageContext, type PublicRepo, page, plural, } from "./layout.ts"; import { renderMarkdown } from "./markdown.ts"; // §11.1's labels. const PLATFORM_LABELS: Record = { "linux-x86_64": "Linux · x86_64", "linux-aarch64": "Linux · ARM64", "macos-aarch64": "macOS · Apple Silicon", "macos-x86_64": "macOS · Intel", "windows-x86_64": "Windows · x86_64", "android-arm64": "Android · ARM64", "android-x86": "Android · x86_64", }; /** §21.9's builds panel: every visible release, newest first. */ export function registerReleasesPage(app: Hono, ctx: PageContext): void { app.get("/:repo/releases", (c) => { const repo = openPublicRepo(ctx, c.req.param("repo")); if (!repo) return notFound(c, ctx); const name = repo.settings.name; const releases = visibleReleases(ctx.kepr, repo.mirror, name); const counts = downloadCounts(ctx.kepr, name); cacheFor(c, false); return c.html( page(ctx, { title: `builds · ${name}`, description: `Releases of ${name}, with downloads and checksums.`, path: `/${name}/releases`, body: html`

builds

${ releases.length > 0 ? html`

latest release

${releaseBlock(repo, releases[0] as Release, counts)}
${releases.length > 1 && html`

earlier releases

`} ${releases.slice(1).map((release) => olderRow(repo, release, counts))}` : html`

no releases yet

` }
`, }), ); }); } function releaseBlock(repo: PublicRepo, release: Release, counts: Map): Html { return html`

v${ release.version }${saveLink(repo, release)}${formatDate(release.publishedAt)}

${releaseDetails(repo, release, counts)}
`; } // An older release reads like an archive row: one line, its downloads, the rest folded away. function olderRow(repo: PublicRepo, release: Release, counts: Map): Html { const save = saveByPrefix(repo.mirror, release.save); return html`

v${ release.version }${saveLink(repo, release)}${html`${save?.message.split("\n")[0] ?? ""}`}${html`${formatDate(release.publishedAt)}`}

notes · checksums ${releaseDetails(repo, release, counts)}
`; } function saveLink(repo: PublicRepo, release: Release): Html { const save = saveByPrefix(repo.mirror, release.save); // A checkpoint has no page of its own (§21.4), and a save the mirror lacks has none either. return save && !save.is_checkpoint ? html`${save.id}` : html`${release.save}`; } function releaseDetails(repo: PublicRepo, release: Release, counts: Map): Html { const name = repo.settings.name; // §11.2: empty notes show the release save's message, else "Release of ." const notes = release.notes !== "" ? renderMarkdown(release.notes, { repo: name, dir: "", at: null }) : html`

${saveByPrefix(repo.mirror, release.save)?.message ?? `Release ${release.version} of ${name}.`}

`; return html`
${notes}
${release.files.map((file) => fileRow( downloadHref(name, release, file), file, counts.get(`${release.version}/${file.platform}`) ?? 0, ), )}
`; } function downloadHref(name: string, release: Release, file: ReleaseFile): string { return `/${name}/releases/${release.version}/${encodeURIComponent(downloadName(name, release, file))}`; } function fileRow(href: string, file: ReleaseFile, downloads: number): Html { const hint = installHint(file.name); return html`

${ PLATFORM_LABELS[file.platform] }${html`↓ ${file.name}`}${html`${formatSize(file.size)} · ${plural(downloads, "download")}`}

sha256 ${file.sha256}

${hint && html`

${hint}

`}
`; } // Kepr 2's install hints, with the wording of §21.9. function installHint(fileName: string): string { const lower = fileName.toLowerCase(); if (lower.endsWith(".app.zip")) return "unzip and drag to Applications"; if (lower.endsWith(".apk")) { return "open on your device to install — allow installs from this source if prompted"; } return ""; }