feat(server): Routen, Seiten und Artefakte tragen das Repository — /repos/<name>/… (Sitzung D)
Zweite Hälfte von Sitzung D (docs/plan/22-multi-repo.md, PR #13): Der Server bediente bisher genau ein Repository der Registry. Jede Route — API, Seiten, Artefakt-Dateien — arbeitete auf `registry.current()`; ein zweites registriertes Repository wurde gebaut und gepollt, war aber unsichtbar und unerreichbar. Jeder Controller löst sein Repository jetzt je Anfrage auf, statt das bediente als Bohne zu halten; jede Route ist zweimal gemappt. Die unscoped Form ist kein Übergangs-Alias, sondern dauerhaft die Art zu sagen „das bediente Repository" — Lesezeichen und die nach Gitea geposteten Links kennen kein Segment. Entschieden gegen die Repo-Spalte: Die Seiten bleiben je Repository, die Navigation bekommt einen Umschalter. Die Aktionen einer Zeile brauchen das Repository ohnehin, Branches kommen von einem origin und Artefakte aus einem Store — und bei dem einen Repository, das die meisten Installationen haben, wäre eine Spalte nur Rauschen. Das Link-Präfix folgt der ZAHL der bedienten Repositories, nicht dem Weg, über den eine Seite erreicht wurde: mit einem behält die Installation ihre bisherigen URLs (Abnahmekriterium der Sitzung), mit mehreren benennt jeder Link sein Repository. werkator.js liest das Präfix einmal aus einem `werkator-repo-base`-Meta. `BranchPermalinks.permanentUrl` bekommt es ebenfalls — der permanente Schlüssel ist ein Hash des Build-Namens allein, zwei Repositories mit `main` teilten sich sonst eine permanente URL. Fünf neue Tests, Gegenprobe per Mutation gezogen (Präfix fest auf leer → genau der Mehr-Repo-Test fällt). 498 Tests grün, ktlint sauber. PR-Dokument docs/prs/2026-09-03-PR#13-…, Plan, Architektur-Skill und AGENTS.md nachgezogen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
4304dd7c4b
commit
c82f2a965c
@@ -99,6 +99,17 @@ function metaContent(name) {
|
||||
|
||||
const giteaRepoUrl = metaContent("werkator-gitea-repo-url");
|
||||
|
||||
// Empty with one served repository, `/repos/<name>` with several (ADR 0009). Every
|
||||
// path this script builds itself is prefixed with it, so an action triggered on a
|
||||
// repository's page acts on that repository — the paths rendered into the DOM
|
||||
// (`data-api`, artifact links) already carry it.
|
||||
const repoBase = metaContent("werkator-repo-base") || "";
|
||||
|
||||
/** The API of the repository this page belongs to; `/api` when only one is served. */
|
||||
function apiBase() {
|
||||
return repoBase ? "/api" + repoBase : "/api";
|
||||
}
|
||||
|
||||
// The control token is deliberately NOT embedded in the pages: reading them is
|
||||
// unauthenticated, so anyone could have read it out of the HTML. The operator
|
||||
// pastes it once per browser from `.git/werkator/control-token` on the server;
|
||||
@@ -389,7 +400,7 @@ function renderBuildRow(build, allowRestart, restartAtOriginHead) {
|
||||
const inProgress = build.status === "running" || build.status === "pending";
|
||||
if (build.artifactKey) {
|
||||
const artifactLink = elem("a", "artifact-link", inProgress ? "⏳" : "📄");
|
||||
artifactLink.href = "/builds/" + encodeURIComponent(build.artifactKey);
|
||||
artifactLink.href = repoBase + "/builds/" + encodeURIComponent(build.artifactKey);
|
||||
artifactLink.title = inProgress ? "Open build log — no artifacts yet" : "Open artifacts";
|
||||
artifactsCell.appendChild(artifactLink);
|
||||
}
|
||||
@@ -542,7 +553,7 @@ function initCurrentBuilds() {
|
||||
return;
|
||||
}
|
||||
const offset = logOffsets.get(build.artifactKey) || 0;
|
||||
const url = `/api/builds/current/${encodeURIComponent(build.artifactKey)}/log?offset=${offset}`;
|
||||
const url = `${apiBase()}/builds/current/${encodeURIComponent(build.artifactKey)}/log?offset=${offset}`;
|
||||
const tail = await fetchJson(url);
|
||||
logOffsets.set(build.artifactKey, tail.nextOffset);
|
||||
if (tail.content) {
|
||||
@@ -690,11 +701,11 @@ document.addEventListener("click", async (event) => {
|
||||
try {
|
||||
if (action === "restart") {
|
||||
const atOriginHead = button.dataset.atOriginHead === "true" ? "&atOriginHead=true" : "";
|
||||
await sendAction("/api/builds/restart?branch=" + encodeURIComponent(button.dataset.branch) + atOriginHead, "POST");
|
||||
await sendAction(apiBase() + "/builds/restart?branch=" + encodeURIComponent(button.dataset.branch) + atOriginHead, "POST");
|
||||
} else if (action === "cancel") {
|
||||
await sendAction(`/api/builds/${encodeURIComponent(button.dataset.artifactKey)}/cancel`, "POST");
|
||||
await sendAction(`${apiBase()}/builds/${encodeURIComponent(button.dataset.artifactKey)}/cancel`, "POST");
|
||||
} else if (action === "delete") {
|
||||
await sendAction("/api/builds/" + encodeURIComponent(button.dataset.artifactKey), "DELETE");
|
||||
await sendAction(apiBase() + "/builds/" + encodeURIComponent(button.dataset.artifactKey), "DELETE");
|
||||
}
|
||||
if (refreshNow) {
|
||||
refreshNow();
|
||||
|
||||
Reference in New Issue
Block a user