feat(git): README des Repositories holen

Erspart den Wechsel ins Git, nur um zu sehen, worum ein halbvergessenes
Projekt eigentlich ging.

GitHub hat einen eigenen Endpunkt, der jede Schreibweise findet - ein Aufruf
genuegt. GitLab und Gitea kennen keinen; dort wird erst das
Wurzelverzeichnis aufgelistet und dann die gefundene Datei geholt. Erkannt
werden README.md und die ueblichen Varianten, Gross- und Kleinschreibung
egal; Markdown hat Vorrang, weil nur das wirklich dargestellt werden kann.

Geholt wird hoechstens einmal am Tag, wie der Kalender, und mit eigenem
Zeitstempel: schlaegt das eine fehl, soll das andere nicht mit blockiert
werden.

Bei 60 000 Zeichen wird abgeschnitten, aber an einer Zeilengrenze - sonst
bliebe womoeglich ein halber Codeblock stehen. Dekodiert wird ueber Buffer
und nicht ueber atob, sonst kaemen Umlaute als Kauderwelsch heraus.

Kein README ist eine Auskunft, kein Fehlschlag: der Zeitstempel wird dann
trotzdem gesetzt, damit nicht jeder Abgleich erneut sucht.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-26 23:41:18 +02:00
co-authored by Claude Opus 5
parent 01884f4747
commit f8e6c66876
9 changed files with 332 additions and 3 deletions
+44 -2
View File
@@ -58,9 +58,10 @@ export async function syncRepository(repositoryId: string): Promise<SyncResult>
try {
const data = await client.fetchRepo(ref);
await persistSnapshot(repository, data);
// Erst nach dem Snapshot: die Zeile muss existieren, bevor der Kalender
// hineingeschrieben werden kann.
// Erst nach dem Snapshot: die Zeile muss existieren, bevor Kalender und
// README hineingeschrieben werden koennen.
await maybeRefreshCalendar(repository.id, client, ref);
await maybeRefreshReadme(repository.id, client, ref);
// Nach dem Speichern, damit die Meldung den neuen Stand sieht.
await notifyAfterSync(repository.id);
return {
@@ -196,6 +197,47 @@ async function maybeRefreshCalendar(
}
}
/**
* Holt das README – wie der Kalender hoechstens einmal am Tag.
*
* Ein eigener Zeitstempel und nicht der des Kalenders: schlaegt das eine
* fehl, soll das andere nicht mit blockiert werden.
*/
async function maybeRefreshReadme(
repositoryId: string,
client: GitClient,
ref: { owner: string; name: string },
): Promise<void> {
try {
const snapshot = await prisma.repoSnapshot.findUnique({
where: { repositoryId },
select: { readmeAt: true },
});
const zuletzt = snapshot?.readmeAt?.getTime() ?? 0;
if (Date.now() - zuletzt < CALENDAR_TTL_MS) return;
const readme = await client.fetchReadme(ref);
// Kein README im Repository: das ist eine Auskunft, kein Fehlschlag.
// Der Zeitstempel wird gesetzt, damit nicht jeder Abgleich erneut sucht,
// und ein etwaiger alter Stand verschwindet.
await prisma.repoSnapshot.update({
where: { repositoryId },
data: {
readme: readme?.content ?? null,
readmePath: readme?.path ?? null,
readmeBaseUrl: readme?.baseUrl ?? null,
readmeAt: new Date(),
},
});
} catch (error) {
// Wie beim Kalender: Beiwerk. Ohne Zeitstempel versucht es der naechste
// Durchgang von selbst wieder.
console.error("[nebula] README konnte nicht geholt werden:", error);
}
}
/** Owner/Name aus den Feldern oder ersatzweise aus der URL bestimmen. */
export function resolveRepoRef(
repository: Pick<Repository, "repoOwner" | "repoName" | "repoUrl">,