Public/Publish-PCRelease.ps1

<#
.SYNOPSIS
    Performs a full release: bump version, commit, tag, push, and create GitHub Release.
.DESCRIPTION
    Orchestrates the complete release workflow:
    1. Validates release readiness (Test-PCReleaseReady)
    2. Calculates next version (Get-PCNextVersion)
    3. Updates version in project file (Set-PCProjectVersion)
    4. Commits the version bump
    5. Creates annotated git tag
    6. Pushes commit and tag to remote
    7. Optionally creates a GitHub Release via gh CLI

    Requires: git, optionally gh (GitHub CLI) for GitHub Release creation.
.PARAMETER IncrementType
    Type of version increment: Major, Minor, or Patch.
.PARAMETER Version
    Explicit version to set (overrides IncrementType calculation).
.PARAMETER Path
    Project directory. Defaults to current directory.
.PARAMETER SkipTests
    Skip test validation during readiness check.
.PARAMETER SkipGitHubRelease
    Skip GitHub Release creation (only tag and push).
.PARAMETER DryRun
    Show what would happen without making changes.
.PARAMETER Force
    Skip interactive confirmation prompts.
.EXAMPLE
    Publish-PCRelease -IncrementType Patch
    # Bumps patch version and releases
.EXAMPLE
    Publish-PCRelease -Version "2.0.0" -Force
    # Sets explicit version and releases without confirmation
.EXAMPLE
    Publish-PCRelease -IncrementType Minor -DryRun
    # Shows what would happen without making changes
#>

function Publish-PCRelease {
    [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Increment')]
    param(
        [Parameter(Mandatory, ParameterSetName = 'Increment')]
        [ValidateSet('Major', 'Minor', 'Patch')]
        [string]$IncrementType,

        [Parameter(Mandatory, ParameterSetName = 'Explicit')]
        [string]$Version,

        [Parameter()]
        [string]$Path = (Get-Location).Path,

        [switch]$SkipTests,
        [switch]$SkipGitHubRelease,
        [switch]$DryRun,
        [switch]$Force
    )

    Push-Location $Path
    try {
        # Step 1: Validate readiness
        Write-Host "Checking release readiness..." -ForegroundColor Cyan
        $readiness = Test-PCReleaseReady -Path $Path
        
        if (-not $readiness.Pass) {
            Write-Host "`nRelease validation FAILED:" -ForegroundColor Red
            foreach ($err in $readiness.Errors) {
                Write-Host " - $err" -ForegroundColor Red
            }
            throw "Release validation failed. Fix the issues above and retry."
        }
        Write-Host " All checks passed" -ForegroundColor Green

        # Step 2: Determine target version
        $currentInfo = Get-PCProjectVersion -Path $Path
        $currentVersion = $currentInfo.Version

        if ($PSCmdlet.ParameterSetName -eq 'Increment') {
            $newVersion = Get-PCNextVersion -IncrementType $IncrementType -CurrentVersion $currentVersion
        }
        else {
            $null = Get-SemanticVersion -Version $Version  # validate format
            $newVersion = $Version -replace '^v', ''
        }

        $tagName = "v$newVersion"

        # Check if tag already exists
        $existingTag = git tag -l $tagName 2>$null
        if ($existingTag) {
            throw "Tag '$tagName' already exists. Choose a different version."
        }

        # Step 3: Confirm
        Write-Host "`nRelease Plan:" -ForegroundColor Cyan
        Write-Host " Current version: $currentVersion"
        Write-Host " New version: $newVersion"
        Write-Host " Tag: $tagName"
        Write-Host " Project: $($currentInfo.ProjectType.FileName)"
        Write-Host ""

        if ($DryRun) {
            Write-Host "[DRY RUN] Would perform:" -ForegroundColor Yellow
            Write-Host " 1. Update version in $($currentInfo.ProjectType.FileName) to $newVersion"
            Write-Host " 2. Commit: 'Release $tagName'"
            Write-Host " 3. Tag: $tagName"
            Write-Host " 4. Push commit and tag to origin"
            if (-not $SkipGitHubRelease) {
                Write-Host " 5. Create GitHub Release"
            }
            return [PSCustomObject]@{
                Status  = 'DryRun'
                Version = $newVersion
                Tag     = $tagName
            }
        }

        if (-not $Force) {
            $confirm = Read-Host "Proceed with release $tagName? [y/N]"
            if ($confirm -notin @('y', 'Y', 'yes', 'Yes')) {
                Write-Host "Release cancelled." -ForegroundColor Yellow
                return
            }
        }

        # Step 4: Bump version
        Write-Host "`nUpdating version..." -ForegroundColor Cyan
        Set-PCProjectVersion -Version $newVersion -Path $Path
        Write-Host " Version set to $newVersion in $($currentInfo.ProjectType.FileName)" -ForegroundColor Green

        # Step 5: Commit
        Write-Host "Committing..." -ForegroundColor Cyan
        git add -A
        git commit -m "Release $tagName"
        if ($LASTEXITCODE -ne 0) { throw "Git commit failed" }
        Write-Host " Committed: Release $tagName" -ForegroundColor Green

        # Step 6: Tag
        Write-Host "Creating tag..." -ForegroundColor Cyan
        git tag -a $tagName -m "Release $newVersion"
        if ($LASTEXITCODE -ne 0) { throw "Git tag failed" }
        Write-Host " Tagged: $tagName" -ForegroundColor Green

        # Step 7: Push
        Write-Host "Pushing to remote..." -ForegroundColor Cyan
        git push origin HEAD
        if ($LASTEXITCODE -ne 0) { throw "Git push failed" }
        git push origin $tagName
        if ($LASTEXITCODE -ne 0) { throw "Git push tag failed" }
        Write-Host " Pushed commit and tag" -ForegroundColor Green

        # Step 8: GitHub Release (optional)
        $ghRelease = $null
        if (-not $SkipGitHubRelease) {
            Write-Host "Creating GitHub Release..." -ForegroundColor Cyan
            $ghAvailable = Get-Command gh -ErrorAction SilentlyContinue
            if ($ghAvailable) {
                $ghArgs = @('release', 'create', $tagName, '--title', "Release $newVersion", '--generate-notes')
                gh @ghArgs
                if ($LASTEXITCODE -eq 0) {
                    Write-Host " GitHub Release created" -ForegroundColor Green
                    $ghRelease = $true
                }
                else {
                    Write-Warning "GitHub Release creation failed (exit code: $LASTEXITCODE). Tag was pushed successfully — you can create the release manually."
                }
            }
            else {
                Write-Warning "gh CLI not found. Tag was pushed — GitHub Actions workflow or manual release creation required."
            }
        }

        # Summary
        Write-Host "`nRelease $tagName complete!" -ForegroundColor Green
        
        return [PSCustomObject]@{
            Status        = 'Released'
            Version       = $newVersion
            Tag           = $tagName
            GitHubRelease = $ghRelease
            ProjectFile   = $currentInfo.ProjectType.FileName
        }
    }
    finally {
        Pop-Location
    }
}