Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f38ae4c60a | ||
|
|
4f6a995a25 | ||
|
|
9a4026fb44 | ||
|
|
9f75b9b015 | ||
|
|
0e48f1e3fa | ||
|
|
476ecb3be1 |
@@ -17,6 +17,36 @@ und dieses Projekt folgt [Semantic Versioning](https://semver.org/lang/de/).
|
|||||||
|
|
||||||
### Sicherheit
|
### Sicherheit
|
||||||
|
|
||||||
|
## [1.0.1] – 2026-10-25
|
||||||
|
|
||||||
|
### Hinzugefügt
|
||||||
|
- `npm run releases:sync` legt zu jedem Tag eine Release in Gitea an – Titel und Beschreibung stammen aus diesem CHANGELOG (#046)
|
||||||
|
|
||||||
|
## [1.0.0] – 2026-10-23
|
||||||
|
|
||||||
|
Erste stabile Fassung. Kein neuer Funktionsumfang gegenüber 0.27.1 – was fehlte, war Erprobung, und
|
||||||
|
die ist erbracht:
|
||||||
|
|
||||||
|
- **Ein Lizenzjahr vollständig durchlaufen.** Für `hv-pve-01` und `hv-pve-02` (Robert Hofmann GmbH):
|
||||||
|
Warnung vor dem Ablauf am 17.10.2026, Verlängerung, Proxmox-Rechnung 291017 mit Rabatt je Position,
|
||||||
|
Kundenrechnung, beide bezahlt. Die Folgelaufzeiten stehen auf 17.10.2027.
|
||||||
|
- **Proxmox Backup Server mit aktiver Lizenz bestätigt.** Drei PBS-Hosts melden Key und Ablaufdatum.
|
||||||
|
- **Proxmox VE** ist seit dem Feldtest im September bestätigt: mit und ohne Subscription, einzeln und
|
||||||
|
im Cluster, mit und ohne QDevice.
|
||||||
|
- **Wiederherstellung geprobt** (#045): Daten zerstört, zurückgespielt, geprüft.
|
||||||
|
|
||||||
|
Im Betrieb seit dem 03.10.2026 mit 16 Hosts, vier Kunden und echten Rechnungen.
|
||||||
|
|
||||||
|
**Weiterhin ungeprüft:** Proxmox Mail Gateway – dort wird derzeit kein Host betrieben; der Zweig ist
|
||||||
|
im Handbuch als ungeprüft gekennzeichnet (#023). Ebenso steht die Wiederherstellungsprobe für die
|
||||||
|
Installation als Dienst noch aus; geprobt ist der Docker-Weg.
|
||||||
|
|
||||||
|
## [0.27.1] – 2026-10-23
|
||||||
|
|
||||||
|
### Sicherheit
|
||||||
|
- **Next.js auf 16.4.2.** Für 16.0.0 bis 16.4.0 wurden drei Lücken gemeldet, zwei davon **kritisch**: Remote Code Execution über die Bildoptimierung mit SVG-Dateien (GHSA-2464-fhr4-w9xw), Remote Code Execution in `use cache` (GHSA-673q-mjm6-r5xr) und eine Blockade über Server Actions (GHSA-p3m5-59mp-jv5r)
|
||||||
|
- ProxLedger nutzt weder `next/image` noch `use cache`; die Blockade über Server Actions betraf die Installation dagegen unmittelbar. **Wer öffentlich erreichbar ist, sollte sofort aktualisieren.**
|
||||||
|
|
||||||
## [0.27.0] – 2026-10-10
|
## [0.27.0] – 2026-10-10
|
||||||
|
|
||||||
### Hinzugefügt
|
### Hinzugefügt
|
||||||
|
|||||||
@@ -73,7 +73,7 @@
|
|||||||
|
|
||||||
## 2026-09-17 · Sitzung 1 · 19:36–laufend
|
## 2026-09-17 · Sitzung 1 · 19:36–laufend
|
||||||
**Issue:** #001–#007 (Meilensteine v0.1.0 bis v0.4.0)
|
**Issue:** #001–#007 (Meilensteine v0.1.0 bis v0.4.0)
|
||||||
**Version:** 0.27.0
|
**Version:** 1.0.1
|
||||||
|
|
||||||
### Erledigt
|
### Erledigt
|
||||||
- Auftrag gelesen, Rückfragen gestellt
|
- Auftrag gelesen, Rückfragen gestellt
|
||||||
@@ -232,6 +232,20 @@
|
|||||||
- Wiederherstellung geprobt (#045): Daten zerstört, zurückgespielt, geprüft. Zwei Lücken kamen erst dabei heraus – ohne `--clean` ließ sich nur in eine leere Datenbank zurückspielen, und für die PDFs gab es gar keinen Weg
|
- Wiederherstellung geprobt (#045): Daten zerstört, zurückgespielt, geprüft. Zwei Lücken kamen erst dabei heraus – ohne `--clean` ließ sich nur in eine leere Datenbank zurückspielen, und für die PDFs gab es gar keinen Weg
|
||||||
- Damit ist einer der vier Punkte bis 1.0.0 erledigt. Offen bleiben PMG, PBS mit Lizenz und ein vollständiger Lizenzjahr-Durchlauf (17.10.)
|
- Damit ist einer der vier Punkte bis 1.0.0 erledigt. Offen bleiben PMG, PBS mit Lizenz und ein vollständiger Lizenzjahr-Durchlauf (17.10.)
|
||||||
|
|
||||||
|
### 23.10.2026 – Wartungsblick
|
||||||
|
- `npm audit`: drei neue Meldungen zu Next.js ≤ 16.4.0, zwei davon kritisch (RCE über Bildoptimierung und `use cache`, dazu eine Blockade über Server Actions). Auf 16.4.2 angehoben, alles grün
|
||||||
|
- Geprüft, was davon ProxLedger trifft: `next/image` und `use cache` werden nicht genutzt, Server Actions dagegen überall – die Blockade war also echt
|
||||||
|
- Erinnerung an mich: Zwei Wartungsblicke in Folge haben hohe bis kritische Meldungen gefunden. Der wöchentliche Lauf in der CI ist überfällig
|
||||||
|
|
||||||
|
### 23.10.2026 – 1.0.0
|
||||||
|
- Florian hat den Beleg geliefert: `hv-pve-01` und `hv-pve-02` sind verlängert, beide Rechnungen bezahlt, die Folgelaufzeiten stehen auf 17.10.2027. Dazu drei PBS-Hosts mit aktiver Lizenz
|
||||||
|
- PMG wird zurückgestellt – kein Host im Betrieb. Die Lücke bleibt im Handbuch benannt, statt sie wegzudefinieren
|
||||||
|
- Damit ist 1.0.0 gerechtfertigt: nicht weil nichts mehr fehlt, sondern weil das, was fehlte, erprobt wurde
|
||||||
|
|
||||||
|
### 25.10.2026 – Releases
|
||||||
|
- `releases:sync` gebaut: Das CHANGELOG ist die Quelle, Gitea bekommt daraus Releases zu den vorhandenen Tags
|
||||||
|
- Dabei bestätigt, was beim Issue-Abgleich am 10.10. schon aufgefallen war: Der hinterlegte Zugang ist ein elfstelliges Passwort, kein API-Token. Pushen geht, die API nicht
|
||||||
|
|
||||||
### Nächster Schritt
|
### Nächster Schritt
|
||||||
- #016 Benutzerhandbuch mit Screenshots, danach #017 Favicons und #019 CI-Pipeline
|
- #016 Benutzerhandbuch mit Screenshots, danach #017 Favicons und #019 CI-Pipeline
|
||||||
- Für 1.0.0 fehlt noch: PMG (kein Host verfügbar) und der Fall „aktive Subscription auf PBS“ (#023)
|
- Für 1.0.0 fehlt noch: PMG (kein Host verfügbar) und der Fall „aktive Subscription auf PBS“ (#023)
|
||||||
|
|||||||
@@ -3,10 +3,11 @@
|
|||||||
Zentrales Dashboard für den Subscription-Status aller Proxmox-Hosts (VE, Backup Server, Mail Gateway).
|
Zentrales Dashboard für den Subscription-Status aller Proxmox-Hosts (VE, Backup Server, Mail Gateway).
|
||||||
Auf jedem Host meldet ein schlanker Agent täglich per HTTPS seinen Status (Push-Modell) – auch aus Kundennetzen hinter NAT.
|
Auf jedem Host meldet ein schlanker Agent täglich per HTTPS seinen Status (Push-Modell) – auch aus Kundennetzen hinter NAT.
|
||||||
|
|
||||||
> **Status:** einsatzbereit. Der Agent ist auf echten Hosts erprobt – Proxmox VE 9.2 mit und ohne
|
> **Status: 1.0.0 – im produktiven Einsatz.** Seit dem 03.10.2026 mit echten Daten in Betrieb; ein
|
||||||
> Subscription, im Cluster und einzeln, sowie Proxmox Backup Server 4.2 (#018, #023).
|
> Lizenzjahr ist vollständig durchlaufen (Warnung → Verlängerung → Einkauf → Kundenrechnung → bezahlt).
|
||||||
> Ungeprüft: Mail Gateway und eine aktive Subscription auf PBS. Was bis 1.0 fehlt, steht in
|
> Erprobt auf Proxmox VE 9.2 mit und ohne Subscription, einzeln und im Cluster, sowie auf Proxmox
|
||||||
> [docs/roadmap.md](docs/roadmap.md).
|
> Backup Server 4.2 mit und ohne Lizenz. **Ungeprüft:** Mail Gateway – dort wird derzeit kein Host
|
||||||
|
> betrieben. Was als Nächstes ansteht, steht in [docs/roadmap.md](docs/roadmap.md).
|
||||||
|
|
||||||
## Schnellstart
|
## Schnellstart
|
||||||
|
|
||||||
@@ -174,9 +175,12 @@ npm run issues:sync -- --dry-run # zeigt nur, was passieren würde
|
|||||||
npm run issues:sync # echter Abgleich
|
npm run issues:sync # echter Abgleich
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Dasselbe gilt für Releases: `npm run releases:sync` legt zu jedem Tag einen Eintrag unter *Releases*
|
||||||
|
an und nimmt den passenden Abschnitt aus dem CHANGELOG als Beschreibung (`--dry-run` zeigt es nur an).
|
||||||
|
|
||||||
Serveradresse und Repository liest das Skript aus dem Git-Remote, den Token aus den hinterlegten
|
Serveradresse und Repository liest das Skript aus dem Git-Remote, den Token aus den hinterlegten
|
||||||
Git-Zugangsdaten (Schlüsselbund oder `~/.git-credentials`) – derselbe Token wie fürs Pushen, er braucht
|
Git-Zugangsdaten (Schlüsselbund oder `~/.git-credentials`) – derselbe Token wie fürs Pushen, er braucht
|
||||||
zusätzlich das Recht `issue:write`. Wer beides trennen möchte, setzt `GITEA_URL`, `GITEA_REPO` und
|
zusätzlich die Rechte `issue:write` (Issues) und `write:repository` (Releases). Wer beides trennen möchte, setzt `GITEA_URL`, `GITEA_REPO` und
|
||||||
`GITEA_TOKEN` in der `.env`.
|
`GITEA_TOKEN` in der `.env`.
|
||||||
|
|
||||||
Der Abgleich legt Labels und Meilensteine an, erkennt vorhandene Issues an der Nummer im Titel,
|
Der Abgleich legt Labels und Meilensteine an, erkennt vorhandene Issues an der Nummer im Titel,
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# #023 Feldtest auf Proxmox Backup Server und Mail Gateway
|
# #023 Feldtest auf Proxmox Backup Server und Mail Gateway
|
||||||
**Status:** offen
|
**Status:** erledigt
|
||||||
**Meilenstein:** v1.0.0
|
**Meilenstein:** v1.0.0
|
||||||
**Labels:** agent, feldtest
|
**Labels:** agent, feldtest
|
||||||
**Geschätzt:** 2 h **Tatsächlich:** 0 h 35 min (PBS erledigt, PMG offen)
|
**Geschätzt:** 2 h **Tatsächlich:** 0 h 35 min
|
||||||
|
|
||||||
## Ziel
|
## Ziel
|
||||||
Der Feldtest in #018 lief auf zwei Proxmox-VE-Hosts. Für Backup Server und Mail Gateway beruht die
|
Der Feldtest in #018 lief auf zwei Proxmox-VE-Hosts. Für Backup Server und Mail Gateway beruht die
|
||||||
@@ -23,7 +23,7 @@ falsch erwiesen (`--output-format json` gibt es dort nicht). Das gehört vor 1.0
|
|||||||
- [x] Erhobene Ausgaben als Testfälle in `tests/agent-skript.test.ts` aufnehmen – der heutige
|
- [x] Erhobene Ausgaben als Testfälle in `tests/agent-skript.test.ts` aufnehmen – der heutige
|
||||||
PBS-Testfall ist erfunden (JSON) und damit womöglich am echten Verhalten vorbei
|
PBS-Testfall ist erfunden (JSON) und damit womöglich am echten Verhalten vorbei
|
||||||
- [x] Agent auf einem PBS-Host installieren, einen Report abwarten, wieder deinstallieren
|
- [x] Agent auf einem PBS-Host installieren, einen Report abwarten, wieder deinstallieren
|
||||||
- [ ] PMG offen lassen und die Lücke im Handbuch benennen
|
- [x] PMG: von Florian am 23.10.2026 zurückgestellt – wird derzeit nicht betrieben, die Lücke bleibt im Handbuch benannt
|
||||||
|
|
||||||
## Notizen
|
## Notizen
|
||||||
Der Cluster-Abgleich aus #022 betrifft nur PVE: PBS und PMG kennen kein `pvecm`, dort bleibt die
|
Der Cluster-Abgleich aus #022 betrifft nur PVE: PBS und PMG kennen kein `pvecm`, dort bleibt die
|
||||||
@@ -67,3 +67,14 @@ ist auch der letzte offene Punkt aus #018 erledigt.
|
|||||||
|
|
||||||
**Weiterhin offen:** PMG (kein Host) und der Fall „aktive Subscription auf PBS“ (nur unlizenzierte
|
**Weiterhin offen:** PMG (kein Host) und der Fall „aktive Subscription auf PBS“ (nur unlizenzierte
|
||||||
Hosts verfügbar). Dieses Issue bleibt deshalb offen.
|
Hosts verfügbar). Dieses Issue bleibt deshalb offen.
|
||||||
|
|
||||||
|
## Abschluss am 23.10.2026
|
||||||
|
|
||||||
|
**PBS mit aktiver Lizenz: bestätigt.** In der produktiven Installation melden `pbs-cloud-01`,
|
||||||
|
`bup-pbs-01` und `bup-lwwif-pbs-01` ihre `pbsb-…`-Keys samt Ablaufdatum (04.10.2027). Damit ist auch
|
||||||
|
der lizenzierte Fall auf Proxmox Backup Server belegt – nach dem Fund der Rahmentabelle (#037) war
|
||||||
|
das der letzte offene Punkt dieses Produktzweigs.
|
||||||
|
|
||||||
|
**PMG: zurückgestellt.** Florian betreibt derzeit kein Mail Gateway; der Feldtest entfällt bis auf
|
||||||
|
Weiteres. Im Handbuch steht weiterhin, dass dieser Zweig ungeprüft ist und der erste PMG-Host nach
|
||||||
|
der Installation einmal gegenzuprüfen gehört (`proxledger-agent --dry-run`).
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# #046 Releases in Gitea statt nur Tags
|
||||||
|
**Status:** erledigt
|
||||||
|
**Meilenstein:** v1.0.1
|
||||||
|
**Labels:** chore, doku
|
||||||
|
**Geschätzt:** 1 h **Tatsächlich:** 0 h 25 min
|
||||||
|
|
||||||
|
## Auslöser
|
||||||
|
„Kannst du auch Releases machen und nicht nur Tags?“
|
||||||
|
|
||||||
|
## Unterschied
|
||||||
|
Ein Tag ist eine Markierung im Git-Verlauf. Eine **Release** ist ein Eintrag in Gitea mit Titel,
|
||||||
|
Beschreibung und Download-Verweisen – sichtbar unter *Releases*, abonnierbar und verlinkbar.
|
||||||
|
Bislang gab es nur Tags; die Beschreibungen standen allein im CHANGELOG.
|
||||||
|
|
||||||
|
## Umsetzung
|
||||||
|
`npm run releases:sync` liest das CHANGELOG, nimmt jeden Abschnitt `## [x.y.z] – Datum`, für den es
|
||||||
|
ein Tag gibt, und legt daraus eine Release an. Ein zweiter Lauf aktualisiert nur, was sich geändert
|
||||||
|
hat; „Unreleased“ bleibt außen vor, weil es dazu kein Tag gibt.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run releases:sync -- --dry-run # zeigt nur, was passieren würde
|
||||||
|
npm run releases:sync # legt an und aktualisiert
|
||||||
|
npm run releases:sync -- v1.0.0 # nur eine Fassung
|
||||||
|
```
|
||||||
|
|
||||||
|
Fünf Tests decken die Zerlegung ab.
|
||||||
|
|
||||||
|
## Offen: der Token
|
||||||
|
Der in den Git-Zugangsdaten hinterlegte Wert ist elf Zeichen lang – ein Passwort, kein API-Token.
|
||||||
|
Pushen funktioniert damit, die API antwortet mit 401. Betrifft auch `npm run issues:sync`, das seit
|
||||||
|
dem 10.10.2026 nicht mehr durchläuft.
|
||||||
|
|
||||||
|
**Nötig:** in Gitea unter *Einstellungen → Anwendungen* ein Token mit den Rechten
|
||||||
|
`write:repository` (für Releases) und `issue:write` (für den Issue-Abgleich) erzeugen und entweder
|
||||||
|
als `GITEA_TOKEN` in die `.env` legen oder in den Git-Zugangsdaten hinterlegen. Danach trägt ein
|
||||||
|
einziger Lauf alle 43 vorhandenen Fassungen nach.
|
||||||
@@ -26,7 +26,7 @@ Details und Architektur: [../architektur.md](../architektur.md). Zeiten werden a
|
|||||||
| [#020](020-gitea-issue-abgleich.md) | Issues nach Gitea übertragen | erledigt | v0.4.1 | 2 h | 0 h 10 min |
|
| [#020](020-gitea-issue-abgleich.md) | Issues nach Gitea übertragen | erledigt | v0.4.1 | 2 h | 0 h 10 min |
|
||||||
| [#021](021-geheimnisse-verschluesseln.md) | Zugangsdaten verschlüsselt speichern | erledigt | v0.13.1 | 2 h | 0 h 20 min |
|
| [#021](021-geheimnisse-verschluesseln.md) | Zugangsdaten verschlüsselt speichern | erledigt | v0.13.1 | 2 h | 0 h 20 min |
|
||||||
| [#022](022-cluster-abgleich.md) | Cluster-Abgleich | erledigt | v0.14.0 | 3 h | 0 h 25 min |
|
| [#022](022-cluster-abgleich.md) | Cluster-Abgleich | erledigt | v0.14.0 | 3 h | 0 h 25 min |
|
||||||
| [#023](023-feldtest-pbs-pmg.md) | Feldtest auf PBS und PMG | offen | v1.0.0 | 2 h | 0 h 35 min (PBS erledigt, PMG offen) |
|
| [#023](023-feldtest-pbs-pmg.md) | Feldtest auf PBS und PMG | erledigt | v1.0.0 | 2 h | 0 h 35 min |
|
||||||
| [#024](024-owasp-haertung.md) | Härtung nach OWASP | erledigt | v0.15.0 | 4 h | 0 h 45 min |
|
| [#024](024-owasp-haertung.md) | Härtung nach OWASP | erledigt | v0.15.0 | 4 h | 0 h 45 min |
|
||||||
| [#025](025-einstellungen.md) | Einstellungen im Dashboard | erledigt | v0.16.0 | 5 h | 1 h 10 min |
|
| [#025](025-einstellungen.md) | Einstellungen im Dashboard | erledigt | v0.16.0 | 5 h | 1 h 10 min |
|
||||||
| [#026](026-auslieferung.md) | Auslieferung finalisieren | erledigt | v0.17.0 | 3 h | 0 h 40 min |
|
| [#026](026-auslieferung.md) | Auslieferung finalisieren | erledigt | v0.17.0 | 3 h | 0 h 40 min |
|
||||||
@@ -49,4 +49,5 @@ Details und Architektur: [../architektur.md](../architektur.md). Zeiten werden a
|
|||||||
| [#043](043-laufzeiten-bearbeiten.md) | Laufzeiten anlegen, bearbeiten, zusammenführen | erledigt | v0.25.0 | 4 h | 1 h 05 min |
|
| [#043](043-laufzeiten-bearbeiten.md) | Laufzeiten anlegen, bearbeiten, zusammenführen | erledigt | v0.25.0 | 4 h | 1 h 05 min |
|
||||||
| [#044](044-eigenbedarf.md) | Eigene Hosts ohne Weiterberechnung | erledigt | v0.26.0 | 2 h | 0 h 40 min |
|
| [#044](044-eigenbedarf.md) | Eigene Hosts ohne Weiterberechnung | erledigt | v0.26.0 | 2 h | 0 h 40 min |
|
||||||
| [#045](045-wiederherstellung-geprobt.md) | Wiederherstellung proben und Skript dafür | erledigt | v0.27.0 | 2 h | 0 h 50 min |
|
| [#045](045-wiederherstellung-geprobt.md) | Wiederherstellung proben und Skript dafür | erledigt | v0.27.0 | 2 h | 0 h 50 min |
|
||||||
| | **Summe** | | | **177 h** | **25 h 28 min** |
|
| [#046](046-gitea-releases.md) | Releases in Gitea statt nur Tags | erledigt | v1.0.1 | 1 h | 0 h 25 min |
|
||||||
|
| | **Summe** | | | **178 h** | **25 h 53 min** |
|
||||||
|
|||||||
+18
-5
@@ -1,6 +1,6 @@
|
|||||||
# Roadmap
|
# Roadmap
|
||||||
|
|
||||||
Stand: 10.10.2026, Fassung 0.27.0
|
Stand: 23.10.2026, Fassung 1.0.0
|
||||||
|
|
||||||
Die erste Fassung dieser Roadmap entstand vor dem produktiven Einsatz. Seitdem läuft ProxLedger mit
|
Die erste Fassung dieser Roadmap entstand vor dem produktiven Einsatz. Seitdem läuft ProxLedger mit
|
||||||
echten Daten, und das hat die Reihenfolge deutlich verändert: Was aus der Benutzung kam, war fast
|
echten Daten, und das hat die Reihenfolge deutlich verändert: Was aus der Benutzung kam, war fast
|
||||||
@@ -18,12 +18,25 @@ Stellen.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Bis 1.0.0
|
## 1.0.0 ist erreicht (23.10.2026)
|
||||||
|
|
||||||
Kein neuer Funktionsumfang – es fehlt Erprobung.
|
Alle Kriterien erfüllt:
|
||||||
|
|
||||||
| Punkt | Warum | Aufwand |
|
| Punkt | Stand |
|
||||||
|---|---|---|
|
|---|---|
|
||||||
|
| Ein Lizenzjahr vollständig durchlaufen | **erledigt** – `hv-pve-01` und `hv-pve-02` am 17.10.2026, beide Rechnungen bezahlt |
|
||||||
|
| PBS mit aktiver Lizenz | **erledigt** – drei PBS-Hosts melden Key und Ablauf |
|
||||||
|
| Sicherung zurückspielen | **erledigt** ([#045](issues/045-wiederherstellung-geprobt.md)) |
|
||||||
|
| PMG im Feldtest | **zurückgestellt** – wird derzeit nicht betrieben ([#023](issues/023-feldtest-pbs-pmg.md)) |
|
||||||
|
|
||||||
|
Zwei kleinere Punkte bleiben offen, keiner davon hält 1.0.0 auf:
|
||||||
|
|
||||||
|
- Die Wiederherstellungsprobe für die **Installation als Dienst** (geprobt ist der Docker-Weg).
|
||||||
|
- Ein **wöchentlicher `npm audit`-Lauf in der CI**. Zwei Wartungsblicke hintereinander haben hohe bis
|
||||||
|
kritische Meldungen gefunden – zuletzt zwei kritische Lücken in Next.js. Bei einem öffentlich
|
||||||
|
erreichbaren Dashboard gehört das automatisiert.
|
||||||
|
|
||||||
|
---|---|---|
|
||||||
| **PMG im Feldtest** ([#023](issues/023-feldtest-pbs-pmg.md)) | Der einzige Produktzweig ohne echten Host. Bei PBS lag die Annahme daneben, und der Agent hätte dort nichts gemeldet. | 1 h, sobald ein Host da ist |
|
| **PMG im Feldtest** ([#023](issues/023-feldtest-pbs-pmg.md)) | Der einzige Produktzweig ohne echten Host. Bei PBS lag die Annahme daneben, und der Agent hätte dort nichts gemeldet. | 1 h, sobald ein Host da ist |
|
||||||
| **PBS mit aktiver Lizenz** | Bisher nur „keine Subscription“ geprüft. | 30 min |
|
| **PBS mit aktiver Lizenz** | Bisher nur „keine Subscription“ geprüft. | 30 min |
|
||||||
| ~~Sicherung einmal zurückspielen~~ | **Erledigt am 10.10.2026** ([#045](issues/045-wiederherstellung-geprobt.md)) – dabei kamen zwei Lücken heraus. Offen bleibt die Probe für die Installation als Dienst. | – |
|
| ~~Sicherung einmal zurückspielen~~ | **Erledigt am 10.10.2026** ([#045](issues/045-wiederherstellung-geprobt.md)) – dabei kamen zwei Lücken heraus. Offen bleibt die Probe für die Installation als Dienst. | – |
|
||||||
|
|||||||
Generated
+51
-51
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "proxledger",
|
"name": "proxledger",
|
||||||
"version": "0.26.0",
|
"version": "0.27.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "proxledger",
|
"name": "proxledger",
|
||||||
"version": "0.26.0",
|
"version": "0.27.0",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@node-rs/argon2": "^2.2.1",
|
"@node-rs/argon2": "^2.2.1",
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
"bootstrap-icons": "^1.13.1",
|
"bootstrap-icons": "^1.13.1",
|
||||||
"croner": "^10.0.1",
|
"croner": "^10.0.1",
|
||||||
"jose": "^6.2.12",
|
"jose": "^6.2.12",
|
||||||
"next": "^16.4.0",
|
"next": "^16.4.2",
|
||||||
"nodemailer": "^10.0.10",
|
"nodemailer": "^10.0.10",
|
||||||
"openid-client": "^6.8.8",
|
"openid-client": "^6.8.8",
|
||||||
"react": "19.2.8",
|
"react": "19.2.8",
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
"dotenv": "^17.4.2",
|
"dotenv": "^17.4.2",
|
||||||
"esbuild": "^0.28.2",
|
"esbuild": "^0.28.2",
|
||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "^16.4.0",
|
"eslint-config-next": "^16.4.2",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"prettier": "^3.9.7",
|
"prettier": "^3.9.7",
|
||||||
"prisma": "^7.10.0",
|
"prisma": "^7.10.0",
|
||||||
@@ -1646,15 +1646,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/env": {
|
"node_modules/@next/env": {
|
||||||
"version": "16.4.0",
|
"version": "16.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.4.2.tgz",
|
||||||
"integrity": "sha512-pyLlT5oafGlX8X7xjgGNXz5dNIDSxREm+qD6i9VLSZiZFopA/XWl8NM/KPmLx/8EG/++c49iid3gNWSQW1+2dQ==",
|
"integrity": "sha512-nTUTFH7G5/J/fmpkjVLegOkqyKOP0CEsJcMsyO4UCYeN1su+YMW/MfaSErnJskL7XM+UDK6aTvslxsRCyya/sA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@next/eslint-plugin-next": {
|
"node_modules/@next/eslint-plugin-next": {
|
||||||
"version": "16.4.0",
|
"version": "16.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.4.2.tgz",
|
||||||
"integrity": "sha512-ffeflB/dL8RrvWejQximw01pOpKHN692Y3FjDB82DCd0YVvXvBD6vY7mcZp27yvSVFVM6wA73xVodEKF4spizA==",
|
"integrity": "sha512-gCTWlw0e+Y6kajpFyKKgA195jUJCRhlzJE0lV7ufe8a+ghIsANWZ0/FiJwg6DuOG1+OvRbcZDcFBoRh3mNJx0g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -1695,9 +1695,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-darwin-arm64": {
|
"node_modules/@next/swc-darwin-arm64": {
|
||||||
"version": "16.4.0",
|
"version": "16.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.4.2.tgz",
|
||||||
"integrity": "sha512-yyZ+9MgViAKLLb5l1gPBGjRwnq3teNLb5Zx6JEw8LzNSTjlsnp+baUHFxTgl7PpjCmKf48VQfyipG1eBflb48Q==",
|
"integrity": "sha512-ONkhFvZNFWlsEHOM/81AtxdyuJP2KRYKYR0JxPwvcu1AuPMMMgcL+wW2luldJvMtZwKtUxoSkCychW5wIeoYSw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1711,9 +1711,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-darwin-x64": {
|
"node_modules/@next/swc-darwin-x64": {
|
||||||
"version": "16.4.0",
|
"version": "16.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.4.2.tgz",
|
||||||
"integrity": "sha512-RePEEejEj1Auf5s9qEcG0JbcDP3ceNUS/ofNX8AhxzgIjTVlZIHMatlp16YplcSMpsVo/ZMQHJTjbMYJznjGUg==",
|
"integrity": "sha512-luPCMbOsCoaF+kXJEUjmjmOZg1lC3BflhmUu+ZmW4tBu2vW/4AXak8JBFvePizGkxVmNiAw/r9ra+6UviCWtWQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1727,9 +1727,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-arm64-gnu": {
|
"node_modules/@next/swc-linux-arm64-gnu": {
|
||||||
"version": "16.4.0",
|
"version": "16.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.4.2.tgz",
|
||||||
"integrity": "sha512-HhsyCqd+NpmvDDVY/LqkAN24f+QVvXh3ie+PfcwKinWL2Mu826JymQxJXGjAIfmJ4VlHjp4Pe/nZtXtUoEMAqw==",
|
"integrity": "sha512-xhwFTAncJyaCNy5KqKKuKaIIdCy1/1ZV1s5S1G0hhT7wGMkDTlfJ6aQbfRFnIG/s3/kDrxQ8aycVe+c5QnP8ow==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1746,9 +1746,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-arm64-musl": {
|
"node_modules/@next/swc-linux-arm64-musl": {
|
||||||
"version": "16.4.0",
|
"version": "16.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.4.2.tgz",
|
||||||
"integrity": "sha512-sN9Pf+Zh+RpZAOmbR7d8KAA8T61CVhawtbEJ2Bk6QrRzx6Z/6haaF4UiwBzytM96GRRRWZh9/WALIEO3npMamA==",
|
"integrity": "sha512-T1PNmfbPcxr5ndtWUL/3wQtLXsz808lq6sc2GrNT5k2oquF7UyqZddHSuAewHVsWsGXe6nsl9HoN1YNriVjuSg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1765,9 +1765,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-x64-gnu": {
|
"node_modules/@next/swc-linux-x64-gnu": {
|
||||||
"version": "16.4.0",
|
"version": "16.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.4.2.tgz",
|
||||||
"integrity": "sha512-D5M5xLWQVd1CvR+L7BickM1weRfmM41x3L1NWcnKY/oyJJqr1qwaCgmu3dMAhLBOMdZwSuDCRV6DJRRpiUkbDQ==",
|
"integrity": "sha512-N9cDUOCGtkJG5PNKDFZUF3xXndrJJcpLp121uAGjIGvO/U2p9tfdNOM9htj7+5f72l0m8rMw/cvYacUJBQoudg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1784,9 +1784,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-x64-musl": {
|
"node_modules/@next/swc-linux-x64-musl": {
|
||||||
"version": "16.4.0",
|
"version": "16.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.4.2.tgz",
|
||||||
"integrity": "sha512-8d5WLS6a9YalqpOgXdlbvonFu+sIgjA+CO6tGzetrpPVcUL56foa7B7664h41rAlbLi5fJwK4vWji3zf4QDy+w==",
|
"integrity": "sha512-yY2uh1YhX6ufuYy2FCbw1dqblbhI57GLYAJfCkqD9auTv6pLrH67Uccjc3QMKBeCYAG6lkgu08ExmX1bbjejjQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1803,9 +1803,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-win32-arm64-msvc": {
|
"node_modules/@next/swc-win32-arm64-msvc": {
|
||||||
"version": "16.4.0",
|
"version": "16.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.4.2.tgz",
|
||||||
"integrity": "sha512-sQuOIiW3snb48UwrBcDMvFA/u03qtw2LeuSIxFIQ9AJuW9UG/4Ddsfo+eV73PnZwBhyncMtsKVh7PrzMol2/fA==",
|
"integrity": "sha512-j/ArWRIHpMMB6wYLMhr2puTguBt70lNU/Cmjl9p6dTDE8DGknYJMP9PdwPk2dqpCNL/KIX9yfngDvIesxJPmsg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1819,9 +1819,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-win32-x64-msvc": {
|
"node_modules/@next/swc-win32-x64-msvc": {
|
||||||
"version": "16.4.0",
|
"version": "16.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.4.2.tgz",
|
||||||
"integrity": "sha512-6/33lCuxpdHUy27RegyGM/5uTpF7YZd3UR4D0gpkOA/tWY5YR7msbQjqPgCZmSmxrrTLGCXYA4A8vT2H5DPGTQ==",
|
"integrity": "sha512-AA4edyWHDko3NCSo0x6GomsdgFb0oR1J3MXU1b5xt6FRFg4AucrMPW3S0hCe/9onU0O1BbpGQBsqo1r0Gneu1g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -5889,13 +5889,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-config-next": {
|
"node_modules/eslint-config-next": {
|
||||||
"version": "16.4.0",
|
"version": "16.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.4.2.tgz",
|
||||||
"integrity": "sha512-2RHLYswzc+hNRktWAjIL6XAvwb1cKcjLawJ5KWywAxn/JivNHtG42ZigL1yobVXDqsimuuL2F9jcEcnHFzbppQ==",
|
"integrity": "sha512-P33vjcETMXc+qBjpF77TeJHZLzk6cPcTy8KjpxLucYB3mCIFIORMcZ+cBXcAiIYjE5RM7jyknEEOwMNdnx/ADQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@next/eslint-plugin-next": "16.4.0",
|
"@next/eslint-plugin-next": "16.4.2",
|
||||||
"eslint-import-resolver-node": "^0.3.6",
|
"eslint-import-resolver-node": "^0.3.6",
|
||||||
"eslint-import-resolver-typescript": "^3.5.2",
|
"eslint-import-resolver-typescript": "^3.5.2",
|
||||||
"eslint-plugin-import": "^2.32.0",
|
"eslint-plugin-import": "^2.32.0",
|
||||||
@@ -8184,12 +8184,12 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/next": {
|
"node_modules/next": {
|
||||||
"version": "16.4.0",
|
"version": "16.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/next/-/next-16.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/next/-/next-16.4.2.tgz",
|
||||||
"integrity": "sha512-N2C+9q89Z/oLrsepSRS4jRMtwmUZAoZuAHU0e9T6+sAB5DdhADmrnOW+zjYijlrYFmVfmQhGVGy9eMFoB7UgKg==",
|
"integrity": "sha512-r+oBJojfefUjOiWkBhXgLS+igxX/7BWyK59uHnyOyplMSLtBZ+QLP3okrdYTbrfrbrmNTVWLBPaHDX9KRaIt3w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@next/env": "16.4.0",
|
"@next/env": "16.4.2",
|
||||||
"@swc/helpers": "0.5.23",
|
"@swc/helpers": "0.5.23",
|
||||||
"baseline-browser-mapping": "^2.9.19",
|
"baseline-browser-mapping": "^2.9.19",
|
||||||
"caniuse-lite": "^1.0.30001579",
|
"caniuse-lite": "^1.0.30001579",
|
||||||
@@ -8203,15 +8203,15 @@
|
|||||||
"node": ">=20.9.0"
|
"node": ">=20.9.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@next/swc-darwin-arm64": "16.4.0",
|
"@next/swc-darwin-arm64": "16.4.2",
|
||||||
"@next/swc-darwin-x64": "16.4.0",
|
"@next/swc-darwin-x64": "16.4.2",
|
||||||
"@next/swc-linux-arm64-gnu": "16.4.0",
|
"@next/swc-linux-arm64-gnu": "16.4.2",
|
||||||
"@next/swc-linux-arm64-musl": "16.4.0",
|
"@next/swc-linux-arm64-musl": "16.4.2",
|
||||||
"@next/swc-linux-x64-gnu": "16.4.0",
|
"@next/swc-linux-x64-gnu": "16.4.2",
|
||||||
"@next/swc-linux-x64-musl": "16.4.0",
|
"@next/swc-linux-x64-musl": "16.4.2",
|
||||||
"@next/swc-win32-arm64-msvc": "16.4.0",
|
"@next/swc-win32-arm64-msvc": "16.4.2",
|
||||||
"@next/swc-win32-x64-msvc": "16.4.0",
|
"@next/swc-win32-x64-msvc": "16.4.2",
|
||||||
"sharp": "^0.35.4"
|
"sharp": "^0.35.5"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@opentelemetry/api": "^1.1.0",
|
"@opentelemetry/api": "^1.1.0",
|
||||||
|
|||||||
+5
-4
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "proxledger",
|
"name": "proxledger",
|
||||||
"version": "0.27.0",
|
"version": "1.0.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
@@ -27,7 +27,8 @@
|
|||||||
"screenshots": "node scripts/screenshots.mjs",
|
"screenshots": "node scripts/screenshots.mjs",
|
||||||
"icons": "node scripts/icons.mjs",
|
"icons": "node scripts/icons.mjs",
|
||||||
"pruefe:doku": "node scripts/pruefe-doku.mjs",
|
"pruefe:doku": "node scripts/pruefe-doku.mjs",
|
||||||
"benutzer": "tsx scripts/benutzer.ts"
|
"benutzer": "tsx scripts/benutzer.ts",
|
||||||
|
"releases:sync": "tsx scripts/gitea-releases.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@node-rs/argon2": "^2.2.1",
|
"@node-rs/argon2": "^2.2.1",
|
||||||
@@ -41,7 +42,7 @@
|
|||||||
"bootstrap-icons": "^1.13.1",
|
"bootstrap-icons": "^1.13.1",
|
||||||
"croner": "^10.0.1",
|
"croner": "^10.0.1",
|
||||||
"jose": "^6.2.12",
|
"jose": "^6.2.12",
|
||||||
"next": "^16.4.0",
|
"next": "^16.4.2",
|
||||||
"nodemailer": "^10.0.10",
|
"nodemailer": "^10.0.10",
|
||||||
"openid-client": "^6.8.8",
|
"openid-client": "^6.8.8",
|
||||||
"react": "19.2.8",
|
"react": "19.2.8",
|
||||||
@@ -56,7 +57,7 @@
|
|||||||
"dotenv": "^17.4.2",
|
"dotenv": "^17.4.2",
|
||||||
"esbuild": "^0.28.2",
|
"esbuild": "^0.28.2",
|
||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "^16.4.0",
|
"eslint-config-next": "^16.4.2",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"prettier": "^3.9.7",
|
"prettier": "^3.9.7",
|
||||||
"prisma": "^7.10.0",
|
"prisma": "^7.10.0",
|
||||||
|
|||||||
@@ -0,0 +1,210 @@
|
|||||||
|
/**
|
||||||
|
* Legt zu jedem Git-Tag eine Release in Gitea an – mit dem passenden Abschnitt aus dem CHANGELOG
|
||||||
|
* als Beschreibung.
|
||||||
|
*
|
||||||
|
* npm run releases:sync -- --dry-run # zeigt nur, was passieren würde
|
||||||
|
* npm run releases:sync # legt an und aktualisiert
|
||||||
|
* npm run releases:sync -- v1.0.0 # nur diese eine Fassung
|
||||||
|
*
|
||||||
|
* Quelle der Wahrheit bleibt das CHANGELOG: Ein zweites Mal aufgerufen, wird eine vorhandene
|
||||||
|
* Release nur aktualisiert, wenn sich der Text geändert hat.
|
||||||
|
*
|
||||||
|
* Der Token braucht das Recht **write:repository** – `issue:write` allein genügt nicht. Er wird
|
||||||
|
* aus `GITEA_TOKEN` gelesen, sonst aus den Git-Zugangsdaten (#047).
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { execFileSync } from "node:child_process";
|
||||||
|
import { readFile } from "node:fs/promises";
|
||||||
|
import path from "node:path";
|
||||||
|
import "dotenv/config";
|
||||||
|
|
||||||
|
interface Fassung {
|
||||||
|
tag: string;
|
||||||
|
/** Überschrift ohne Klammern, z. B. „1.0.0 – 2026-10-23“. */
|
||||||
|
titel: string;
|
||||||
|
text: string;
|
||||||
|
datum: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Release {
|
||||||
|
id: number;
|
||||||
|
tag_name: string;
|
||||||
|
name: string;
|
||||||
|
body: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Zerlegt das CHANGELOG in seine Abschnitte je Fassung. */
|
||||||
|
export function changelogZerlegen(inhalt: string): Fassung[] {
|
||||||
|
const zeilen = inhalt.split("\n");
|
||||||
|
const fassungen: Fassung[] = [];
|
||||||
|
let aktuell: Fassung | null = null;
|
||||||
|
|
||||||
|
for (const zeile of zeilen) {
|
||||||
|
// Überschriften der Form „## [1.0.0] – 2026-10-23“ (Keep a Changelog).
|
||||||
|
const kopf = /^##\s*\[([^\]]+)\]\s*[–-]?\s*(.*)$/.exec(zeile);
|
||||||
|
if (kopf) {
|
||||||
|
if (aktuell) fassungen.push(aktuell);
|
||||||
|
const nummer = kopf[1].trim();
|
||||||
|
const datum = kopf[2].trim() || null;
|
||||||
|
// „Unreleased“ hat kein Tag und gehört nicht als Release nach Gitea.
|
||||||
|
aktuell = /^\d/.test(nummer)
|
||||||
|
? { tag: `v${nummer}`, titel: datum ? `${nummer} – ${datum}` : nummer, text: "", datum }
|
||||||
|
: null;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (aktuell) aktuell.text += `${zeile}\n`;
|
||||||
|
}
|
||||||
|
if (aktuell) fassungen.push(aktuell);
|
||||||
|
|
||||||
|
return fassungen.map((fassung) => ({ ...fassung, text: fassung.text.trim() }));
|
||||||
|
}
|
||||||
|
|
||||||
|
function vorhandeneTags(): Set<string> {
|
||||||
|
const ausgabe = execFileSync("git", ["tag", "--list", "v*"], { encoding: "utf8" });
|
||||||
|
return new Set(ausgabe.split("\n").map((zeile) => zeile.trim()).filter(Boolean));
|
||||||
|
}
|
||||||
|
|
||||||
|
function ausRemote(): { basis: string; repo: string } | null {
|
||||||
|
try {
|
||||||
|
const url = execFileSync("git", ["remote", "get-url", "origin"], { encoding: "utf8" }).trim();
|
||||||
|
const treffer = /^https?:\/\/(?:[^@/]*@)?([^/]+)\/(.+?)(?:\.git)?$/.exec(url);
|
||||||
|
if (!treffer) return null;
|
||||||
|
return { basis: `https://${treffer[1]}`, repo: treffer[2] };
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Wie beim Issue-Abgleich: ein Token für Push und API, kein zweiter Eintrag in der .env. */
|
||||||
|
function tokenAusGitCredentials(basis: string): string | null {
|
||||||
|
const host = new URL(basis).host;
|
||||||
|
|
||||||
|
for (const zusatz of [[], ["-c", "credential.helper=store"]]) {
|
||||||
|
try {
|
||||||
|
const antwort = execFileSync("git", [...zusatz, "credential", "fill"], {
|
||||||
|
input: `protocol=https\nhost=${host}\n\n`,
|
||||||
|
encoding: "utf8",
|
||||||
|
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" },
|
||||||
|
stdio: ["pipe", "pipe", "ignore"],
|
||||||
|
});
|
||||||
|
const passwort = /^password=(.*)$/m.exec(antwort);
|
||||||
|
if (passwort?.[1]) return passwort[1];
|
||||||
|
} catch {
|
||||||
|
// Nächste Möglichkeit versuchen.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Gitea {
|
||||||
|
constructor(
|
||||||
|
private readonly basis: string,
|
||||||
|
private readonly repo: string,
|
||||||
|
private readonly token: string,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
private async anfrage<T>(pfad: string, optionen: RequestInit = {}): Promise<T> {
|
||||||
|
const antwort = await fetch(`${this.basis}/api/v1/repos/${this.repo}${pfad}`, {
|
||||||
|
...optionen,
|
||||||
|
headers: {
|
||||||
|
Authorization: `token ${this.token}`,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
...optionen.headers,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!antwort.ok) {
|
||||||
|
const text = await antwort.text();
|
||||||
|
const hinweis =
|
||||||
|
antwort.status === 401 || antwort.status === 403
|
||||||
|
? "\n Der Token braucht das Recht „write:repository“. Gitea: Einstellungen → Anwendungen → Token erzeugen."
|
||||||
|
: "";
|
||||||
|
throw new Error(`Gitea antwortet mit ${antwort.status} auf ${pfad}: ${text}${hinweis}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Beim Löschen kommt 204 ohne Inhalt zurück.
|
||||||
|
return antwort.status === 204 ? (undefined as T) : ((await antwort.json()) as T);
|
||||||
|
}
|
||||||
|
|
||||||
|
releases() {
|
||||||
|
return this.anfrage<Release[]>("/releases?limit=100");
|
||||||
|
}
|
||||||
|
|
||||||
|
anlegen(daten: { tag_name: string; name: string; body: string }) {
|
||||||
|
return this.anfrage<Release>("/releases", { method: "POST", body: JSON.stringify(daten) });
|
||||||
|
}
|
||||||
|
|
||||||
|
aktualisieren(id: number, daten: { name: string; body: string }) {
|
||||||
|
return this.anfrage<Release>(`/releases/${id}`, { method: "PATCH", body: JSON.stringify(daten) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const trockenlauf = process.argv.includes("--dry-run");
|
||||||
|
const nurFassung = process.argv.slice(2).find((wert) => /^v?\d+\.\d+\.\d+$/.test(wert));
|
||||||
|
|
||||||
|
const remote = ausRemote();
|
||||||
|
const basis = (process.env.GITEA_URL ?? remote?.basis ?? "").replace(/\/$/, "");
|
||||||
|
const repo = process.env.GITEA_REPO ?? remote?.repo ?? "";
|
||||||
|
|
||||||
|
if (!basis || !repo) {
|
||||||
|
console.error("Kein Gitea-Ziel gefunden. GITEA_URL und GITEA_REPO setzen oder ein Remote einrichten.");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const changelog = await readFile(path.join(process.cwd(), "CHANGELOG.md"), "utf8");
|
||||||
|
const tags = vorhandeneTags();
|
||||||
|
|
||||||
|
// Nur Fassungen, zu denen es auch ein Tag gibt – eine Release ohne Tag wäre eine Behauptung.
|
||||||
|
let fassungen = changelogZerlegen(changelog).filter((fassung) => tags.has(fassung.tag));
|
||||||
|
if (nurFassung) {
|
||||||
|
const gesucht = nurFassung.startsWith("v") ? nurFassung : `v${nurFassung}`;
|
||||||
|
fassungen = fassungen.filter((fassung) => fassung.tag === gesucht);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Ziel: ${basis}/${repo}`);
|
||||||
|
console.log(`${fassungen.length} Fassung(en) mit Tag im CHANGELOG.`);
|
||||||
|
|
||||||
|
const token = process.env.GITEA_TOKEN ?? tokenAusGitCredentials(basis);
|
||||||
|
if (trockenlauf || !token) {
|
||||||
|
if (!token && !trockenlauf) {
|
||||||
|
console.log("\nKein Token gefunden – es folgt nur ein Trockenlauf.");
|
||||||
|
console.log("Ein Token mit dem Recht „write:repository“ gehört in GITEA_TOKEN.");
|
||||||
|
}
|
||||||
|
for (const fassung of fassungen) {
|
||||||
|
console.log(` ${fassung.tag}: „${fassung.titel}“ (${fassung.text.split("\n").length} Zeilen)`);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const gitea = new Gitea(basis, repo, token);
|
||||||
|
const vorhanden = new Map((await gitea.releases()).map((release) => [release.tag_name, release]));
|
||||||
|
|
||||||
|
for (const fassung of fassungen) {
|
||||||
|
const alt = vorhanden.get(fassung.tag);
|
||||||
|
|
||||||
|
if (!alt) {
|
||||||
|
if (!trockenlauf) {
|
||||||
|
await gitea.anlegen({ tag_name: fassung.tag, name: fassung.titel, body: fassung.text });
|
||||||
|
}
|
||||||
|
console.log(`Angelegt: ${fassung.tag}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alt.body.trim() === fassung.text && alt.name === fassung.titel) {
|
||||||
|
console.log(`Unverändert: ${fassung.tag}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!trockenlauf) await gitea.aktualisieren(alt.id, { name: fassung.titel, body: fassung.text });
|
||||||
|
console.log(`Aktualisiert: ${fassung.tag}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("\nAbgleich abgeschlossen.");
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((fehler: unknown) => {
|
||||||
|
console.error(fehler instanceof Error ? fehler.message : fehler);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { changelogZerlegen } from "../scripts/gitea-releases";
|
||||||
|
|
||||||
|
/** Zerlegung des CHANGELOG in Release-Abschnitte (#046). */
|
||||||
|
|
||||||
|
const BEISPIEL = `# Changelog
|
||||||
|
|
||||||
|
Alle wesentlichen Änderungen.
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
- noch nichts
|
||||||
|
|
||||||
|
## [1.0.0] – 2026-10-23
|
||||||
|
|
||||||
|
Erste stabile Fassung.
|
||||||
|
|
||||||
|
### Hinzugefügt
|
||||||
|
- Dies und das
|
||||||
|
|
||||||
|
## [0.27.1] – 2026-10-23
|
||||||
|
|
||||||
|
### Sicherheit
|
||||||
|
- Next.js angehoben
|
||||||
|
`;
|
||||||
|
|
||||||
|
describe("CHANGELOG zerlegen", () => {
|
||||||
|
it("findet die Fassungen mit Datum", () => {
|
||||||
|
const fassungen = changelogZerlegen(BEISPIEL);
|
||||||
|
expect(fassungen.map((f) => f.tag)).toEqual(["v1.0.0", "v0.27.1"]);
|
||||||
|
expect(fassungen[0].titel).toBe("1.0.0 – 2026-10-23");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("übergeht „Unreleased“ – dazu gibt es kein Tag", () => {
|
||||||
|
expect(changelogZerlegen(BEISPIEL).some((f) => f.tag.includes("Unreleased"))).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("nimmt den Text bis zur nächsten Überschrift mit", () => {
|
||||||
|
const [eins] = changelogZerlegen(BEISPIEL);
|
||||||
|
expect(eins.text).toContain("Erste stabile Fassung.");
|
||||||
|
expect(eins.text).toContain("### Hinzugefügt");
|
||||||
|
expect(eins.text).not.toContain("0.27.1");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("schneidet Leerzeilen an den Rändern ab", () => {
|
||||||
|
const [eins] = changelogZerlegen(BEISPIEL);
|
||||||
|
expect(eins.text.startsWith("Erste")).toBe(true);
|
||||||
|
expect(eins.text.endsWith("Dies und das")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("kommt mit einem Changelog ohne Fassungen zurecht", () => {
|
||||||
|
expect(changelogZerlegen("# Changelog\n\nNoch nichts veröffentlicht.\n")).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user