[CmdletBinding(SupportsShouldProcess = $true)] param( [string]$Channel = "win-x64-stable", [string]$GiteaRepoUrl = "https://git.fristy.net/fristy/rgb-led-strip-controller", [string]$Tag = "", [string]$Token = $env:VPK_TOKEN, [string]$ExpectedTag = "", [switch]$Prerelease, [switch]$Merge ) $ErrorActionPreference = "Stop" Set-StrictMode -Version Latest $repoRoot = [System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot "..\..")) if ([string]::IsNullOrWhiteSpace($Token)) { throw "Gitea token is required. Pass -Token or set the VPK_TOKEN environment variable." } function Assert-WorkspacePath { param([string]$Path) $fullPath = [System.IO.Path]::GetFullPath($Path) if (-not $fullPath.StartsWith($repoRoot, [System.StringComparison]::OrdinalIgnoreCase)) { throw "Refusing to use path outside workspace: $fullPath" } return $fullPath } function Get-GiteaReleaseTagNames { param( [string]$RepoUrl, [string]$AccessToken ) $uri = [Uri]$RepoUrl $segments = $uri.AbsolutePath.Trim("/").Split("/") | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } if ($segments.Count -lt 2) { throw "Could not parse owner/repo from GiteaRepoUrl: $RepoUrl" } $owner = $segments[-2] $repo = $segments[-1] $apiUrl = "{0}://{1}/api/v1/repos/{2}/{3}/releases?draft=false&limit=50" -f $uri.Scheme, $uri.Authority, $owner, $repo try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 $headers = @{ Authorization = "token $AccessToken" } $releases = Invoke-RestMethod -Uri $apiUrl -Method Get -Headers $headers -ErrorAction Stop } catch { throw "Gitea releases could not be read: $apiUrl`n$($_.Exception.Message)" } return @($releases | ForEach-Object { $_.tag_name } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) } $releaseDir = Assert-WorkspacePath (Join-Path $repoRoot "artifacts\velopack\$Channel") $manifestPath = Join-Path $releaseDir "releases.$Channel.json" if (-not (Test-Path -LiteralPath $releaseDir)) { throw "Release directory not found: $releaseDir" } if (-not (Test-Path -LiteralPath $manifestPath)) { throw "Release manifest not found: $manifestPath" } $localManifest = Get-Content -Raw -LiteralPath $manifestPath | ConvertFrom-Json if ($null -eq $localManifest.Assets -or $localManifest.Assets.Count -eq 0) { throw "Release manifest does not contain any assets: $manifestPath" } $packageId = $localManifest.Assets[0].PackageId $localVersions = @( $localManifest.Assets | Where-Object { $_.PackageId -eq $packageId -and -not [string]::IsNullOrWhiteSpace($_.Version) } | ForEach-Object { [Version]$_.Version } ) if ($localVersions.Count -eq 0) { throw "Could not determine local package version from: $manifestPath" } $localVersion = ($localVersions | Sort-Object -Descending | Select-Object -First 1) $tag = if ([string]::IsNullOrWhiteSpace($Tag)) { "v$($localVersion.ToString())" } else { $Tag } if (-not [string]::IsNullOrWhiteSpace($ExpectedTag) -and -not [string]::Equals($tag, $ExpectedTag, [System.StringComparison]::OrdinalIgnoreCase)) { throw "Derived tag $tag does not match expected tag $ExpectedTag. Check the pushed tag against the project version." } $setupFiles = @(Get-ChildItem -LiteralPath $releaseDir -Filter "*-Setup.exe" -File) $portableFiles = @(Get-ChildItem -LiteralPath $releaseDir -Filter "*-Portable.zip" -File) if ($setupFiles.Count -eq 0) { throw "No Setup.exe file found in release directory: $releaseDir" } if ($portableFiles.Count -eq 0) { throw "No Portable.zip file found in release directory: $releaseDir" } $existingTags = Get-GiteaReleaseTagNames -RepoUrl $GiteaRepoUrl -AccessToken $Token if ($existingTags -contains $tag -and -not $Merge.IsPresent) { throw "Gitea already contains tag $tag. Upload is blocked (pass -Merge to merge into the existing release)." } $releaseUrl = $GiteaRepoUrl.TrimEnd("/") + "/releases/tag/" + $tag $vpkArgs = @( "tool", "run", "vpk", "--", "upload", "gitea", "--outputDir", $releaseDir, "--channel", $Channel, "--repoUrl", $GiteaRepoUrl, "--token", $Token, "--tag", $tag, "--publish" ) if ($Prerelease.IsPresent) { $vpkArgs += "--pre" } if ($Merge.IsPresent) { $vpkArgs += "--merge" } if ($PSCmdlet.ShouldProcess("$GiteaRepoUrl@$tag", "Upload Velopack release")) { & "dotnet" @vpkArgs if ($LASTEXITCODE -ne 0) { throw "vpk upload failed with exit code $LASTEXITCODE" } } Write-Host "" Write-Host "Velopack feed upload complete." -ForegroundColor Green Write-Host "Version: $($localVersion.ToString())" Write-Host "Tag: $tag" Write-Host "Channel: $Channel" Write-Host "ReleaseDir: $releaseDir" Write-Host "Release: $releaseUrl" Write-Host "Prerelease: $(if ($Prerelease.IsPresent) { 'yes' } else { 'no' })"