archives.ts

3.1 KB · raw

1import type { Hono } from "hono";2import { fileAt, headId, history, type Save, saveByPrefix } from "../mirror/queries.ts";3import { type Html, html } from "./html.ts";4import {5	cacheFor,6	notFound,7	openPublicRepo,8	type PageContext,9	type PublicRepo,10	page,11	relativeTime,12} from "./layout.ts";1314const RECENT = 20;1516export function registerArchivesPage(app: Hono, ctx: PageContext): void {17	app.get("/:repo/archives", (c) => {18		const repo = openPublicRepo(ctx, c.req.param("repo"));19		if (!repo) return notFound(c, ctx);20		const name = repo.settings.name;21		const id = headId(repo.mirror);22		const head = id ? saveByPrefix(repo.mirror, id) : null;23		const recent = history(repo.mirror, RECENT + 1, 0).filter((save) => save.id !== head?.id);24		cacheFor(c, false);25		return c.html(26			page(ctx, {27				title: `archives · ${name}`,28				description: `Source tarballs of ${name}, one per save.`,29				path: `/${name}/archives`,30				body: html`<p class="breadcrumb mono"><a href="/${name}">${name}</a></p>31<h1 class="file-page-title">archives</h1>32<section class="repo-section">33${34	head35		? [36				row(ctx, repo, head, true),37				...recent.slice(0, RECENT).map((save) => row(ctx, repo, save, false)),38			]39		: html`<p class="muted mono save-empty">no saves yet</p>`40}41</section>`,42			}),43		);44	});45}4647// HEAD's row names HEAD.tar.gz, which always follows the newest save; the others pin their save.48// The `nix profile add` line shows only where the save has a flake to install, as in Kepr 2.49function row(ctx: PageContext, repo: PublicRepo, save: Save, isHead: boolean): Html {50	const name = repo.settings.name;51	const file = isHead ? "HEAD" : save.id;52	const tarball = `/${name}/archive/${file}.tar.gz`;53	const nix = `nix profile add https://${ctx.domain}${tarball}`;54	const curl = `curl -fL https://${ctx.domain}${tarball} | tar xz`;55	const koh = `koh steal ${ctx.domain}/${name}`;56	const hasFlake = fileAt(repo.mirror, save.id, "flake.nix") !== null;57	// HEAD can be a checkpoint, which has no page of its own (§21.4).58	const id = save.is_checkpoint59		? html`<span class="history-id">${save.id}</span>`60		: html`<a class="history-id" href="/${name}/saves/${save.id}">${save.id}</a>`;61	return html`<div class="archive-row${isHead ? " archive-row--head" : ""}">62<p class="archive-save">${63		isHead && html`<span class="archive-head mono">HEAD</span>`64	}${id}${html`<span class="history-msg archive-msg">${save.message.split("\n")[0]}</span>`}${html`<span class="history-when">${relativeTime(save.created_at)}</span>`}</p>65<p class="archive-links mono"><a href="${66		tarball67	}">↓ ${name}-${isHead ? "HEAD" : save.id.slice(0, 8)}.tar.gz</a>68<button type="button" class="copy-btn" data-copy="${save.id}"69aria-label="copy save ${save.id}" hidden>hash</button>70<button type="button" class="copy-btn" data-copy="${koh}"71aria-label="copy Koh steal command" hidden>koh</button>72<button type="button" class="copy-btn" data-copy="${curl}"73aria-label="copy archive download command" hidden>curl</button></p>74${75	hasFlake &&76	html`<div class="install-line"><code>${nix}</code><button type="button" class="copy-btn" data-copy="${nix}" hidden>copy</button></div>`77}78</div>`;79}