[CmdletBinding()] param( [string] $ServerUrl = $env:GITEA_PACKAGE_SERVER_URL, [string] $Owner = $env:GITEA_PACKAGE_OWNER, [string] $PackageName = $(if ([string]::IsNullOrWhiteSpace($env:GITEA_PACKAGE_NAME)) { 'ymhut-box-winui' } else { $env:GITEA_PACKAGE_NAME }), [string] $PackageVersion = $env:GITEA_PACKAGE_VERSION, [string[]] $ArtifactPath = @('installer_output\YMhut_Box_WinUI_Setup_*.exe'), [string] $Username = $env:GITEA_PACKAGE_USERNAME, [string] $Token = $env:GITEA_PACKAGE_TOKEN, [switch] $Overwrite ) $ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' Add-Type -AssemblyName System.Net.Http $Root = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path function Get-DefaultPackageVersion { $versionFile = Join-Path $Root 'version.json' if (-not (Test-Path -LiteralPath $versionFile)) { return '2.0.0.1' } $json = Get-Content -LiteralPath $versionFile -Raw -Encoding UTF8 | ConvertFrom-Json $version = if ($json.version) { [string]$json.version } else { '2.0.0' } $build = if ($json.build) { ([string]$json.build -replace '[^0-9]', '') } else { '1' } if ([string]::IsNullOrWhiteSpace($build)) { $build = '1' } $parts = @() foreach ($part in ($version -split '\.')) { $digits = ($part -replace '[^0-9]', '') if ($digits) { $parts += [int]$digits } } while ($parts.Count -lt 3) { $parts += 0 } $parts = $parts | Select-Object -First 3 (($parts + [int]$build) | ForEach-Object { [Math]::Min($_, 65535) }) -join '.' } function ConvertTo-PathSegment([string] $Value) { [Uri]::EscapeDataString($Value) } function Assert-PackageName([string] $Value, [string] $Label) { if ($Value -notmatch '^[A-Za-z0-9._+-]+$') { throw "$Label '$Value' is invalid for Gitea Generic Packages. Use only letters, numbers, dots, hyphens, plus signs, and underscores." } } function Invoke-GiteaPackageRequest { param( [Parameter(Mandatory = $true)] [string] $Method, [Parameter(Mandatory = $true)] [string] $Uri, [string] $FilePath ) $request = [Net.Http.HttpRequestMessage]::new([Net.Http.HttpMethod]::new($Method), $Uri) $request.Headers.Authorization = [Net.Http.Headers.AuthenticationHeaderValue]::new('Basic', $basicAuth) $stream = $null if (-not [string]::IsNullOrWhiteSpace($FilePath)) { $stream = [IO.File]::OpenRead($FilePath) $request.Content = [Net.Http.StreamContent]::new($stream) $request.Content.Headers.ContentType = [Net.Http.Headers.MediaTypeHeaderValue]::Parse('application/octet-stream') } try { $response = $httpClient.SendAsync($request).GetAwaiter().GetResult() return [int]$response.StatusCode } finally { if ($request) { $request.Dispose() } if ($stream) { $stream.Dispose() } } } if ([string]::IsNullOrWhiteSpace($ServerUrl)) { $ServerUrl = $env:GITHUB_SERVER_URL } if ([string]::IsNullOrWhiteSpace($Owner)) { $Owner = $env:GITHUB_REPOSITORY_OWNER } if ([string]::IsNullOrWhiteSpace($Owner) -and -not [string]::IsNullOrWhiteSpace($env:GITHUB_REPOSITORY)) { $Owner = ($env:GITHUB_REPOSITORY -split '/', 2)[0] } if ([string]::IsNullOrWhiteSpace($Username)) { $Username = $env:GITEA_ACTOR } if ([string]::IsNullOrWhiteSpace($Username)) { $Username = $env:GITHUB_ACTOR } if ([string]::IsNullOrWhiteSpace($PackageVersion)) { $PackageVersion = Get-DefaultPackageVersion } $ServerUrl = $ServerUrl.TrimEnd('/') $rawPackageVersion = $PackageVersion if ([string]::IsNullOrWhiteSpace($ServerUrl)) { throw 'Gitea server URL is required. Set GITEA_PACKAGE_SERVER_URL or run inside Gitea Actions.' } if ([string]::IsNullOrWhiteSpace($Owner)) { throw 'Gitea package owner is required. Set GITEA_PACKAGE_OWNER or run inside Gitea Actions.' } if ([string]::IsNullOrWhiteSpace($Username)) { throw 'Gitea package username is required. Set GITEA_PACKAGE_USERNAME or run inside Gitea Actions.' } if ([string]::IsNullOrWhiteSpace($Token)) { throw 'Gitea package token is required. Set the PACKAGE_TOKEN secret or pass -Token.' } if ([string]::IsNullOrWhiteSpace($PackageVersion)) { throw 'Gitea package version is required.' } if ($rawPackageVersion -ne $rawPackageVersion.Trim()) { throw 'Gitea package version cannot have leading or trailing whitespace.' } Assert-PackageName $PackageName 'Package name' $artifacts = @() foreach ($path in $ArtifactPath) { $resolvedPattern = if ([IO.Path]::IsPathRooted($path)) { $path } else { Join-Path $Root $path } $artifacts += @(Get-ChildItem -Path $resolvedPattern -File -ErrorAction SilentlyContinue) } $artifacts = @($artifacts | Sort-Object FullName -Unique) if ($artifacts.Count -eq 0) { throw "No artifact files matched: $($ArtifactPath -join ', ')" } foreach ($artifact in $artifacts) { Assert-PackageName $artifact.Name 'Artifact file name' } $basicAuth = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes("$Username`:$Token")) $versionUri = "$ServerUrl/api/packages/$(ConvertTo-PathSegment $Owner)/generic/$(ConvertTo-PathSegment $PackageName)/$(ConvertTo-PathSegment $PackageVersion)" $httpClient = [Net.Http.HttpClient]::new() Write-Host "Publishing Gitea Generic Package" Write-Host " Server: $ServerUrl" Write-Host " Owner: $Owner" Write-Host " Package: $PackageName" Write-Host " Version: $PackageVersion" try { if ($Overwrite) { $deleteStatus = Invoke-GiteaPackageRequest -Method 'DELETE' -Uri $versionUri if ($deleteStatus -eq 204) { Write-Host ' Existing package version deleted before upload.' } elseif ($deleteStatus -eq 404) { Write-Host ' No existing package version to delete.' } else { throw "Unexpected response while deleting existing package version: HTTP $deleteStatus" } } foreach ($artifact in $artifacts) { $fileUri = "$versionUri/$(ConvertTo-PathSegment $artifact.Name)" Write-Host " Uploading: $($artifact.Name)" $uploadStatus = Invoke-GiteaPackageRequest -Method 'PUT' -Uri $fileUri -FilePath $artifact.FullName if ($uploadStatus -eq 409 -and -not $Overwrite) { throw "Package file already exists: $($artifact.Name). Re-run with -Overwrite to delete the package version first." } if ($uploadStatus -ne 201) { throw "Unexpected response while uploading $($artifact.Name): HTTP $uploadStatus" } Write-Host " Uploaded: $fileUri" } } finally { $httpClient.Dispose() }