Zum Inhalt

Architektur

Repo Blueprint ist ein Repository, das sein eigener erster Nutzer ist: Die Workflows, die ihn prüfen, veröffentlichen und aktualisieren, sind die Vorlagen der Workflows jedes Repositorys, das aus ihm entsteht, und jedes seiner Releases erreicht sie alle als Pull Request. Diese Seite zeigt, wie er aufgebaut ist und welchen Weg ein Release nimmt. Die Automatisierung beschreibt jeden Workflow, das Sicherheitsdesign (englisch), was der Blueprint schützt und wem er vertraut, die Referenz (englisch) jede Frage, Variable und jedes Secret, und die Entscheidungen (englisch), warum er so arbeitet.

Wie der Blueprint funktioniert

---
config:
  flowchart:
    wrappingWidth: 400
---
flowchart TB
    accTitle: Wie der Blueprint funktioniert
    accDescr: Copier rendert ein neues Repository aus den Fragen in copier.yml und den Dateien in template/, die die Workflows aus .github/workflows/, die Manifeste aus stacks/ und blueprint.py aufnehmen. Renovate hält die Workflows und die Stacks aktuell.

    renovate(["Renovate"])

    subgraph here ["Dieses Repository"]
        workflows[".github/workflows/<br>laufen hier und sind<br>die Vorlagen"]
        stacks["stacks/<br>Manifeste und<br>Lock-Dateien"]
        tool["blueprint.py<br>Einstellungen · Checkliste<br>Bots"]
        template["template/<br>die Dateien eines<br>neuen Repositorys"]
        questions["copier.yml<br>die Fragen"]
    end

    copier(["Copier"])
    repo["Ein neues Repository"]

    renovate -. "hält aktuell" .-> workflows
    renovate -.-> stacks
    workflows --> template
    stacks --> template
    tool --> template
    template --> copier
    questions --> copier
    copier --> repo

    classDef bot fill:#526cfe2e,stroke:#526cfe,stroke-width:2px
    classDef done fill:#16a34a2e,stroke:#16a34a,stroke-width:2px
    class renovate bot
    class repo done
  • copier.yml enthält die Fragen. template/ enthält die Dateien eines neuen Repositorys; ein Datei- oder Ordnername wie [% if stack == 'php' %]composer.json[% endif %] trägt seine Bedingung, und ein Name, der leer gerendert wird, entfällt. Die Trennzeichen {= =} und [% %] kommen in keinem Workflow, Skript oder Manifest vor; so bleiben ${{ }} von GitHub Actions und [[ ]] von Bash und TOML unverändert.
  • Die Workflows in .github/workflows/ laufen in diesem Repository und sind zugleich die Vorlagen der Workflows jedes neuen Repositorys: actionlint prüft sie, und Renovate hält ihre Actions hier aktuell. Eine Zeile mit Not in the blueprint itself schaltet einen Job in diesem Repository ab und fehlt in neuen.
  • blueprint.py startet das Werkzeug der Einstellungen als Code, der Checkliste und der Bots. Sein Code liegt in blueprint/, ein Modul für jede Aufgabe, mit einer eigenen ruff.toml, damit die Ruff-Einstellungen eines Projekts nicht für ihn gelten.
  • stacks/ enthält Manifeste und Lock-Dateien jeder Projektart, die Renovate aktuell hält; ein neues Projekt startet von ihnen.
  • tests/ prüft blueprint.py gegen ein simuliertes GitHub und rendert jede Projektart; der Variants-Workflow rendert jede einzelne und führt ihre eigenen Prüfungen mit den echten Werkzeugen aus. Außerdem bringt er ein Repository jeder Art vom letzten Release auf den Commit; das muss jede Datei des Blueprints so bekommen, wie ein neues Repository sie hat, und dieselben Prüfungen bestehen: in jedem Pull Request außer dem Release-Pull-Request, und jede Woche. Ebenso aktualisiert er eines mit den Antworten jedes Repositorys, das den Blueprint übernimmt, aus tests/answers/: ohne den Beispielcode und mit dessen Lizenz, Sprachen und Discussions; dessen Website und Lint müssen ebenfalls bestehen. Der Downstream-Workflow klont jedes dieser Repositories und aktualisiert es wie sein Blueprint-Bot, auf main und auf den Pull Request: Seine Zusammenfassung nennt die Dateien, deren Update der Pull Request ändert, und jeden Konflikt, bevor das Release sie dorthin bringt, und ein Konflikt in einer Datei, die keine Textseite ist, etwa einem Workflow, einem Skript oder einer Konfiguration, lässt ihn scheitern (downstream.py). Der Windows-Workflow führt scripts/check.sh außerdem unter Windows aus, in Git Bash wie auf dem Rechner eines Mitwirkenden, jede Woche und für jeden Pull Request, der den Code oder die Tests ändert. Der Workflow Next runner führt die Variants jede Woche auf dem Runner aus, auf den die Jobs als Nächstes umziehen.
  • docs/ macht diese Dokumentation zur Website des Blueprints: ihre Seiten binden die READMEs ein, und der Dashboard-Workflow veröffentlicht sie mit dem Dashboard unter dashboard/.
  • reference.py schreibt die Referenz der Website aus copier.yml, den Workflows und den Einstellungen als Code der Vorlage, sodass jede Frage, Variable und jedes Secret dort beschrieben ist, wo man sie sucht; tests/test_reference.py scheitert, solange eine Seite von ihnen abweicht.
---
config:
  flowchart:
    wrappingWidth: 400
---
flowchart TB
    accTitle: Wie eine Änderung der Vorlage die Repositories vor ihrem Release trifft
    accDescr: Ein Pull Request ändert, was die Repositories vom Blueprint bekommen. Der Variants-Workflow erzeugt ein neues Repository jeder Art, bringt eines jeder Art vom letzten Release auf den Stand und eines mit den Antworten jedes Repositorys aus tests/answers, das jede Datei so bekommen muss wie ein neues Repository mit diesen Antworten und den Bau seiner Website und den Lint seiner Workflows und seines Markdowns bestehen muss. Der Downstream-Workflow klont jedes Repository und aktualisiert es wie sein Blueprint-Bot, auf main und auf den Pull Request, und nennt die Dateien, deren Update der Pull Request ändert, und jeden Konflikt; ein Konflikt, den der Pull Request in einen Workflow oder ein Skript bringt, lässt ihn scheitern. Dann wird der Pull Request gemergt, und das Release und der Rollout bringen ihn in jedes Repository.

    change(["Ein Pull Request ändert die Vorlage"])
    variants["Variants: ein neues Repository jeder Art,<br>eines vom letzten Release aktualisiert<br>und eines mit den Antworten jedes Repositorys<br>aus tests/answers/"]
    downstream["Downstream: jedes Repository geklont<br>und wie von seinem Blueprint-Bot aktualisiert,<br>auf main und auf den Pull Request"]
    summary["Die Zusammenfassung: die Dateien, deren Update<br>der Pull Request ändert, und jeder Konflikt"]
    conflict{"Ein Konflikt, den der Pull Request<br>in einen Workflow oder ein Skript bringt?"}
    fix(["Die Vorlage korrigieren oder im Changelog sagen,<br>wie ein Repository ihn löst"])
    merge["Der Pull Request wird gemergt"]
    rollout(["Das Release und der Rollout bringen ihn<br>in jedes Repository"])

    change --> variants & downstream
    downstream --> summary --> conflict
    conflict -- "ja: die Prüfung scheitert" --> fix --> change
    conflict -- "nein" --> merge
    variants -- "jede Variante besteht" --> merge
    merge --> rollout

    classDef bot fill:#526cfe2e,stroke:#526cfe,stroke-width:2px
    classDef person fill:#f59e0b2e,stroke:#f59e0b,stroke-width:2px
    classDef result fill:#16a34a2e,stroke:#16a34a,stroke-width:2px
    class variants,downstream,summary bot
    class change,fix person
    class rollout result

Der Weg eines Releases

---
config:
  flowchart:
    wrappingWidth: 400
---
flowchart TB
    accTitle: Der Weg eines Releases des Blueprints
    accDescr: Ein Pull Request wird in main des Blueprints gemergt, und der Release-Workflow hält den Release-Pull-Request mit der nächsten Version. Sein Merge, durch den Maintainer oder von selbst, wenn er nur Updates enthält, veröffentlicht das Release mit einem signierten Tag und prüft es. Der Rollout-Workflow startet dann den Blueprint-Bot jedes Repositorys, das den Blueprint übernimmt, heute fünf. Jeder Bot prüft das Release, führt copier update aus und hält einen Update-Pull-Request als Release-App, damit die Prüfungen des Repositorys laufen. Ohne Konflikte merged er sich selbst, sobald sie bestehen; mit Konflikten oder mit BLUEPRINT_AUTOMERGE auf off wartet er auf den Maintainer. Das Dashboard zeigt das Release des Blueprints, auf dem jedes Repository steht.

    merge(["Ein Pull Request wird<br>in main des Blueprints gemergt"])
    release["Release-Workflow<br>hält den Release-Pull-Request<br>mit der nächsten Version"]
    publish["Sein Merge veröffentlicht das Release<br>signiert seinen Tag und prüft es"]
    rollout["Rollout-Workflow<br>startet den Blueprint-Bot<br>jedes Repositorys"]
    bot["Blueprint-Bot, in jedem der fünf<br>prüft das Release<br>und führt copier update aus"]
    pull["Ein Update-Pull-Request<br>als Release-App"]
    checks["Die Prüfungen des Repositorys<br>testen das Update"]
    merged["Er merged sich selbst,<br>sobald jede Prüfung besteht"]
    wait(["Er wartet auf den Maintainer<br>mit Konflikten oder mit<br>BLUEPRINT_AUTOMERGE auf off"])
    dashboard(["Das Dashboard zeigt das Release,<br>auf dem jedes Repository steht"])

    merge --> release --> publish --> rollout --> bot --> pull --> checks
    checks --> merged --> dashboard
    checks -. "Konflikte" .-> wait
    wait -. "gelöst und gemergt" .-> dashboard

    classDef bot fill:#526cfe2e,stroke:#526cfe,stroke-width:2px
    classDef person fill:#f59e0b2e,stroke:#f59e0b,stroke-width:2px
    classDef done fill:#16a34a2e,stroke:#16a34a,stroke-width:2px
    class release,publish,rollout,bot,pull,checks,merged bot
    class merge,wait person
    class dashboard done
  1. Ein Merge in main des Blueprints startet den Release-Workflow, dessen release-please einen Release-Pull-Request mit der nächsten Version hält, bestimmt aus den Titeln der gemergten Pull Requests, und mit den Einträgen des Changelogs als Notizen.
  2. Das Release: Der Maintainer merged den Release-Pull-Request, oder er merged sich selbst, sobald seine Prüfungen bestehen, wenn er nur Updates enthält. Der Release-Workflow signiert seinen Tag mit gitsign, veröffentlicht das Release mit seinen SBOMs und seiner signierten Herkunft und prüft es so, wie es seine Nutzer können.
  3. Der Rollout-Workflow startet den Blueprint-Bot jedes Repositorys der Release-App gleich nach dem Release, und der wöchentliche Lauf der Bots bleibt als Sicherheitsnetz, damit ein Fix jedes Repository in Minuten erreicht (Entscheidungen 0052 und 0010, englisch).
  4. Der Blueprint-Bot jedes Repositorys prüft das Release, bevor er etwas ändert: Das Release muss unveränderlich sein, sein Tag muss auf einen Commit von main des Blueprints zeigen, und der Release-Workflow des Blueprints muss den Tag signiert haben (Entscheidung 0020, englisch). Dann führt er copier update darauf aus und hält einen Pull Request auf dem Branch blueprint/update, als Release-App, damit die Prüfungen des Repositorys laufen.
  5. Die Prüfungen des Repositorys testen das Update wie jeden Pull Request. Ohne Konflikte merged sich der Pull Request selbst, sobald jede Prüfung besteht; mit Konflikten oder mit BLUEPRINT_AUTOMERGE auf off wartet er auf den Maintainer, wie die Fehlerbehebung zeigt.
  6. Das Dashboard zeigt das Release des Blueprints, auf dem jedes Repository steht, und den Update-Pull-Request in der Farbe seiner Prüfungen, unter dennis-otto.github.io/repo-blueprint/dashboard.