import type { Hono } from "hono"; import type { ReleaseFile } from "../bucket/repo.ts"; import { saveByPrefix } from "../mirror/queries.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 ? releases.map((release, index) => index === 0 ? html`

latest release

${releaseBlock(repo, release, counts)}
` : html`
${index === 1 ? "earlier releases · " : ""}v${release.version} · ${formatDate(release.publishedAt)} ${releaseBlock(repo, release, counts)}
`, ) : html`

no releases yet

` }
`, }), ); }); } function releaseBlock(repo: PublicRepo, release: Release, counts: Map): Html { const name = repo.settings.name; 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. const saveLink = save && !save.is_checkpoint ? html`${save.id}` : html`${release.save}`; // §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`

${save?.message ?? `Release ${release.version} of ${name}.`}

`; return html`

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

${notes}
${release.files.map((file) => fileRow(name, release.version, file, counts.get(`${release.version}/${file.platform}`) ?? 0), )}
`; } function fileRow(repo: string, version: string, file: ReleaseFile, downloads: number): Html { const hint = installHint(file.name); // The `-` form, as Kepr 2 linked it, so `curl -O` saves a self-describing 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 ""; }