src/GitHubReleaseManagement.ps1
|
# GitHub Release Management - Complete Smart Release Ecosystem function New-SmartRelease { <# .SYNOPSIS Creates a complete semantic release with Git tags and GitHub release using the proven Draft → Smart Tags → Publish strategy. .DESCRIPTION This is the main function for creating complete semantic releases. It combines Git tag creation with GitHub release management using a safe, proven workflow strategy: 1. 📦 Create GitHub Release as DRAFT (safe, reversible) 2. 🏷️ Create Smart Tags (only if Draft successful) 3. 🚀 Publish GitHub Release (only if Smart Tags successful) This strategy ensures that failed operations can be safely rolled back and provides comprehensive status reporting at each step. .PARAMETER TargetVersion The semantic version to create (e.g., "v1.2.3", "1.2.3"). .PARAMETER RepositoryPath Path to the Git repository. Defaults to current working directory. .PARAMETER ReleaseNotes Custom release notes content. .PARAMETER ReleaseNotesFile Path to a file containing release notes. .PARAMETER Force Force creation even if version already exists. .PARAMETER PushToRemote Push created tags to remote repository. .PARAMETER SkipGitHubRelease Only create Git tags, skip GitHub release creation. .EXAMPLE $result = New-SmartRelease -TargetVersion "v1.2.3" # Creates complete release with draft → tags → publish workflow .EXAMPLE $result = New-SmartRelease -TargetVersion "v2.0.0" -ReleaseNotes "Major release with breaking changes" Write-Host $result.GitHubSummary .OUTPUTS PSCustomObject with comprehensive release status including: - Git tag creation status - GitHub release creation status - Each step's success/failure - Rollback information - GitHub workflow integration data #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory = $true, Position = 0)] [ValidateScript({ if ($_ -match '^v?\d+\.\d+\.\d+(-(?:alpha|beta|rc|preview|pre)(?:\.\d+)?)?(\+[a-zA-Z0-9\-\.]+)?$') { $true } else { throw "TargetVersion '$_' is not a valid semantic version." } })] [string]$TargetVersion, [Parameter()] [string]$RepositoryPath = (Get-Location).Path, [Parameter()] [string]$ReleaseNotes, [Parameter()] [string]$ReleaseNotesFile, [Parameter()] [switch]$Force, [Parameter()] [switch]$PushToRemote, [Parameter()] [switch]$SkipGitHubRelease ) begin { $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() Write-SafeInfoLog -Message "Starting smart release creation" -Additional @{ TargetVersion = $TargetVersion } # Normalize version $normalizedVersion = if ($TargetVersion.StartsWith('v')) { $TargetVersion } else { "v$TargetVersion" } $isPrerelease = $normalizedVersion -match '-(alpha|beta|rc|preview|pre)' # Initialize comprehensive result object $result = [PSCustomObject]@{ TargetVersion = $normalizedVersion Success = $false # Git Tag Results GitTagsResult = $null TagsCreated = @() TagsMovedFrom = @{} TagsStaticized = @() # GitHub Release Results GitHubReleaseResult = $null ReleaseId = $null ReleaseUrl = "" ReleaseDraftCreated = $false ReleasePublished = $false # Workflow Status StepResults = @{ DraftCreation = @{ Success = $false; Message = ""; Timestamp = $null } TagCreation = @{ Success = $false; Message = ""; Timestamp = $null } ReleasePublication = @{ Success = $false; Message = ""; Timestamp = $null } } # Standard fields Duration = $null ConflictsResolved = @() RollbackInfo = @{ TagsToDelete = @() TagsToRestore = @{} ReleaseToDelete = $null OriginalState = @{} } GitHubSummary = "" StepOutputs = @{} IsPrerelease = $isPrerelease PushToRemote = $PushToRemote.IsPresent Repository = $RepositoryPath } } process { try { Push-Location $RepositoryPath # Step 1: Create GitHub Draft Release (if not skipped) if (-not $SkipGitHubRelease) { Write-SafeInfoLog -Message "Step 1: Creating GitHub draft release" -Additional @{ Version = $normalizedVersion } $result.StepResults.DraftCreation.Timestamp = Get-Date if ($PSCmdlet.ShouldProcess($normalizedVersion, "Create GitHub draft release")) { # Real execution $draftResult = New-GitHubDraftRelease -Version $normalizedVersion -ReleaseNotes $ReleaseNotes -ReleaseNotesFile $ReleaseNotesFile -RepositoryPath $RepositoryPath $result.GitHubReleaseResult = $draftResult if ($draftResult.Success) { $result.ReleaseDraftCreated = $true $result.ReleaseId = $draftResult.ReleaseId $result.ReleaseUrl = $draftResult.HtmlUrl $result.RollbackInfo.ReleaseToDelete = $draftResult.ReleaseId $result.StepResults.DraftCreation.Success = $true $result.StepResults.DraftCreation.Message = "Draft release created successfully" Write-SafeInfoLog -Message "Draft release created successfully" -Additional @{ ReleaseId = $draftResult.ReleaseId } } else { throw "Failed to create draft release: $($draftResult.ErrorMessage)" } } else { # WhatIf simulation $mockDraftResult = [PSCustomObject]@{ Success = $true ReleaseId = "simulated-draft-id-12345" HtmlUrl = "https://github.com/owner/repo/releases/tag/$normalizedVersion" IsDraft = $true IsPrerelease = $isPrerelease ErrorMessage = "" } $result.GitHubReleaseResult = $mockDraftResult $result.ReleaseDraftCreated = $true $result.ReleaseId = $mockDraftResult.ReleaseId $result.ReleaseUrl = $mockDraftResult.HtmlUrl $result.RollbackInfo.ReleaseToDelete = $mockDraftResult.ReleaseId $result.StepResults.DraftCreation.Success = $true $result.StepResults.DraftCreation.Message = "Draft release would be created successfully (WhatIf)" Write-SafeInfoLog -Message "Draft release simulation successful" -Additional @{ ReleaseId = "$($mockDraftResult.ReleaseId) (WhatIf)" } } } else { $result.StepResults.DraftCreation.Success = $true $result.StepResults.DraftCreation.Message = "Skipped (SkipGitHubRelease specified)" } # Step 2: Create Smart Tags (only if draft successful or skipped) if ($result.StepResults.DraftCreation.Success) { Write-SafeInfoLog -Message "Step 2: Creating smart tags" -Additional @{ Version = $normalizedVersion } $result.StepResults.TagCreation.Timestamp = Get-Date if ($WhatIfPreference) { # WhatIf simulation for tag creation (matches New-SemanticReleaseTags output format) $mockTagResult = [PSCustomObject]@{ Success = $true AllTags = @("$normalizedVersion", "latest", "v0.1") SmartTags = @("v0.1") MovingTags = @("latest") ReleaseTag = $normalizedVersion ErrorMessage = "" } $result.GitTagsResult = $mockTagResult $result.TagsCreated = $mockTagResult.AllTags $result.TagsMovedFrom = @{} $result.TagsStaticized = @() $result.RollbackInfo.TagsToDelete = $mockTagResult.AllTags $result.RollbackInfo.TagsToRestore = @{} $result.StepResults.TagCreation.Success = $true $result.StepResults.TagCreation.Message = "Smart tags would be created successfully (WhatIf)" Write-SafeInfoLog -Message "Smart tags simulation successful" -Additional @{ Tags = "$($mockTagResult.TagsCreated -join ', ') (WhatIf)" } } else { # Real execution - New-SemanticReleaseTags pushes tags automatically $tagResult = New-SemanticReleaseTags -TargetVersion $normalizedVersion -RepositoryPath $RepositoryPath -Force:$Force $result.GitTagsResult = $tagResult if ($tagResult.Success) { # Note: New-SemanticReleaseTags returns AllTags, SmartTags, MovingTags (not TagsCreated) $result.TagsCreated = if ($tagResult.AllTags) { @($tagResult.AllTags) } else { @() } $result.TagsMovedFrom = @{} # MovingTags are in separate property $result.TagsStaticized = @() $result.RollbackInfo.TagsToDelete = $result.TagsCreated $result.RollbackInfo.TagsToRestore = @{} $result.StepResults.TagCreation.Success = $true $result.StepResults.TagCreation.Message = "Smart tags created successfully" Write-SafeInfoLog -Message "Smart tags created successfully" -Additional @{ Tags = ($result.TagsCreated -join ', ') } } else { throw "Failed to create smart tags: $($tagResult.ErrorMessage)" } } } # Step 3: Publish GitHub Release (only if tags successful and release exists) if ($result.StepResults.TagCreation.Success -and $result.ReleaseDraftCreated) { Write-SafeInfoLog -Message "Step 3: Publishing GitHub release" -Additional @{ ReleaseId = $result.ReleaseId } $result.StepResults.ReleasePublication.Timestamp = Get-Date if ($PSCmdlet.ShouldProcess($result.ReleaseId, "Publish GitHub release")) { # Real execution $publishResult = Publish-GitHubRelease -ReleaseId $result.ReleaseId -MarkAsLatest:(-not $isPrerelease) -RepositoryPath $RepositoryPath if ($publishResult.Success) { $result.ReleasePublished = $true $result.StepResults.ReleasePublication.Success = $true $result.StepResults.ReleasePublication.Message = "Release published successfully" $result.RollbackInfo.ReleaseToDelete = $null # Don't delete published releases # Update ReleaseUrl to use the correct tag-based URL (not untagged-xxx) # After publish, the release is now associated with the actual version tag $repoInfo = gh repo view --json nameWithOwner 2>$null | ConvertFrom-Json if ($repoInfo -and $repoInfo.nameWithOwner) { $result.ReleaseUrl = "https://github.com/$($repoInfo.nameWithOwner)/releases/tag/$normalizedVersion" } Write-SafeInfoLog -Message "Release published successfully" -Additional @{ ReleaseId = $result.ReleaseId; ReleaseUrl = $result.ReleaseUrl } } else { $result.ConflictsResolved += "Release publication failed but draft and tags exist: $($publishResult.ErrorMessage)" $result.StepResults.ReleasePublication.Message = "Publication failed: $($publishResult.ErrorMessage)" } } else { # WhatIf simulation $result.ReleasePublished = $true $result.StepResults.ReleasePublication.Success = $true $result.StepResults.ReleasePublication.Message = "Release would be published successfully (WhatIf)" $result.RollbackInfo.ReleaseToDelete = $null # Don't delete published releases in simulation Write-SafeInfoLog -Message "Release publication simulation successful" -Additional @{ ReleaseId = "$($result.ReleaseId) (WhatIf)" } } } # Determine overall success $result.Success = $result.StepResults.TagCreation.Success -and ($SkipGitHubRelease -or $result.StepResults.ReleasePublication.Success) $stopwatch.Stop() $result.Duration = $stopwatch.Elapsed # Generate GitHub Summary and Step Outputs $result.GitHubSummary = New-SmartReleaseStepSummary -Result $result $result.StepOutputs = ConvertTo-SmartReleaseStepOutputs -Result $result Write-SafeInfoLog -Message "Smart release completed" -Additional @{ Success = $result.Success; Duration = "$($result.Duration.TotalSeconds)s" } } catch { $stopwatch.Stop() $result.Duration = $stopwatch.Elapsed $result.Success = $false $result.GitHubSummary = "❌ **Smart Release Failed**`n`nError: $($_.Exception.Message)`n`nSee rollback information for cleanup steps." # Rollback on failure if ($result.RollbackInfo.ReleaseToDelete) { Write-SafeWarningLog -Message "Rolling back: Deleting draft release" -Additional @{ ReleaseId = $result.RollbackInfo.ReleaseToDelete } try { Remove-GitHubRelease -ReleaseId $result.RollbackInfo.ReleaseToDelete -RepositoryPath $RepositoryPath $result.ConflictsResolved += "Rolled back: Deleted draft release $($result.RollbackInfo.ReleaseToDelete)" } catch { $result.ConflictsResolved += "Rollback failed: Could not delete draft release $($result.RollbackInfo.ReleaseToDelete)" } } Write-SafeErrorLog -Message "Smart release failed" -Additional @{ Error = $_.Exception.Message } throw } finally { Pop-Location } } end { return $result } } function New-GitHubDraftRelease { <# .SYNOPSIS Creates a GitHub Release as draft using GitHub CLI with comprehensive error handling. .DESCRIPTION Creates a GitHub release in draft mode as the first step of the proven release strategy. Draft releases are safe and reversible, allowing for validation before publication. .PARAMETER Version The semantic version for the release. .PARAMETER RepositoryPath Path to the Git repository. .PARAMETER ReleaseNotes Custom release notes content. .PARAMETER ReleaseNotesFile Path to a file containing release notes. .PARAMETER Title Custom release title. Defaults to version-based title. .EXAMPLE $result = New-GitHubDraftRelease -Version "v1.2.3" #> [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Version, [Parameter()] [string]$RepositoryPath = (Get-Location).Path, [Parameter()] [string]$ReleaseNotes, [Parameter()] [string]$ReleaseNotesFile, [Parameter()] [string]$Title ) begin { Write-SafeInfoLog -Message "Creating GitHub draft release" -Additional @{ Version = $Version } $result = [PSCustomObject]@{ Version = $Version Success = $false ReleaseId = $null HtmlUrl = "" IsDraft = $true IsPrerelease = $Version -match '-(alpha|beta|rc|preview|pre)' Title = $Title ErrorMessage = "" CreatedAt = $null GitHubSummary = "" StepOutputs = @{} } } process { try { Push-Location $RepositoryPath # Validate GitHub CLI $ghVersion = gh version 2>$null if ($LASTEXITCODE -ne 0) { throw "GitHub CLI (gh) is not available. Please install GitHub CLI and authenticate." } # Prepare release title if (-not $Title) { $Title = if ($result.IsPrerelease) { "🧪 Prerelease $Version" } else { "🚀 Release $Version" } } $result.Title = $Title # Prepare release notes $notesArgs = @() if ($ReleaseNotesFile -and (Test-Path $ReleaseNotesFile)) { $notesArgs += "--notes-file", $ReleaseNotesFile } elseif ($ReleaseNotes) { $notesArgs += "--notes", $ReleaseNotes } else { $notesArgs += "--generate-notes" } # Create draft release $ghArgs = @( "release", "create", $Version "--title", $Title "--draft" ) + $notesArgs if ($result.IsPrerelease) { $ghArgs += "--prerelease" } Write-SafeDebugLog -Message "Executing GitHub CLI" -Additional @{ Command = "gh $($ghArgs -join ' ')" } $output = & gh @ghArgs 2>&1 if ($LASTEXITCODE -eq 0) { # Extract release URL from output (gh release create outputs the URL) $releaseUrl = $output | Where-Object { $_ -match "https://github.com/.+/releases/" } | Select-Object -First 1 if ($releaseUrl) { $result.HtmlUrl = $releaseUrl.Trim() } else { $result.HtmlUrl = ($output -join "`n").Trim() } # Try to get release details via gh release view # Note: Draft releases may use "untagged-*" format, so we try both approaches $releaseDetails = $null # First try: Use the version tag directly $releaseDetails = gh release view $Version --json id,htmlUrl,isDraft,createdAt 2>$null | ConvertFrom-Json # Second try: If no details found, list all releases and find by URL if (-not $releaseDetails -or -not $releaseDetails.id) { Write-SafeDebugLog -Message "Trying to find release by URL" -Additional @{ URL = $result.HtmlUrl } $allReleases = gh release list --json tagName,isDraft,url --limit 10 2>$null | ConvertFrom-Json if ($allReleases) { # Find the draft release we just created (most recent draft) $draftRelease = $allReleases | Where-Object { $_.isDraft -eq $true } | Select-Object -First 1 if ($draftRelease) { $releaseDetails = gh release view $draftRelease.tagName --json id,htmlUrl,isDraft,createdAt 2>$null | ConvertFrom-Json } } } # Third try: Extract tag from URL if it contains "untagged-" pattern if ((-not $releaseDetails -or -not $releaseDetails.id) -and $result.HtmlUrl -match '/releases/tag/(.+)$') { $extractedTag = $Matches[1] Write-SafeDebugLog -Message "Trying extracted tag from URL" -Additional @{ Tag = $extractedTag } $releaseDetails = gh release view $extractedTag --json id,htmlUrl,isDraft,createdAt 2>$null | ConvertFrom-Json } if ($releaseDetails -and $releaseDetails.id) { $result.ReleaseId = $releaseDetails.id $result.HtmlUrl = $releaseDetails.htmlUrl $result.IsDraft = $releaseDetails.isDraft $result.CreatedAt = $releaseDetails.createdAt } else { # Fallback: Use the URL as identifier (for publish step we'll use tag name instead) Write-SafeWarningLog -Message "Could not retrieve release ID, using version as fallback" -Additional @{ Version = $Version } $result.ReleaseId = $Version # Use version/tag as fallback identifier } $result.Success = $true Write-SafeInfoLog -Message "Draft release created successfully" -Additional @{ ReleaseId = $result.ReleaseId; URL = $result.HtmlUrl } } else { $result.ErrorMessage = $output -join "`n" throw "GitHub CLI failed: $($result.ErrorMessage)" } # Generate outputs $result.GitHubSummary = "✅ **Draft Release Created**: [$Version]($($result.HtmlUrl))" $result.StepOutputs = @{ "draft-success" = "true" "release-id" = $result.ReleaseId "release-url" = $result.HtmlUrl "is-draft" = $result.IsDraft.ToString().ToLower() "is-prerelease" = $result.IsPrerelease.ToString().ToLower() } } catch { $result.Success = $false $result.ErrorMessage = $_.Exception.Message $result.GitHubSummary = "❌ **Draft Release Failed**: $($_.Exception.Message)" $result.StepOutputs = @{ "draft-success" = "false" "error-message" = $_.Exception.Message } Write-SafeErrorLog -Message "Failed to create draft release" -Additional @{ Error = $_.Exception.Message } throw } finally { Pop-Location } } end { return $result } } function Publish-GitHubRelease { <# .SYNOPSIS Publishes a GitHub draft release using GitHub CLI. .PARAMETER ReleaseId The GitHub release ID or tag name to publish. .PARAMETER RepositoryPath Path to the Git repository. .PARAMETER MarkAsLatest Mark this release as the latest release. #> [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$ReleaseId, [Parameter()] [string]$RepositoryPath = (Get-Location).Path, [Parameter()] [switch]$MarkAsLatest ) begin { Write-SafeInfoLog -Message "Publishing GitHub release" -Additional @{ ReleaseId = $ReleaseId } $result = [PSCustomObject]@{ ReleaseId = $ReleaseId Success = $false Published = $false MarkedAsLatest = $false PublishedAt = $null ErrorMessage = "" } } process { try { Push-Location $RepositoryPath # ReleaseId can be either: # 1. A GitHub internal release ID (e.g., "RE_kwDON...") # 2. A tag name (e.g., "v0.1.46") # 3. An untagged release identifier (e.g., "untagged-abc123") # Try to get release info - gh release view accepts both ID and tag name $releaseInfo = gh release view $ReleaseId --json tagName 2>$null | ConvertFrom-Json # Determine the tag name to use for editing $tagName = if ($releaseInfo -and $releaseInfo.tagName) { $releaseInfo.tagName } else { # If we can't get release info, assume ReleaseId is the tag name itself Write-SafeDebugLog -Message "Using ReleaseId as tag name" -Additional @{ ReleaseId = $ReleaseId } $ReleaseId } Write-SafeDebugLog -Message "Publishing release with tag" -Additional @{ TagName = $tagName } # Publish release if ($MarkAsLatest) { $editOutput = gh release edit $tagName --draft=false --latest 2>&1 } else { $editOutput = gh release edit $tagName --draft=false 2>&1 } if ($LASTEXITCODE -eq 0) { $result.Success = $true $result.Published = $true $result.MarkedAsLatest = $MarkAsLatest.IsPresent $result.PublishedAt = Get-Date Write-SafeInfoLog -Message "Release published successfully" -Additional @{ ReleaseId = $ReleaseId; TagName = $tagName; Latest = $MarkAsLatest.IsPresent } } else { $errorMsg = if ($editOutput) { $editOutput -join "`n" } else { "Unknown error" } throw "Failed to publish release '$tagName': $errorMsg" } } catch { $result.Success = $false $result.ErrorMessage = $_.Exception.Message Write-SafeErrorLog -Message "Failed to publish release" -Additional @{ Error = $_.Exception.Message } throw } finally { Pop-Location } } end { return $result } } function Remove-GitHubRelease { <# .SYNOPSIS Removes a GitHub release using GitHub CLI. .PARAMETER ReleaseId The GitHub release ID to remove. .PARAMETER RepositoryPath Path to the Git repository. #> [CmdletBinding()] param( [Parameter(Mandatory)] [string]$ReleaseId, [Parameter()] [string]$RepositoryPath = (Get-Location).Path ) try { Push-Location $RepositoryPath # Get release tag name $releaseInfo = gh release view $ReleaseId --json tagName 2>$null | ConvertFrom-Json if ($releaseInfo) { gh release delete $releaseInfo.tagName --yes if ($LASTEXITCODE -eq 0) { Write-SafeInfoLog -Message "Release deleted successfully" -Additional @{ ReleaseId = $ReleaseId } return $true } } Write-SafeWarningLog -Message "Failed to delete release" -Additional @{ ReleaseId = $ReleaseId } return $false } catch { Write-SafeErrorLog -Message "Error deleting release" -Additional @{ ReleaseId = $ReleaseId; Error = $_.Exception.Message } return $false } finally { Pop-Location } } # Helper functions for Smart Release workflow function New-SmartReleaseStepSummary { <# .SYNOPSIS Generates comprehensive GitHub step summary for Smart Release operations. #> [CmdletBinding()] param( [Parameter(Mandatory, ValueFromPipeline)] [PSCustomObject]$Result ) $statusIcon = if ($Result.Success) { "✅" } else { "❌" } $summary = @" ### $statusIcon **Smart Release Results** | Property | Value | |----------|-------| | **Target Version** | ``$($Result.TargetVersion)`` | | **Overall Success** | $(if($Result.Success){"✅ Yes"}else{"❌ No"}) | | **Duration** | $([math]::Round($Result.Duration.TotalSeconds, 2))s | | **Is Prerelease** | $(if($Result.IsPrerelease){"🧪 Yes"}else{"🚀 No"}) | #### 🏗️ **Workflow Steps** | Step | Status | Timestamp | Message | |------|--------|-----------|---------| $(foreach($step in $Result.StepResults.GetEnumerator()) { $status = if($step.Value.Success) {"✅"} else {"❌"} $timestamp = if($step.Value.Timestamp) {$step.Value.Timestamp.ToString("HH:mm:ss")} else {"-"} "| $($step.Key) | $status | $timestamp | $($step.Value.Message) |" }) #### 🏷️ **Git Tags Created** $(if ($Result.TagsCreated.Count -gt 0) { ($Result.TagsCreated | ForEach-Object { "- ``$_``" }) -join "`n" } else { "- *No tags created*" }) #### 🔄 **Tag Movements** $(if ($Result.TagsMovedFrom.Count -gt 0) { ($Result.TagsMovedFrom.GetEnumerator() | ForEach-Object { "- 🔄 ``$($_.Key)``: ``$($_.Value)`` → ``$($Result.TargetVersion)``" }) -join "`n" } else { "- *No tag movements*" }) $(if ($Result.ReleaseUrl) { @" #### 🚀 **GitHub Release** - **Status**: $(if($Result.ReleasePublished){"✅ Published"}elseif($Result.ReleaseDraftCreated){"📝 Draft Created"}else{"❌ Failed"}) - **URL**: [$($Result.TargetVersion)]($($Result.ReleaseUrl)) - **Release ID**: ``$($Result.ReleaseId)`` "@ }) $(if ($Result.ConflictsResolved.Count -gt 0) { @" #### ⚠️ **Issues Resolved** $(($Result.ConflictsResolved | ForEach-Object { "- ⚠️ $_" }) -join "`n") "@ }) <details> <summary>🔧 <strong>Technical Details</strong></summary> **Repository:** ``$($Result.Repository)`` **Operation:** Smart Release (Draft → Tags → Publish) **Timestamp:** $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss UTC') **Push to Remote:** $(if($Result.PushToRemote){"✅ Yes"}else{"❌ No"}) $(if ($Result.RollbackInfo.TagsToDelete.Count -gt 0 -or $Result.RollbackInfo.ReleaseToDelete) { @" **Rollback Info:** $(if($Result.RollbackInfo.TagsToDelete.Count -gt 0){"- Tags to clean up: $($Result.RollbackInfo.TagsToDelete -join ', ')"}) $(if($Result.RollbackInfo.ReleaseToDelete){"- Release to clean up: $($Result.RollbackInfo.ReleaseToDelete)"}) - Original state preserved: ✅ "@ }) </details> "@ return $summary } function ConvertTo-SmartReleaseStepOutputs { <# .SYNOPSIS Converts Smart Release results to GitHub Actions step outputs. #> [CmdletBinding()] param( [Parameter(Mandatory, ValueFromPipeline)] [PSCustomObject]$Result ) # Safely get values with null-coalescing $tagsCreated = if ($Result.TagsCreated) { $Result.TagsCreated -join ',' } else { "" } $tagsCreatedCount = if ($Result.TagsCreated) { $Result.TagsCreated.Count } else { 0 } $durationSeconds = if ($Result.Duration) { [math]::Round($Result.Duration.TotalSeconds, 2) } else { 0 } $outputs = @{ "success" = $Result.Success.ToString().ToLower() "target-version" = if ($Result.TargetVersion) { $Result.TargetVersion } else { "" } "is-prerelease" = $Result.IsPrerelease.ToString().ToLower() "duration-seconds" = $durationSeconds # Git tag outputs "tags-created" = $tagsCreated "tags-created-count" = $tagsCreatedCount # GitHub release outputs "release-draft-created" = $Result.ReleaseDraftCreated.ToString().ToLower() "release-published" = $Result.ReleasePublished.ToString().ToLower() "release-id" = if ($Result.ReleaseId) { $Result.ReleaseId } else { "" } "release-url" = if ($Result.ReleaseUrl) { $Result.ReleaseUrl } else { "" } # Workflow step statuses "draft-step-success" = $Result.StepResults.DraftCreation.Success.ToString().ToLower() "tags-step-success" = $Result.StepResults.TagCreation.Success.ToString().ToLower() "publish-step-success" = $Result.StepResults.ReleasePublication.Success.ToString().ToLower() } # Add optional outputs if ($Result.TagsMovedFrom -and $Result.TagsMovedFrom.Count -gt 0) { $outputs["tags-moved"] = ($Result.TagsMovedFrom.Keys -join ',') $outputs["tags-moved-count"] = $Result.TagsMovedFrom.Count } if ($Result.ConflictsResolved -and $Result.ConflictsResolved.Count -gt 0) { $outputs["issues-resolved"] = $Result.ConflictsResolved.Count $outputs["has-issues"] = "true" } else { $outputs["has-issues"] = "false" } return $outputs } |