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}46474849function 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 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}