From 2af82d60d018b8ba2639c5b65c34510563725e8a Mon Sep 17 00:00:00 2001 From: Jens Reinemann Date: Tue, 19 May 2026 22:41:20 +0200 Subject: [PATCH] infra(forgejo): migrate gh-tickets scripts + skills to Forgejo API --- .github/skills/gh-tickets/SKILL.md | 95 +++++++-------- .../skills/gh-tickets/create-next-ticket.ps1 | 111 ++++++------------ .github/skills/gh-tickets/next-ticket.ps1 | 80 +++++++------ .../skills/gh-tickets/set-board-status.ps1 | 71 +++++------ .github/skills/ship/SKILL.md | 6 +- .github/skills/vps-deploy/SKILL.md | 2 +- 6 files changed, 161 insertions(+), 204 deletions(-) diff --git a/.github/skills/gh-tickets/SKILL.md b/.github/skills/gh-tickets/SKILL.md index c7e52a7..e21f6ac 100644 --- a/.github/skills/gh-tickets/SKILL.md +++ b/.github/skills/gh-tickets/SKILL.md @@ -1,11 +1,11 @@ --- name: gh-tickets -description: "Konventionen für GitHub-Issues in diesem Workspace: Aufgabentyp-Labels, Sortierung über das Project Board (Number-Feld 'Order') und CLI-Abfragen. Verwende diesen Skill immer dann, wenn Issues angelegt, abgefragt, sortiert oder vom nextstep-Router verarbeitet werden. Trigger-Phrasen: 'nächstes Ticket', 'Issue anlegen', 'Reihenfolge', 'Priorität', 'nextstep', 'Order', 'Board'." +description: "Konventionen für Forgejo-Issues in diesem Workspace: Aufgabentyp-Labels, Sortierung über status:todo-Labels und CLI-Abfragen. Verwende diesen Skill immer dann, wenn Issues angelegt, abgefragt, sortiert oder vom nextstep-Router verarbeitet werden. Trigger-Phrasen: 'nächstes Ticket', 'Issue anlegen', 'Reihenfolge', 'Priorität', 'nextstep', 'Status', 'Board'." --- -# Skill: GitHub Tickets (gh-tickets) +# Skill: Forgejo Tickets (gh-tickets) -Dieses Dokument definiert die verbindlichen Konventionen für GitHub-Issues im Repository `jreinemann-euris/bollwerk`. +Dieses Dokument definiert die verbindlichen Konventionen für Issues im Forgejo-Repository `bollwerkadmin/bollwerk` (`https://git.bollwerk.online`). --- @@ -49,83 +49,69 @@ Stoppe und fordere den User zur Zuordnung auf. Starte den Workflow **nicht** ohn --- -## 2. Sortierung über Project Board +## 2. Sortierung über Status-Labels -Die Abarbeitungsreihenfolge wird über das **Project Board** gesteuert, nicht über Labels. +Die Abarbeitungsreihenfolge wird über **Status-Labels** in Forgejo gesteuert. -### Board-Daten +### Status-Labels -| Eigenschaft | Wert | -| ------------ | ---------------------------------------------------- | -| Projekt-Name | `Bollwerk` | -| Projekt-Nr | `2` | -| Owner | `jreinemann-euris` | -| Sortierfeld | `Order` (Number-Feld) | -| URL | https://github.com/users/jreinemann-euris/projects/2 | +| Label | Bedeutung | +| ------------------- | ------------------------------------------------- | +| `status:todo` | Nächste offene Aufgabe (wird von next-ticket.ps1 gefunden) | +| `status:in-progress`| Aktuell in Bearbeitung | +| `status:done` | Abgeschlossen | +| `status:backlog` | Im Backlog – wird erst nach todo-Items bearbeitet | -### Order-Feld Konventionen +### Repository -- **Kleinere Zahl = höhere Priorität** (wird zuerst abgearbeitet) -- **Jeder Order-Wert MUSS eindeutig sein** – keine zwei Items dürfen denselben Wert haben -- Standardwerte: 10, 20, 30, 40, … (in 10er-Schritten, um Platz für Einfügungen zu lassen) -- Ein dringend vorgezogenes Ticket bekommt einen Wert **zwischen** den bestehenden (z.B. 15 zwischen 10 und 20, oder 5 vor 10) -- Wenn keine bestimmte Position vorgegeben ist, erhält das Ticket den **höchsten bestehenden Order-Wert + 10** (= wird am Ende eingefügt) -- Items ohne Order-Wert sind ein **Fehler** und müssen sofort einen Order-Wert erhalten +| Eigenschaft | Wert | +| ------------ | ------------------------------------------------- | +| URL | https://git.bollwerk.online/bollwerkadmin/bollwerk | +| API-Base | https://git.bollwerk.online/api/v1 | +| Token | `.github/skills/gh-tickets/forgejo-token.txt` (lokal, nicht committed) | ### Nächstes offenes Issue ermitteln **Skript:** `.github/skills/gh-tickets/next-ticket.ps1` ```powershell -# Nächstes Ticket (priority:high zuerst, dann Order aufsteigend): +# Nächstes Ticket (priority:high zuerst, dann Issue-Nummer aufsteigend): & ".github/skills/gh-tickets/next-ticket.ps1" # Bestimmtes Issue abfragen (Typ-Label prüfen): -& ".github/skills/gh-tickets/next-ticket.ps1" -IssueNumber 98 +& ".github/skills/gh-tickets/next-ticket.ps1" -IssueNumber 128 ``` -Ausgabe: `#68 [M] CRM: Erweiterte Kundensuche (Order: 120)` +Ausgabe: `#128 [I] infra(forgejo): Projektlinks, Skills und Referenzen umstellen (Order: 128)` -### Issue zum Board hinzufügen - -```powershell -# Issue zum Board hinzufügen -gh project item-add 2 --owner jreinemann-euris --url "https://github.com/jreinemann-euris/bollwerk/issues/" - -# Order-Wert setzen (erfordert Item-ID und Field-ID) -gh project item-edit --id --field-id --project-id --number -``` - -### Board-Status aktualisieren +### Status aktualisieren **Skript:** `.github/skills/gh-tickets/set-board-status.ps1` ```powershell # Status auf "In Progress" setzen -& ".github/skills/gh-tickets/set-board-status.ps1" -IssueNumber 68 -Status InProgress +& ".github/skills/gh-tickets/set-board-status.ps1" -IssueNumber 128 -Status InProgress # Status auf "Done" setzen -& ".github/skills/gh-tickets/set-board-status.ps1" -IssueNumber 68 -Status Done +& ".github/skills/gh-tickets/set-board-status.ps1" -IssueNumber 128 -Status Done # Status auf "Todo" zurücksetzen -& ".github/skills/gh-tickets/set-board-status.ps1" -IssueNumber 68 -Status Todo +& ".github/skills/gh-tickets/set-board-status.ps1" -IssueNumber 128 -Status Todo ``` -Gültige Status-Werte: `Todo`, `InProgress`, `Done` +Gültige Status-Werte: `Todo`, `InProgress`, `Done`, `Backlog` --- ## 3. Issue anlegen (Checkliste) -Beim Anlegen eines neuen Issues **MÜSSEN alle 5 Schritte** durchgeführt werden: +Beim Anlegen eines neuen Issues **MÜSSEN alle 3 Schritte** durchgeführt werden: -1. **Titel**: Beschreibend, ggf. mit Modulpräfix (z.B. `CRM:`, `feat(core):`) -2. **Type-Label**: Genau eines von `migration`, `tech-decision`, `infrastructure`, `planning`, `test` -3. **Weitere Labels**: Optional (z.B. `crm`, `enhancement`) -4. **Board**: Issue **sofort** zum Project Board hinzufügen (ein Issue ohne Board-Eintrag existiert nicht für die Abarbeitung!) -5. **Order**: **Eindeutigen** Order-Wert setzen. Vor dem Setzen die bestehenden Order-Werte abfragen und sicherstellen, dass der gewählte Wert noch nicht vergeben ist. Ohne spezifische Positionsvorgabe: höchsten bestehenden Wert + 10 +1. **Titel**: Beschreibend, ggf. mit Modulpräfix (z.B. `feat(core):`, `infra(forgejo):`) +2. **Type-Label**: Genau eines von `migration`, `tech-decision`, `infrastructure`, `block-planning`, `feature`, `refactoring`, `planning`, `test` +3. **Status-Label**: `status:todo` (sofort bearbeiten) oder `status:backlog` (später) -> **Kein Issue ohne Board + Order!** Ein Issue ohne Board-Eintrag wird bei „nächste Aufgabe" nicht gefunden. +> **Kein Issue ohne Status-Label!** Ein Issue ohne `status:todo` oder `status:backlog` wird bei „nächste Aufgabe" nicht gefunden. ### Dringend vorgezogenes Issue anlegen ("als nächstes") @@ -134,21 +120,24 @@ Beim Anlegen eines neuen Issues **MÜSSEN alle 5 Schritte** durchgeführt werden Wenn während einer laufenden Aufgabe ein neues Issue entdeckt wird, das **direkt danach** bearbeitet werden soll: ```powershell -# Issue anlegen und als nächstes Ticket einsortieren (niedrigster Order-Wert) -& ".github/skills/gh-tickets/create-next-ticket.ps1" -Title "CRM: Bug in Kundensuche" -Labels "migration,crm" -Body "Beschreibung..." +# Issue anlegen mit status:todo (wird als nächstes gefunden, niedrigste Nummer) +& ".github/skills/gh-tickets/create-next-ticket.ps1" -Title "fix(server): Bug in Auth" -Labels "infrastructure" -Body "Beschreibung..." + +# Issue ins Backlog legen +& ".github/skills/gh-tickets/create-next-ticket.ps1" -Title "feat: Neues Feature" -Labels "feature" -Status Backlog ``` -Das Skript erledigt automatisch alle 5 Schritte: Issue anlegen, Board hinzufügen, Order auf niedrigsten Wert setzen. +Das Skript erledigt automatisch: Issue anlegen + Status-Label setzen. **Trigger-Phrasen:** „als nächstes anlegen", „nächstes Ticket erstellen", „direkt danach bearbeiten" --- -## 4. Sortierung: Priorität + Order (zweistufig) +## 4. Sortierung: Priorität + Issue-Nummer (zweistufig) Die Abarbeitungsreihenfolge wird **zweistufig** bestimmt: -1. **Primär: Label `priority:high`** → Issues mit diesem Label kommen **immer zuerst**, unabhängig vom Order-Wert -2. **Sekundär: Label `priority:low`** → Issues mit diesem Label kommen **immer zuletzt**, unabhängig vom Order-Wert -3. **Tertiär: Order-Feld** → Innerhalb derselben Prioritätsstufe sortiert der Order-Wert aufsteigend (kleinere Zahl = früher dran) +1. **Primär: Label `priority:high`** → Issues mit diesem Label kommen **immer zuerst** +2. **Sekundär: Label `priority:low`** → Issues mit diesem Label kommen **immer zuletzt** +3. **Tertiär: Issue-Nummer aufsteigend** → Niedrigere Nummer = früher erstellt = höhere Priorität -Beispiel: Ein Issue mit `priority:high` und Order 300 kommt **vor** einem Issue ohne Priorität mit Order 5. Ein normales Issue mit Order 999 kommt **vor** einem `priority:low`-Issue mit Order 1. +Beispiel: Ein Issue #50 mit `priority:high` kommt **vor** Issue #10 ohne Priorität. diff --git a/.github/skills/gh-tickets/create-next-ticket.ps1 b/.github/skills/gh-tickets/create-next-ticket.ps1 index 75c48ed..e8699ec 100644 --- a/.github/skills/gh-tickets/create-next-ticket.ps1 +++ b/.github/skills/gh-tickets/create-next-ticket.ps1 @@ -1,20 +1,21 @@ <# .SYNOPSIS - Erstellt ein neues GitHub-Issue und fügt es zum Project Board hinzu. + Erstellt ein neues Issue auf Forgejo und setzt das Status-Label. .DESCRIPTION - Legt ein Issue an, fügt es zum Project Board hinzu, setzt den Board-Status - und den Order-Wert. - - Status "Todo" (Standard): Order = niedrigster Todo-Wert minus 1 → wird als nächstes Ticket eingereiht. - - Status "Backlog": Order = höchster nicht-Done-Wert plus 10 → wird ans Ende gestellt. + Legt ein Issue an und setzt ein Status-Label (status:todo oder status:backlog). + Die Reihenfolge ergibt sich aus der Issue-Nummer (aufsteigend = ältere zuerst). + - Status "Todo" (Standard): Label status:todo → wird als nächstes Ticket gefunden. + - Status "Backlog": Label status:backlog → wird erst nach Todo-Items bearbeitet. .PARAMETER Title Titel des neuen Issues. .PARAMETER Body Body-Text des Issues (Markdown). Optional, Default: leer. .PARAMETER Labels - Komma-separierte Labels (z.B. "migration,crm,enhancement"). Muss mindestens - ein Type-Label enthalten: migration, tech-decision oder infrastructure. + Komma-separierte Labels (z.B. "infrastructure,enhancement"). Muss mindestens + ein Type-Label enthalten: migration, tech-decision, infrastructure, block-planning, + feature, refactoring, planning oder test. .PARAMETER Status - Board-Status des neuen Tickets: "Todo" (Standard) oder "Backlog". + Status des neuen Tickets: "Todo" (Standard) oder "Backlog". #> param( [Parameter(Mandatory)][string]$Title, @@ -24,13 +25,15 @@ param( [string]$Status = "Todo" ) -$repo = "jreinemann-euris/bollwerk" -$projectId = "PVT_kwHOCFqiJ84BXk9U" -$orderFieldId = "PVTF_lAHOCFqiJ84BXk9UzhSw4jo" -$statusFieldId = "PVTSSF_lAHOCFqiJ84BXk9UzhSw4es" -$statusOptionMap = @{ - "Todo" = "f75ad846" - "Backlog" = "4ce6ee37" +$forgejoBase = "https://git.bollwerk.online/api/v1" +$repo = "bollwerkadmin/bollwerk" +$tokenFile = Join-Path $PSScriptRoot "forgejo-token.txt" +$token = (Get-Content $tokenFile -Raw -ErrorAction Stop).Trim() +$headers = @{ Authorization = "token $token"; "Content-Type" = "application/json" } + +$statusLabelMap = @{ + "Todo" = "status:todo" + "Backlog" = "status:backlog" } # --- 1. Type-Label validieren --- @@ -42,65 +45,27 @@ if (-not $hasType) { exit 1 } -# --- 2. Issue anlegen --- -$createArgs = @("issue", "create", "--repo", $repo, "--title", $Title, "--label", $Labels) -if ($Body) { - $createArgs += "--body" - $createArgs += $Body -} -$issueUrl = & gh @createArgs 2>&1 -if ($LASTEXITCODE -ne 0) { - Write-Error "Fehler beim Anlegen des Issues: $issueUrl" - exit 1 +# --- 2. Alle Label-Namen zu IDs auflösen --- +$allLabels = (Invoke-WebRequest -Uri "$forgejoBase/repos/$repo/labels?limit=50" -Headers $headers -UseBasicParsing).Content | ConvertFrom-Json +$statusLabelName = $statusLabelMap[$Status] +$allRequiredLabels = $labelList + $statusLabelName + +$labelIds = @() +foreach ($name in $allRequiredLabels) { + $found = $allLabels | Where-Object { $_.name -eq $name } | Select-Object -First 1 + if ($found) { $labelIds += $found.id } + else { Write-Warning "Label '$name' nicht gefunden – wird übersprungen." } } -$issueNumber = ($issueUrl -split '/')[-1] +# --- 3. Issue anlegen --- +$issueBody = @{ title = $Title; labels = $labelIds } +if ($Body) { $issueBody["body"] = $Body } +$issueJson = $issueBody | ConvertTo-Json -# --- 3. Zum Board hinzufügen --- -$addResult = gh project item-add 2 --owner jreinemann-euris --url $issueUrl --format json 2>&1 -if ($LASTEXITCODE -ne 0) { - Write-Error "Fehler beim Hinzufügen zum Board: $addResult" - exit 1 -} -$itemId = ($addResult | ConvertFrom-Json).id +$issueResp = Invoke-WebRequest -Uri "$forgejoBase/repos/$repo/issues" -Method Post -Headers $headers -Body $issueJson -UseBasicParsing +$issue = $issueResp.Content | ConvertFrom-Json +$issueNumber = $issue.number +$issueUrl = $issue.html_url -# --- 4. Order-Wert ermitteln --- -$raw = gh project item-list 2 --owner jreinemann-euris --format json --limit 200 | ConvertFrom-Json -$otherItems = $raw.items | Where-Object { $_.status -ne "Done" -and $_.id -ne $itemId } - -if ($Status -eq "Todo") { - # Als nächstes Ticket: niedrigster bestehender Wert minus 1 - $refOrder = $otherItems | - ForEach-Object { [double]$_.order } | - Where-Object { $_ -gt 0 } | - Measure-Object -Minimum | - Select-Object -ExpandProperty Minimum - if ($null -eq $refOrder) { $refOrder = 10 } - $newOrder = $refOrder - 1 -} else { - # Backlog: ans Ende stellen (höchster bestehender Wert plus 10) - $refOrder = $otherItems | - ForEach-Object { [double]$_.order } | - Where-Object { $_ -gt 0 } | - Measure-Object -Maximum | - Select-Object -ExpandProperty Maximum - if ($null -eq $refOrder) { $refOrder = 0 } - $newOrder = $refOrder + 10 -} - -# --- 5. Order setzen --- -gh project item-edit --project-id $projectId --id $itemId --field-id $orderFieldId --number $newOrder 2>&1 | Out-Null -if ($LASTEXITCODE -ne 0) { - Write-Error "Fehler beim Setzen des Order-Werts." - exit 1 -} - -# --- 6. Board-Status setzen --- -$optionId = $statusOptionMap[$Status] -gh project item-edit --project-id $projectId --id $itemId --field-id $statusFieldId --single-select-option-id $optionId 2>&1 | Out-Null -if ($LASTEXITCODE -ne 0) { - Write-Error "Fehler beim Setzen des Board-Status." - exit 1 -} - -Write-Host "#$issueNumber $Title (Status: $Status, Order: $newOrder)" +Write-Host "#$issueNumber $Title (Status: $Status)" +Write-Host "URL: $issueUrl" diff --git a/.github/skills/gh-tickets/next-ticket.ps1 b/.github/skills/gh-tickets/next-ticket.ps1 index c68ddc6..d60df73 100644 --- a/.github/skills/gh-tickets/next-ticket.ps1 +++ b/.github/skills/gh-tickets/next-ticket.ps1 @@ -1,56 +1,58 @@ <# .SYNOPSIS - Ermittelt das nächste offene Ticket aus dem GitHub Project Board. + Ermittelt das nächste offene Ticket aus Forgejo (via status:todo Label). .DESCRIPTION - Sortierung: priority:high (0) → normal (1) → priority:low (2), dann Order aufsteigend. - Gibt Nummer, Typ-Label, Titel und Order aus. + Sortierung: priority:high (0) → normal (1) → priority:low (2), dann Issue-Nummer aufsteigend. + Gibt Nummer, Typ-Label, Titel und Order (= Issue-Nummer) aus. .PARAMETER IssueNumber - Optional: Direkt eine Issue-Nummer abfragen statt Board-Suche. + Optional: Direkt eine Issue-Nummer abfragen statt automatischer Suche. #> param([int]$IssueNumber) -$repo = "jreinemann-euris/bollwerk" +$forgejoBase = "https://git.bollwerk.online/api/v1" +$repo = "bollwerkadmin/bollwerk" +$tokenFile = Join-Path $PSScriptRoot "forgejo-token.txt" +$token = (Get-Content $tokenFile -Raw -ErrorAction Stop).Trim() +$headers = @{ Authorization = "token $token" } + +function Get-IssueType($labelNames) { + if ($labelNames -contains "block-planning") { "[B]" } + elseif ($labelNames -contains "migration") { "[M]" } + elseif ($labelNames -contains "feature") { "[F]" } + elseif ($labelNames -contains "refactoring") { "[F]" } + elseif ($labelNames -contains "tech-decision") { "[T]" } + elseif ($labelNames -contains "infrastructure") { "[I]" } + elseif ($labelNames -contains "planning") { "[P]" } + elseif ($labelNames -contains "test") { "[X]" } + else { "[?]" } +} if ($IssueNumber -gt 0) { - # Variante A: Explizite Issue-Nummer - $json = gh issue view $IssueNumber --repo $repo --json number,title,labels | ConvertFrom-Json - $labels = $json.labels | ForEach-Object { $_.name } - $type = if ($labels -contains "block-planning") { "[B]" } - elseif ($labels -contains "migration") { "[M]" } - elseif ($labels -contains "feature") { "[F]" } - elseif ($labels -contains "refactoring") { "[F]" } - elseif ($labels -contains "tech-decision") { "[T]" } - elseif ($labels -contains "infrastructure") { "[I]" } - elseif ($labels -contains "planning") { "[P]" } - elseif ($labels -contains "test") { "[X]" } - else { "[?]" } - Write-Host "#$($json.number) $type $($json.title)" + # Variante A: Explizite Issue-Nummer → Forgejo REST API + $url = "$forgejoBase/repos/$repo/issues/$IssueNumber" + $issue = (Invoke-WebRequest -Uri $url -Headers $headers -UseBasicParsing).Content | ConvertFrom-Json + $labelNames = $issue.labels | ForEach-Object { $_.name } + $type = Get-IssueType $labelNames + Write-Host "#$($issue.number) $type $($issue.title)" } else { - # Variante B: Nächstes Ticket per Board-Query (client-seitig auf Todo gefiltert) - $raw = gh project item-list 2 --owner jreinemann-euris --format json --limit 200 | ConvertFrom-Json - if ($null -eq $raw -or $null -eq $raw.items) { - Write-Host "Keine offenen Tickets gefunden." + # Variante B: Nächstes Ticket per Forgejo-Label status:todo + $url = "$forgejoBase/repos/$repo/issues?state=open&type=issues&labels=status%3Atodo&limit=50&sort=oldest" + $issues = (Invoke-WebRequest -Uri $url -Headers $headers -UseBasicParsing).Content | ConvertFrom-Json + if ($null -eq $issues -or $issues.Count -eq 0) { + Write-Host "Keine offenen Tickets mit 'status:todo' gefunden." return } - $todos = $raw.items | Where-Object { $_.status -eq "Todo" } | ForEach-Object { - $labels = $_.labels - $prio = if ($labels -contains "priority:high") { 0 } - elseif ($labels -contains "priority:low") { 2 } - else { 1 } - $type = if ($labels -contains "block-planning") { "[B]" } - elseif ($labels -contains "migration") { "[M]" } - elseif ($labels -contains "feature") { "[F]" } - elseif ($labels -contains "refactoring") { "[F]" } - elseif ($labels -contains "tech-decision") { "[T]" } - elseif ($labels -contains "infrastructure") { "[I]" } - elseif ($labels -contains "planning") { "[P]" } - elseif ($labels -contains "test") { "[X]" } - else { "[?]" } + $todos = $issues | ForEach-Object { + $labelNames = $_.labels | ForEach-Object { $_.name } + $prio = if ($labelNames -contains "priority:high") { 0 } + elseif ($labelNames -contains "priority:low") { 2 } + else { 1 } + $type = Get-IssueType $labelNames [PSCustomObject]@{ - Number = $_.content.number - Title = $_.content.title - Order = if ($null -ne $_.order) { $_.order } else { 999999 } + Number = $_.number + Title = $_.title + Order = $_.number # Issue-Nummer als Tiebreaker (aufsteigend = älteste zuerst) Prio = $prio Type = $type } diff --git a/.github/skills/gh-tickets/set-board-status.ps1 b/.github/skills/gh-tickets/set-board-status.ps1 index a3f8066..7bcad2e 100644 --- a/.github/skills/gh-tickets/set-board-status.ps1 +++ b/.github/skills/gh-tickets/set-board-status.ps1 @@ -1,61 +1,62 @@ <# .SYNOPSIS - Setzt den Board-Status eines Issues im GitHub Project Board. + Setzt den Status eines Issues in Forgejo via Label (status:todo / status:in-progress / status:done / status:backlog). .DESCRIPTION - Ermittelt die Item-ID des Issues im Board und setzt den Status - auf den angegebenen Wert. + Entfernt alle status:*-Labels und setzt das neue Status-Label. Akzeptierte Werte: "Todo", "InProgress" (oder "In Progress"), "Done", "Backlog". .PARAMETER IssueNumber Die Issue-Nummer (z.B. 68). .PARAMETER Status - Der Zielstatus: "Todo", "InProgress", "In Progress" oder "Done". + Der Zielstatus: "Todo", "InProgress", "In Progress", "Done" oder "Backlog". #> param( [Parameter(Mandatory)][int]$IssueNumber, [Parameter(Mandatory)][string]$Status ) -$projectId = "PVT_kwHOCFqiJ84BXk9U" -$statusFieldId = "PVTSSF_lAHOCFqiJ84BXk9UzhSw4es" +$forgejoBase = "https://git.bollwerk.online/api/v1" +$repo = "bollwerkadmin/bollwerk" +$tokenFile = Join-Path $PSScriptRoot "forgejo-token.txt" +$token = (Get-Content $tokenFile -Raw -ErrorAction Stop).Trim() +$headers = @{ Authorization = "token $token"; "Content-Type" = "application/json" } -# Normalisierung: "In Progress" → "InProgress" (robust gegen Agent-Varianten) +# Normalisierung $normalizedStatus = $Status.Trim() -replace '\s+', '' -# "InProgress" nach Kleinschreibung angleichen an Map-Schlüssel -$statusMap = @{ - "Todo" = "f75ad846" - "InProgress" = "47fc9ee4" - "Done" = "98236657" - "Backlog" = "4ce6ee37" + +$labelMap = @{ + "Todo" = "status:todo" + "InProgress" = "status:in-progress" + "Done" = "status:done" + "Backlog" = "status:backlog" } -if (-not $statusMap.ContainsKey($normalizedStatus)) { - $valid = $statusMap.Keys -join ", " - Write-Error "Ungültiger Status '$Status'. Gültige Werte: $valid (auch 'In Progress' für InProgress, 'Backlog' für Backlog)." +if (-not $labelMap.ContainsKey($normalizedStatus)) { + $valid = $labelMap.Keys -join ", " + Write-Error "Ungültiger Status '$Status'. Gültige Werte: $valid (auch 'In Progress' für InProgress)." exit 1 } -$optionId = $statusMap[$normalizedStatus] +$newLabel = $labelMap[$normalizedStatus] -# Item-ID im Board finden -$raw = gh project item-list 2 --owner jreinemann-euris --format json --limit 200 2>&1 -if ($LASTEXITCODE -ne 0) { - Write-Error "Fehler beim Abrufen des Project Boards: $raw" - exit 1 +# Alle Labels des Issues abrufen +$issueUrl = "$forgejoBase/repos/$repo/issues/$IssueNumber" +$issue = (Invoke-WebRequest -Uri $issueUrl -Headers $headers -UseBasicParsing).Content | ConvertFrom-Json + +# Alte status:*-Labels entfernen +$statusLabelIds = $issue.labels | Where-Object { $_.name -like "status:*" } | ForEach-Object { $_.id } +foreach ($id in $statusLabelIds) { + Invoke-WebRequest -Uri "$forgejoBase/repos/$repo/issues/$IssueNumber/labels/$id" -Method Delete -Headers $headers -UseBasicParsing | Out-Null } -$items = $raw | ConvertFrom-Json -$item = $items.items | Where-Object { $null -ne $_.content -and $_.content.number -eq $IssueNumber } | Select-Object -First 1 -if (-not $item) { - Write-Error "Issue #$IssueNumber nicht im Project Board gefunden. Bitte zuerst mit 'gh project item-add' hinzufügen." +# Neues Status-Label-ID ermitteln +$allLabels = (Invoke-WebRequest -Uri "$forgejoBase/repos/$repo/labels?limit=50" -Headers $headers -UseBasicParsing).Content | ConvertFrom-Json +$targetLabel = $allLabels | Where-Object { $_.name -eq $newLabel } | Select-Object -First 1 +if (-not $targetLabel) { + Write-Error "Label '$newLabel' nicht auf Forgejo gefunden. Bitte zuerst anlegen." exit 1 } -$itemId = $item.id +# Neues Status-Label setzen +$body = "{`"labels`":[" + $targetLabel.id + "]}" +Invoke-WebRequest -Uri "$forgejoBase/repos/$repo/issues/$IssueNumber/labels" -Method Post -Headers $headers -Body $body -UseBasicParsing | Out-Null -$editOutput = gh project item-edit --project-id $projectId --id $itemId --field-id $statusFieldId --single-select-option-id $optionId 2>&1 -if ($LASTEXITCODE -eq 0) { - Write-Host "Issue #$IssueNumber → $normalizedStatus" -} -else { - Write-Error "Fehler beim Setzen des Status für Issue #${IssueNumber}: $editOutput" - exit 1 -} +Write-Host "Issue #$IssueNumber → $normalizedStatus" diff --git a/.github/skills/ship/SKILL.md b/.github/skills/ship/SKILL.md index 8bf757e..b1bb3d7 100644 --- a/.github/skills/ship/SKILL.md +++ b/.github/skills/ship/SKILL.md @@ -1,11 +1,11 @@ --- name: ship -description: "CoPuPi-Workflow: Commit, Mac-Build-Test, Push, GitHub-Pipeline beobachten. Verwende diesen Skill für alles rund um das Ausliefern von Code – git push, Pipeline-Status, CI-Fehler beheben, Version hochzählen. Trigger-Phrasen: 'ship', 'push', 'pipeline', 'CI', 'deployen', 'ausliefern', 'version bump'." +description: "CoPuPi-Workflow: Commit, Mac-Build-Test, Push zu Forgejo. Verwende diesen Skill für alles rund um das Ausliefern von Code – git push, Version hochzählen. Trigger-Phrasen: 'ship', 'push', 'pipeline', 'CI', 'deployen', 'ausliefern', 'version bump'." --- # Skill: Ship (CoPuPi-Workflow) -Dieser Skill kapselt Tools und Konventionen für den Ship-Workflow – den Weg vom lokalen Commit bis zur grünen CI-Pipeline. +Dieser Skill kapselt Tools und Konventionen für den Ship-Workflow – den Weg vom lokalen Commit bis zum Push nach Forgejo (`git.bollwerk.online`). Der vollständige Workflow wird über `.github/prompts/ship.prompt.md` gesteuert. @@ -15,7 +15,7 @@ Der vollständige Workflow wird über `.github/prompts/ship.prompt.md` gesteuert ### `watch-pipeline.ps1` -Beobachtet den neuesten GitHub Actions Run bis zum Abschluss. Gibt Status, Dauer und Warnungen aus. +Beobachtet den neuesten Forgejo Actions Run bis zum Abschluss. Gibt Status, Dauer und Warnungen aus. **Aufruf:** diff --git a/.github/skills/vps-deploy/SKILL.md b/.github/skills/vps-deploy/SKILL.md index ce392c7..4d49b24 100644 --- a/.github/skills/vps-deploy/SKILL.md +++ b/.github/skills/vps-deploy/SKILL.md @@ -216,6 +216,6 @@ Die SQLite-Datenbank wird unter `/opt/bollwerk/data/` auf dem Host gemountet und - **1 GB RAM:** JVM-Heap auf 384 MB begrenzt. Kein Spielraum für weitere Dienste. - **Kein HTTPS:** Server läuft aktuell nur auf HTTP Port 8080. Für HTTPS → Caddy als Reverse Proxy einrichten. -- **Kein CI/CD:** Deployment ist manuell (JAR bauen → scp → docker compose up). Ggf. GitHub Actions Pipeline ergänzen. +- **Kein CI/CD:** Deployment ist manuell (JAR bauen → scp → docker compose up). Ggf. Forgejo Actions Pipeline ergänzen. - **Dockerfile lokal:** Das Dockerfile auf dem VPS (`/opt/bollwerk/Dockerfile`) ist ein schlankes Runtime-Only-Image. Das Multi-Stage-Dockerfile im Repo-Root ist für lokale Builds gedacht. - **SSH-Escape-Problem:** Beim Schreiben von Dateien via SSH-Heredoc werden JSON-Quotes zerstört. Dateien immer lokal erstellen und per `scp` hochladen.