CI / pruefen (push) Canceled after 0s
Behebt florian.wilde/nebula#1. Der Rueckweg "Issue geschlossen -> Aufgabe erledigt" gab es seit v1.7.0 schon, aber nur ueber den Webhook. Ist keiner eingerichtet - und am Repository war keiner eingetragen -, blieb er ganz aus. Damit hing die Funktion an einer Voraussetzung, die man ihr von aussen nicht ansieht. Der Abgleich fragt jetzt den Zustand verknuepfter Issues nach und hakt ab, was in Git geschlossen ist. Beides ergaenzt sich: der Webhook wirkt sofort, der Abgleich verlaesslich. Gespart wird, wo es geht: Nummern, die der gerade geholte Snapshot als offen fuehrt, werden nicht einzeln nachgefragt. Umgekehrt gilt das nicht - die Liste ist gedeckelt, aus dem Fehlen einer Nummer folgt nichts. Deshalb ein Deckel von 25 Nachfragen je Durchgang. **Nur abhaken, nie zuruecknehmen.** Der Abgleich sieht Zustaende, keine Ereignisse. Ein offenes Issue kann auch bedeuten, dass das Schliessen von Nebula aus fehlgeschlagen ist; ein symmetrischer Abgleich wuerde dann eine bewusst erledigte Aufgabe wieder aufmachen. Das Zuruecknehmen bleibt dem Webhook vorbehalten, der ein echtes "reopened" sieht. Ein geloeschtes Issue ist ebenfalls kein erledigtes. Nebenbei eine Unstimmigkeit aus v1.7.0 geradegezogen: der Webhook setzte nur den Status und liess aus, was die Oberflaeche tut - naechste Fassung einer wiederkehrenden Aufgabe, Eintrag im Verlauf. Beide Wege gehen jetzt durch dieselbe Funktion. Geprueft: 232 Unit-Tests, 31 E2E-Tests. Der neue E2E-Test laeuft als einziger wirklich nach draussen - er haengt eine Aufgabe an ein seit Jahren geschlossenes Issue und prueft, dass der Abgleich sie abhakt. Ist GitHub nicht erreichbar, ueberspringt er sich statt rot zu werden. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
/** * Das README eines Repositories – gemeinsame Teile fuer alle drei Anbieter. * * Gezeigt wird es auf der Projektseite, damit man nicht ins Git wechseln * muss, nur um zu sehen, worum ein halbvergessenes Projekt eigentlich ging. */ export interface RepoReadme { /** Roher Markdown-Text. */ content: string; /** Dateiname im Repository, etwa "README.md". */ path: string; /** * Basis, gegen die relative Links im Text aufzuloesen sind – die * Web-Ansicht des Repositories im Verzeichnis des READMEs. */ baseUrl: string | null; /** Wurde am Deckel abgeschnitten? */ truncated: boolean; } /** * Hoechstlaenge. Ein README von einem Megabyte gibt es, und es waere in * einer Vorschau ohnehin unlesbar – abgeschnitten wird an einer * Zeilengrenze, damit kein halber Codeblock stehen bleibt. */ export const README_MAX_CHARS = 60_000; /** * Dateinamen, die als README gelten – in dieser Reihenfolge. Markdown zuerst, * weil nur das dargestellt werden kann; die uebrigen landen als Klartext in * einem Codeblock, was immer noch besser ist als nichts. */ const KANDIDATEN = [ "readme.md", "readme.markdown", "readme.mdown", "readme", "readme.txt", "readme.rst", ]; /** * Sucht aus einer Verzeichnisauflistung die README-Datei heraus. * Gross- und Kleinschreibung ist egal; die Reihenfolge oben entscheidet. */ export function pickReadme(namen: string[]): string | null { const klein = new Map(namen.map((name) => [name.toLowerCase(), name])); for (const kandidat of KANDIDATEN) { const treffer = klein.get(kandidat); if (treffer) return treffer; } return null; } /** Kuerzt am Deckel, aber an einer Zeilengrenze. */ export function capReadme(text: string): { content: string; truncated: boolean } { if (text.length <= README_MAX_CHARS) return { content: text, truncated: false }; const roh = text.slice(0, README_MAX_CHARS); const letzterUmbruch = roh.lastIndexOf("\n"); return { // Nur wenn der Umbruch nicht ganz am Anfang liegt – sonst bliebe fast // nichts uebrig. content: letzterUmbruch > README_MAX_CHARS / 2 ? roh.slice(0, letzterUmbruch) : roh, truncated: true, }; } /** * Dekodiert, was die Anbieter als base64 liefern. * * Bewusst ueber Buffer und nicht ueber atob: der Inhalt ist UTF-8, und atob * gaebe Bytes zurueck, aus denen Umlaute als Kauderwelsch herauskaemen. */ export function decodeBase64(value: string): string { return Buffer.from(value.replace(/\s/g, ""), "base64").toString("utf8"); }