Types/OpenPackage.Publisher/GitHubRelease.ps1

<#
.SYNOPSIS
    Publishes Releases to GitHub
.DESCRIPTION
    Publishes a Release to GitHub, if the version does not already exist.
#>

[CmdletBinding(SupportsShouldProcess,ConfirmImpact='High')]
param(
# The package
[Parameter(Mandatory,ValueFromPipeline)]
[IO.Packaging.Package]
$Package,

# One or more release asssets. These are files that are attached to the package.
[string[]]
$ReleaseAsset,

# The GitHub Token used to publish the release.
# If there is no GitHubEvent, this is presumed to be a Personal Access Token.
[string]
$GitHubToken = $env:GH_TOKEN,

# The GitHubOwner.
# If running in a workflow, this should be automatically detected.
# If running outside of a workflow, this must be provided.
[string]
$GitHubOwner = $env:GITHUB_OWNER,

# The GitHub Repository, in the form of `owner/repo`
# If running in a workflow, this should be automatically detected.
# If running outside of a workflow, this must be provided.
[string]
$GitHubRepository = $env:GITHUB_REPOSITORY
)

# If there is no package version, throw
if (-not $package.Version) { throw "No Package Version" }

# If the package has no repository
if (-not $package.RelationshipExists('repository')) {
    throw "Package not related to repository" # throw
}

# Get the git app (as opposed to any git functions)
$gitApp = $ExecutionContext.SessionState.InvokeCommand.GetCommand('git', 'application')
# If we could not get git
if (-not $gitApp) {
    throw "No Git" # throw.
}

# Get the repository remote url
$repositoryUrl = @(& $gitApp remote)[0] |
    ForEach-Object {
        & $gitApp remote get-url $_
    }

# and throw if we could not
if (-not $repositoryUrl) {
    throw "No Repository"
}

# Make sure this package is related to the repository
$packageGitRepo = $package.GetRelationship('repository').TargetUri
if ($packageGitRepo -ne $repositoryUrl) {
    # and throw if that is not the case.
    throw "Package unrelated to '$repositoryUrl'"    
}


# Get the github event
$gitHubEvent = if ($env:GITHUB_EVENT_PATH) {
    [IO.File]::ReadAllText($env:GITHUB_EVENT_PATH) | ConvertFrom-Json
} else { $null }

# Warn about confirmation if we do not have one
if (-not $gitHubEvent) {
    Write-Warning "No github event found, prompting for confirmation"    
} else {
    # If the event is not a merg, warn and return
    if (-not ($gitHubEvent.head_commit.message -match "Merge Pull Request #(?<PRNumber>\d+)") -and 
        (-not $gitHubEvent.psobject.properties['inputs'])) {
        "::warning::Pull Request has not merged, skipping github release" | Out-Host
        return
    }    
}

# Find the target version
$targetVersion = "v$($package.Version)"

# Go get all of our releases
$releasesURL    = "https://api.github.com/repos/$GitHubRepository/releases"

# List out our source if we are running in a workflow.
if ($gitHubEvent) {
    "Release URL: $releasesURL" | Out-Host
}

# Go get the releases.
$listOfReleases = 
    Invoke-RestMethod -Uri $releasesURL -Method Get -Headers @{
        "Accept" = "application/vnd.github.v3+json"    
        "Authorization" = 
            if ($gitHubEvent) {
                "Bearer $GitHubToken"
            } elseif ($GitHubToken) {
                "Basic $(
                    [System.Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$GitHubToken"))
                )"

            }
        
    }

# and see if ours already exists
$releaseExists = $listOfReleases | Where-Object tag_name -eq $targetVersion

# If it does
if ($releaseExists) {
    
    if ($gitHubEvent) {
        "::warning::Release '$($releaseExists.Name )' Already Exists" | Out-Host
    } else {
        Write-Warning "'$($releaseExists.Name )' Already Exists"
    }
    
    # store this to a variable so that we may potentially add assets.
    $releasedIt = $releaseExists
} else {
    # If no release exists yet,
    # prepare our parameters
    $releaseParameters = [Ordered]@{
        Uri = $releasesURL
        Method = 'POST'
        Body = [Ordered]@{
            owner = "$gitHubOwner"
            repo  = "$GitHubRepository"
            tag_name = $targetVersion
            name = "$($Package.Identifier) $($package.Version)"
            body = 
                if ($env:RELEASENOTES) {
                    $env:RELEASENOTES
                } elseif ($package.PowerShellManifest.PrivateData.PSData.ReleaseNotes) {
                    $package.PowerShellManifest.PrivateData.PSData.ReleaseNotes
                } else {
                    "$($package.Identifier) $targetVersion"
                }
            draft = 
                if ($env:RELEASEISDRAFT) { [bool]::Parse($env:RELEASEISDRAFT) } else { $false }
            prerelease = 
                if ($env:PRERELEASE) { [bool]::Parse($env:PRERELEASE) } else { $false }
        }
    }

    # If -WhatIf was passed, return the release parameters
    if ($whatIfPreference) {
        return $releaseParameters
    }
    
    # If there is no github event, prompt before releasing.
    if (-not $gitHubEvent -and -not $psCmdlet.ShouldProcess("Release $($releaseParameters.body.name)")) {
        return
    }

    # Create the release
    $releaseParameters.body = $releaseParameters.body | ConvertTo-Json -Depth 10
    $ReleaseHeaders = @{
        "Accept" = "application/vnd.github.v3+json"
        "Content-type" = "application/json"
        "Authorization" = if ($gitHubEvent) {
            "Bearer $GitHubToken"
        } else {
            "Basic $(
                [System.Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$GitHubToken"))
            )"

        }
    }
    
    $releasedIt = Invoke-RestMethod @releaseParameters -Headers $releaseHeaders
}

if (-not $releasedIt) {
    throw "Release failed"
} else {
    $releasedIt | Out-Host
}



if ($ReleaseAsset) {
    $releaseUploadUrl = $releasedIt.upload_url -replace '\{.+$'
    
    $filesToRelease = 
        Get-ChildItem -File -Path $ReleaseAsset -ErrorAction Ignore        

    $releasedFiles = @{}
    foreach ($file in $filesToRelease) {
        if ($releasedFiles[$file.Name]) {
            Write-Warning "Already attached file $($file.Name)"
            continue
        }         
        else {
            # If there is no github event, prompt before attaching.
            if (-not $gitHubEvent -and -not $psCmdlet.ShouldProcess("Attach $($file.name)")) {
                continue
            }
            $fileBytes = [IO.File]::ReadAllBytes($file.FullName)
            $releasedFiles[$file.Name] =
                Invoke-RestMethod -Uri "${releaseUploadUrl}?name=$($file.Name)" -Headers @{
                    "Accept"        = "application/vnd.github+json"                    
                    "Authorization" = if ($GitHubEvent) {
                        "Bearer $GitHubToken"
                    } else {
                        "Basic $(
                            [System.Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$GitHubToken"))
                        )"

                    }
                } -Body $fileBytes -ContentType Application/octet-stream
            $releasedFiles[$file.Name]
        }
    }

    if ($gitHubEvent) {
        "Attached $($releasedFiles.Count) file(s) to release" | Out-Host
    }    
}