<# .SYNOPSIS Low-level Gitea REST API wrapper (curl-based, BOM-safe). .DESCRIPTION Mirrors Invoke-GitHubApi.ps1's design: uses curl.exe so JSON bodies are written via --data-binary @file (no PowerShell Set-Content BOM, which Gitea rejects with "invalid character '\u00ef' looking for beginning of value"). Reads GITEA_URL + GITEA_TOKEN from the environment. .PARAMETER Method HTTP verb. Default GET. .PARAMETER Path Path under /api/v1 (with or without leading slash). Query params allowed. .PARAMETER BodyJson JSON body for write verbs. Validated as JSON before sending. .PARAMETER Raw Return a pscustomobject with Status, Headers, Body instead of parsed JSON. .EXAMPLE .\Invoke-GiteaApi.ps1 -Path "/version" .\Invoke-GiteaApi.ps1 -Path "/user" .\Invoke-GiteaApi.ps1 -Method POST -Path "/user/repos" -BodyJson $json #> param( [ValidateSet("GET", "POST", "PATCH", "PUT", "DELETE")] [string]$Method = "GET", [Parameter(Mandatory = $true)] [string]$Path, [string]$BodyJson, [switch]$Raw ) $ErrorActionPreference = "Stop" if ([string]::IsNullOrWhiteSpace($env:GITEA_TOKEN)) { throw "Set GITEA_TOKEN before calling the Gitea API. Store it in .env.local.ps1 (gitignored)." } if (-not (Get-Command curl.exe -ErrorAction SilentlyContinue)) { throw "curl.exe not found in PATH." } $base = if ($env:GITEA_URL) { $env:GITEA_URL.TrimEnd("/") } else { "https://gitea-hjwh0svsoo9p5w5kj2j6b1bd.urieljareth.org" } $base = "$base/api/v1" $cleanPath = $Path.TrimStart("/") $uri = "$base/$cleanPath" # Gitea accepts "token " (canonical) and "Bearer " (since 1.16+). $curlArgs = @( "-sS", "-i", "-X", $Method.ToUpper(), "-H", "Authorization: token $env:GITEA_TOKEN", "-H", "Accept: application/json", "-H", "User-Agent: gitea-skill" ) $tmpBody = $null if ($PSBoundParameters.ContainsKey("BodyJson")) { try { $null = $BodyJson | ConvertFrom-Json } catch { throw "BodyJson is not valid JSON: $($_.Exception.Message)" } $tmpBody = [System.IO.Path]::GetTempFileName() [System.IO.File]::WriteAllText($tmpBody, $BodyJson, (New-Object System.Text.UTF8Encoding($false))) $curlArgs += @("--data-binary", "@$tmpBody", "-H", "Content-Type: application/json") } try { $output = & curl.exe @curlArgs $uri $exitCode = $LASTEXITCODE if ($exitCode -ne 0) { throw "Gitea API request failed with exit code $exitCode." } $rawStr = ($output | Out-String) $parts = $rawStr -split "(?:`r`n`r`n|`n`n)", 2 $headersBlock = if ($parts.Count -ge 1) { $parts[0] } else { "" } $bodyBlock = if ($parts.Count -ge 2) { $parts[1] } else { "{}" } $statusLine = ($headersBlock -split "`n" | Select-Object -First 1).Trim() if ($statusLine -notmatch " 20[04-9] ") { $snippet = $bodyBlock.Trim() if ($snippet.Length -gt 500) { $snippet = $snippet.Substring(0, 500) + "..." } throw "Gitea API HTTP error: $statusLine`nURI: $uri`nBody: $snippet" } if ($Raw) { [pscustomobject]@{ Status = $statusLine; Headers = $headersBlock; Body = $bodyBlock } } else { try { $bodyBlock | ConvertFrom-Json } catch { $bodyBlock } } } finally { if ($tmpBody -and (Test-Path -LiteralPath $tmpBody)) { Remove-Item -LiteralPath $tmpBody -Force -ErrorAction SilentlyContinue } }