forked from Crockan/MercuryToolbox
chore(release): prepare public source release
This commit is contained in:
@@ -0,0 +1,280 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[ValidatePattern('^v\d+\.\d+\.\d+.*$')]
|
||||
[string]$Tag,
|
||||
[string]$NotesPath,
|
||||
[string]$BaseUrl,
|
||||
[string]$Owner,
|
||||
[string]$Repo,
|
||||
[string]$ApiToken,
|
||||
[string]$DistRoot = (Join-Path (Split-Path -Parent $PSScriptRoot) 'dist')
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Set-StrictMode -Version Latest
|
||||
|
||||
function Invoke-GitCapture {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string[]]$ArgumentList
|
||||
)
|
||||
|
||||
$output = & git @ArgumentList 2>&1
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "git $($ArgumentList -join ' ') failed: $($output -join "`n")"
|
||||
}
|
||||
|
||||
return (($output | ForEach-Object { [string]$_ }) -join "`n").Trim()
|
||||
}
|
||||
|
||||
function Resolve-GiteaRepositoryContext {
|
||||
param(
|
||||
[string]$RemoteUrl
|
||||
)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($RemoteUrl)) {
|
||||
$RemoteUrl = Invoke-GitCapture -ArgumentList @('remote', 'get-url', 'origin')
|
||||
}
|
||||
|
||||
$normalized = $RemoteUrl.Trim()
|
||||
if ($normalized -match '^(https?://[^/]+)/([^/]+)/([^/]+?)(?:\.git)?$') {
|
||||
return [pscustomobject]@{
|
||||
BaseUrl = $Matches[1]
|
||||
Owner = $Matches[2]
|
||||
Repo = $Matches[3]
|
||||
}
|
||||
}
|
||||
|
||||
if ($normalized -match '^ssh://git@([^/:]+)(?::(\d+))?/([^/]+)/([^/]+?)(?:\.git)?$') {
|
||||
$scheme = 'https'
|
||||
$giteaHost = $Matches[1]
|
||||
$port = if ([string]::IsNullOrWhiteSpace($Matches[2])) { '' } else { ":$($Matches[2])" }
|
||||
return [pscustomobject]@{
|
||||
BaseUrl = "${scheme}://$giteaHost$port"
|
||||
Owner = $Matches[3]
|
||||
Repo = $Matches[4]
|
||||
}
|
||||
}
|
||||
|
||||
throw "Could not parse Gitea origin remote URL: $normalized"
|
||||
}
|
||||
|
||||
function Resolve-ApiToken {
|
||||
param(
|
||||
[string]$BaseUrl,
|
||||
[string]$Owner,
|
||||
[string]$Repo,
|
||||
[string]$ExplicitToken
|
||||
)
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($ExplicitToken)) {
|
||||
return $ExplicitToken
|
||||
}
|
||||
|
||||
foreach ($name in @('GITEA_API_TOKEN', 'GITEA_TOKEN', 'GITHUB_TOKEN')) {
|
||||
$value = [Environment]::GetEnvironmentVariable($name)
|
||||
if (-not [string]::IsNullOrWhiteSpace($value)) {
|
||||
return $value
|
||||
}
|
||||
}
|
||||
|
||||
$uri = [Uri]$BaseUrl
|
||||
$lines = @(
|
||||
'protocol=https'
|
||||
"host=$($uri.Authority)"
|
||||
"path=$Owner/$Repo.git"
|
||||
''
|
||||
) | git credential fill
|
||||
$passwordLine = $lines | Where-Object { $_ -like 'password=*' } | Select-Object -First 1
|
||||
if ($null -eq $passwordLine) {
|
||||
throw 'Could not resolve a Gitea API token from the current environment or git credential manager.'
|
||||
}
|
||||
|
||||
return $passwordLine.Substring(9)
|
||||
}
|
||||
|
||||
function Invoke-GiteaApi {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Method,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Uri,
|
||||
[string]$Token,
|
||||
[object]$Body
|
||||
)
|
||||
|
||||
$headers = @{
|
||||
Authorization = "token $Token"
|
||||
}
|
||||
|
||||
$invokeArgs = @{
|
||||
Method = $Method
|
||||
Uri = $Uri
|
||||
Headers = $headers
|
||||
ErrorAction = 'Stop'
|
||||
}
|
||||
|
||||
if ($PSBoundParameters.ContainsKey('Body')) {
|
||||
$invokeArgs.ContentType = 'application/json'
|
||||
$invokeArgs.Body = ($Body | ConvertTo-Json -Depth 10)
|
||||
}
|
||||
|
||||
try {
|
||||
return Invoke-RestMethod @invokeArgs
|
||||
}
|
||||
catch {
|
||||
$response = $_.Exception.Response
|
||||
if ($null -eq $response) {
|
||||
throw
|
||||
}
|
||||
$statusCode = [int]$response.StatusCode
|
||||
$payload = ''
|
||||
if ($response -is [System.Net.Http.HttpResponseMessage]) {
|
||||
if ($null -ne $_.ErrorDetails -and -not [string]::IsNullOrWhiteSpace($_.ErrorDetails.Message)) {
|
||||
$payload = $_.ErrorDetails.Message
|
||||
}
|
||||
elseif ($null -ne $response.Content) {
|
||||
try {
|
||||
$payload = $response.Content.ReadAsStringAsync().GetAwaiter().GetResult()
|
||||
}
|
||||
catch {
|
||||
$payload = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif ($response.PSObject.Methods.Name -contains 'GetResponseStream') {
|
||||
$stream = $response.GetResponseStream()
|
||||
if ($null -ne $stream) {
|
||||
$reader = [System.IO.StreamReader]::new($stream)
|
||||
try {
|
||||
$payload = $reader.ReadToEnd()
|
||||
}
|
||||
finally {
|
||||
$reader.Dispose()
|
||||
$stream.Dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
throw "Gitea API $Method $Uri failed with HTTP ${statusCode}: $payload"
|
||||
}
|
||||
}
|
||||
|
||||
function Find-ReleaseArtifacts {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$RootPath
|
||||
)
|
||||
|
||||
if (-not (Test-Path -LiteralPath $RootPath)) {
|
||||
throw "dist root not found: $RootPath"
|
||||
}
|
||||
|
||||
$zip = Get-ChildItem -LiteralPath $RootPath -File -Filter 'MercuryToolbox-*-ReleaseFast.zip' |
|
||||
Sort-Object LastWriteTimeUtc -Descending |
|
||||
Select-Object -First 1
|
||||
if ($null -eq $zip) {
|
||||
throw "No packaged archive found under $RootPath. Run just package first."
|
||||
}
|
||||
|
||||
$packageRoot = Join-Path $RootPath $zip.BaseName
|
||||
if (-not (Test-Path -LiteralPath $packageRoot -PathType Container)) {
|
||||
throw "Package directory matching archive was not found: $packageRoot"
|
||||
}
|
||||
|
||||
$hashPath = Join-Path $packageRoot 'SHA256SUMS.txt'
|
||||
$manifestPath = Join-Path $packageRoot 'mercury-toolbox-package.json'
|
||||
foreach ($requiredPath in @($hashPath, $manifestPath)) {
|
||||
if (-not (Test-Path -LiteralPath $requiredPath -PathType Leaf)) {
|
||||
throw "Expected packaged artifact not found: $requiredPath"
|
||||
}
|
||||
}
|
||||
|
||||
return @($zip.FullName, $hashPath, $manifestPath)
|
||||
}
|
||||
|
||||
$workspaceRoot = Split-Path -Parent $PSScriptRoot
|
||||
Push-Location -LiteralPath $workspaceRoot
|
||||
|
||||
try {
|
||||
$context = Resolve-GiteaRepositoryContext
|
||||
if ([string]::IsNullOrWhiteSpace($BaseUrl)) {
|
||||
$BaseUrl = $context.BaseUrl
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($Owner)) {
|
||||
$Owner = $context.Owner
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($Repo)) {
|
||||
$Repo = $context.Repo
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($NotesPath)) {
|
||||
$NotesPath = Join-Path $workspaceRoot (Join-Path 'docs\releases' "$Tag.md")
|
||||
}
|
||||
$ApiToken = Resolve-ApiToken -BaseUrl $BaseUrl -Owner $Owner -Repo $Repo -ExplicitToken $ApiToken
|
||||
if (-not (Test-Path -LiteralPath $NotesPath -PathType Leaf)) {
|
||||
throw "Release notes not found: $NotesPath"
|
||||
}
|
||||
|
||||
$notes = Get-Content -Raw -LiteralPath $NotesPath
|
||||
$releaseName = "Mercury Toolbox $Tag"
|
||||
$apiBase = "$BaseUrl/api/v1/repos/$Owner/$Repo"
|
||||
$targetCommitish = Invoke-GitCapture -ArgumentList @('rev-parse', 'HEAD')
|
||||
|
||||
$release = $null
|
||||
try {
|
||||
$release = Invoke-GiteaApi -Method 'GET' -Uri "$apiBase/releases/tags/$Tag" -Token $ApiToken
|
||||
}
|
||||
catch {
|
||||
if (-not $_.Exception.Message.Contains('HTTP 404')) {
|
||||
throw
|
||||
}
|
||||
}
|
||||
|
||||
if ($null -eq $release) {
|
||||
$release = Invoke-GiteaApi -Method 'POST' -Uri "$apiBase/releases" -Token $ApiToken -Body @{
|
||||
tag_name = $Tag
|
||||
target_commitish = $targetCommitish
|
||||
name = $releaseName
|
||||
body = $notes
|
||||
draft = $false
|
||||
prerelease = $false
|
||||
}
|
||||
}
|
||||
else {
|
||||
$release = Invoke-GiteaApi -Method 'PATCH' -Uri "$apiBase/releases/$($release.id)" -Token $ApiToken -Body @{
|
||||
tag_name = $Tag
|
||||
target_commitish = $targetCommitish
|
||||
name = $releaseName
|
||||
body = $notes
|
||||
draft = $false
|
||||
prerelease = $false
|
||||
}
|
||||
}
|
||||
|
||||
$assets = @()
|
||||
if ($release.PSObject.Properties.Name -contains 'assets' -and $null -ne $release.assets) {
|
||||
$assets = @($release.assets)
|
||||
}
|
||||
|
||||
foreach ($assetPath in (Find-ReleaseArtifacts -RootPath $DistRoot)) {
|
||||
$assetName = [System.IO.Path]::GetFileName($assetPath)
|
||||
$existing = $assets | Where-Object { $_.name -eq $assetName } | Select-Object -First 1
|
||||
if ($null -ne $existing) {
|
||||
Invoke-GiteaApi -Method 'DELETE' -Uri "$apiBase/releases/$($release.id)/assets/$($existing.id)" -Token $ApiToken | Out-Null
|
||||
}
|
||||
|
||||
$uploadHeaders = @{
|
||||
Authorization = "token $ApiToken"
|
||||
}
|
||||
$uploadUri = "$apiBase/releases/$($release.id)/assets?name=$([Uri]::EscapeDataString($assetName))"
|
||||
Invoke-RestMethod -Method 'POST' -Uri $uploadUri -Headers $uploadHeaders -Form @{
|
||||
attachment = Get-Item -LiteralPath $assetPath
|
||||
} -ErrorAction Stop | Out-Null
|
||||
Write-Host "Uploaded release asset: $assetName"
|
||||
}
|
||||
|
||||
Write-Host "Release published: $($release.html_url)"
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
}
|
||||
Reference in New Issue
Block a user