b967d82d724e

delete a repo's kept tarballs from the bucket on its admin page (v3.0.146)

time
2026-09-30 13:54 UTC · 1 week ago
machine
Junes-MacBook-Pro.local
agent
claude-opus-5-5
session
admin repo settings
parent
02702253e7ae
3 files package.json+1 −1src/admin/repos.ts+19 −0tests/repoworkspace.test.ts+16 −0
package.json+1 −1
@@ -1,6 +1,6 @@11 {22 	"name": "kepr",3-	"version": "3.0.145",3+	"version": "3.0.146",44 	"private": true,55 	"type": "module",66 	"scripts": {

view file

src/admin/repos.ts+19 −0
@@ -15,6 +15,7 @@1515 	visibleReleases,1616 } from "../releases/releases.ts";1717 import { type RepoText, repoText, setRepoText } from "../server/db.ts";18+import { dropTarballs } from "../tarball/cache.ts";1819 import { type AdminContext, adminPage, formField, notice, textFields } from "./console.ts";1920 import { type AdminRepo, allRepos, findRepo, type RepoEdit, updateRepo } from "./store.ts";2021 import { LIMITS } from "./text.ts";@@ -105,6 +106,18 @@105106 		);106107 	});107108 109+	app.post("/admin/repos/:repo/tarballs/delete", async (c) => {110+		const repo = findRepo(ctx.kepr, c.req.param("repo"));111+		if (!repo) return c.notFound();112+		try {113+			await dropTarballs(ctx.sync.bucket, repo.name);114+		} catch (error) {115+			if (!(error instanceof BucketWriteError)) throw error;116+			return c.html(reposPage(c, ctx, `${repo.name}: ${error.message}`, repo.name), 503);117+		}118+		return c.redirect(`/admin/repos?repo=${repo.name}&saved=1#repo-editor`, 303);119+	});120+108121 	app.post("/admin/releases/:repo/:version/delete", async (c) => {109122 		const found = findRelease(ctx, c.req.param("repo"), c.req.param("version"));110123 		if (!found) return c.notFound();@@ -227,6 +240,12 @@227240 	html`<section class="repo-section" id="builds"><h2 class="section-label mono">builds</h2>228241 ${releases.map((release) => releaseForm(c, repo.name, release))}</section>`229242 }243+<section class="repo-section"><h2 class="section-label mono">archives</h2>244+<form class="issues-form" method="post" action="/admin/repos/${repo.name}/tarballs/delete">245+${formField(c)}246+<p class="issue-meta mono">Tarballs are kept in the bucket once downloaded, and rebuilt on the next download.</p>247+<button class="issues-submit" type="submit">delete kept tarballs</button>248+</form></section>230249 </article>`;231250 }232251 

view file

tests/repoworkspace.test.ts+16 −0
@@ -13,6 +13,7 @@1313 describe("the selected repo's workspace (§24)", () => {1414 	let inst: Instance;1515 	let browser: Browser;16+	let bucketDir: string;1617 	const workspace = async () => (await browser.get("/admin/repos?repo=app")).text();1718 	const save = (fields: Record<string, string>) =>1819 		browser.post("/admin/repos/app", { public: "1", ...fields });@@ -32,6 +33,7 @@3233 				files: [{ platform: "linux-x86_64", name: "app", bytes: new Uint8Array([1]) }],3334 			});3435 		}36+		bucketDir = repos.bucketDir;3537 		inst = await instance(repos.bucketDir);3638 		inst.kepr.run(3739 			`INSERT INTO issues (repo, number, title, body, type, filed_at)@@ -117,6 +119,20 @@117119 			expect(await response.text()).toContain(error);118120 		}119121 		expect(await workspace()).not.toContain('value="Zig');122+	});123+124+	test("deletes the repo's kept tarballs from the bucket", async () => {125+		await save({});126+		const head = (await inst.app.request("/app/archive/HEAD.tar.gz")).headers.get("location");127+		expect((await inst.app.request(head ?? "")).status).toBe(200);128+		const kept = new Bun.Glob("**/*.tar.gz");129+		const keptIn = () => Array.fromAsync(kept.scan(join(bucketDir, ".kepr", "tarballs", "app")));130+		expect(await keptIn()).toHaveLength(1);131+		expect(await workspace()).toContain('action="/admin/repos/app/tarballs/delete"');132+		const deleted = await browser.post("/admin/repos/app/tarballs/delete", {});133+		expect(deleted.headers.get("location")).toBe("/admin/repos?repo=app&saved=1#repo-editor");134+		expect(await keptIn()).toEqual([]);135+		expect((await inst.app.request(head ?? "")).status).toBe(200);120136 	});121137 });122138 

view file