releases.ts

4.0 KB · raw

1import type { Hono } from "hono";2import type { ReleaseFile } from "../bucket/repo.ts";3import { saveByPrefix } from "../mirror/queries.ts";4import { downloadCounts, type Release, visibleReleases } from "../releases/releases.ts";5import { type Html, html } from "./html.ts";6import {7	cacheFor,8	formatDate,9	formatSize,10	notFound,11	openPublicRepo,12	type PageContext,13	type PublicRepo,14	page,15	plural,16} from "./layout.ts";17import { renderMarkdown } from "./markdown.ts";1819// §11.1's labels.20const PLATFORM_LABELS: Record<string, string> = {21	"linux-x86_64": "Linux · x86_64",22	"linux-aarch64": "Linux · ARM64",23	"macos-aarch64": "macOS · Apple Silicon",24	"macos-x86_64": "macOS · Intel",25	"windows-x86_64": "Windows · x86_64",26	"android-arm64": "Android · ARM64",27	"android-x86": "Android · x86_64",28};2930/** §21.9's builds panel: every visible release, newest first. */31export function registerReleasesPage(app: Hono, ctx: PageContext): void {32	app.get("/:repo/releases", (c) => {33		const repo = openPublicRepo(ctx, c.req.param("repo"));34		if (!repo) return notFound(c, ctx);35		const name = repo.settings.name;36		const releases = visibleReleases(ctx.kepr, repo.mirror, name);37		const counts = downloadCounts(ctx.kepr, name);38		cacheFor(c, false);39		return c.html(40			page(ctx, {41				title: `builds · ${name}`,42				description: `Releases of ${name}, with downloads and checksums.`,43				path: `/${name}/releases`,44				body: html`<p class="breadcrumb mono"><a href="/${name}">${name}</a></p>45<h1 class="file-page-title">builds</h1>46<section class="repo-section">47${48	releases.length > 049		? releases.map((release) => releaseBlock(repo, release, counts))50		: html`<p class="muted mono save-empty">no releases yet</p>`51}52</section>`,53			}),54		);55	});56}5758function releaseBlock(repo: PublicRepo, release: Release, counts: Map<string, number>): Html {59	const name = repo.settings.name;60	const save = saveByPrefix(repo.mirror, release.save);61	// A checkpoint has no page of its own (§21.4), and a save the mirror lacks has none either.62	const saveLink =63		save && !save.is_checkpoint64			? html`<a class="history-id" href="/${name}/saves/${save.id}">${save.id}</a>`65			: html`<span class="history-id">${release.save}</span>`;66	// §11.2: empty notes show the release save's message, else "Release <v> of <repo>."67	const notes =68		release.notes !== ""69			? renderMarkdown(release.notes, { repo: name, dir: "", at: null })70			: html`<p>${save?.message ?? `Release ${release.version} of ${name}.`}</p>`;71	return html`<article class="release" id="${release.version}">72<p class="release-head"><span class="release-version">v${73		release.version74	}</span>${saveLink}<span class="history-when">${formatDate(release.publishedAt)}</span></p>75<div class="release-notes markdown">${notes}</div>76<div class="release-files">77${release.files.map((file) =>78	fileRow(name, release.version, file, counts.get(`${release.version}/${file.platform}`) ?? 0),79)}80</div>81</article>`;82}8384function fileRow(repo: string, version: string, file: ReleaseFile, downloads: number): Html {85	const hint = installHint(file.name);86	// The `<repo>-<platform>` form, as Kepr 2 linked it, so `curl -O` saves a self-describing name.87	return html`<div class="release-file">88<p class="release-file-head"><span class="release-platform">${89		PLATFORM_LABELS[file.platform]90	}</span>${html`<a class="release-download mono" href="${`/${repo}/releases/${version}/${repo}-${file.platform}`}">↓ ${file.name}</a>`}${html`<span class="release-meta mono">${formatSize(file.size)} · ${plural(downloads, "download")}</span>`}</p>91<p class="release-sha mono"><span class="muted">sha256</span> ${file.sha256}</p>92${hint && html`<p class="release-hint muted">${hint}</p>`}93</div>`;94}9596// Kepr 2's install hints, with the wording of §21.9.97function installHint(fileName: string): string {98	const lower = fileName.toLowerCase();99	if (lower.endsWith(".app.zip")) return "unzip and drag to Applications";100	if (lower.endsWith(".apk")) {101		return "open on your device to install — allow installs from this source if prompted";102	}103	return "";104}