tasks/GitHubRelease.functions.ps1

# This should not be in this folder but submitted up to PowerShellForGitHub
# That's the goal, but I needed to get moving here first.
#
# Adapted from https://github.com/PowerShell/vscode-powershell/blob/master/tools/GitHubTools.psm1
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
function Publish-GitHubRelease {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]
        $Owner,

        [Parameter(Mandatory)]
        [string]
        $Repository,

        [Parameter(Mandatory)]
        [string]
        $Tag,

        [Parameter(Mandatory)]
        [string]
        $ReleaseName,

        [Parameter(Mandatory)]
        [string]
        $Description,

        [Parameter(Mandatory)]
        [string]
        $GitHubToken,

        [Parameter()]
        [Alias('Branch', 'Commit')]
        [string]
        $Commitish,

        [Parameter()]
        [string[]]
        $AssetPath,

        [switch]
        $Draft,

        [switch]
        $Prerelease
    )

    $restParams = @{
        tag_name   = $Tag
        name       = $ReleaseName
        body       = $Description
        draft      = [bool]$Draft
        prerelease = [bool]$Prerelease
    }

    if ($Commitish) {
        $restParams.target_commitish = $Commitish
    }

    $restBody = ConvertTo-Json -InputObject $restParams
    $uri = "https://api.github.com/repos/$Owner/$Repository/releases"
    $headers = @{
        Accept        = 'application/vnd.github.v3+json'
        Authorization = "token $GitHubToken"
    }

    $response = Invoke-RestMethod -Method Post -Uri $uri -Body $restBody -Headers $headers
    $AddMemberParams = @{
        MemberType = 'NoteProperty'
        Name = 'ReleaseID'
        Value = $response.id
        TypeName = 'GitHub.Release.Created'
        ErrorAction = 'SilentlyContinue'
    }
    $response | Add-Member @AddMemberParams

    if ($AssetPath) {
        $AddGitHubAssetToRelease = @{
            Owner       = $Owner
            Repository  = $Repository
            GitHubToken = $GitHubToken
            AssetPath   = $AssetPath
            ReleaseID   = $response.id
        }
        $null = Add-GitHubAssetToRelease @AddGitHubAssetToRelease

        $GetGitHubReleaseParams = @{
            Owner       = $Owner
            Repository  = $Repository
            ReleaseID   = $response.id
            GitHubToken = $GitHubToken
        }
        $response = Get-GitHubReleaseFromReleaseID @GetGitHubReleaseParams
    }

    return $response
}

function Get-GitHubReleaseFromTagName {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [string]
        $Owner,

        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [string]
        $Repository,

        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [string]
        $Tag,

        [Parameter(Mandatory)]
        [string]
        $GitHubToken
    )

    $uri = "https://api.github.com/repos/$Owner/$Repository/releases/tags/$Tag"
    $headers = @{
        Accept        = 'application/vnd.github.v3+json'
        Authorization = "token $GitHubToken"
    }

    $response = Invoke-RestMethod -Method Get -Uri $uri -Headers $headers
    $AddMemberParams = @{
        MemberType = 'NoteProperty'
        Name = 'ReleaseID'
        Value = $response.id
        TypeName = 'GitHub.Release'
        ErrorAction = 'SilentlyContinue'
    }
    $response | Add-Member @AddMemberParams

    return $response
}

function Get-GitHubReleaseFromReleaseID {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [string]
        $Owner,

        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [string]
        $Repository,

        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [string]
        $ReleaseID,

        [Parameter(Mandatory)]
        [string]
        $GitHubToken
    )

    $uri = "https://api.github.com/repos/$Owner/$Repository/releases/$ReleaseID"
    $headers = @{
        Accept        = 'application/vnd.github.v3+json'
        Authorization = "token $GitHubToken"
    }

    $response = Invoke-RestMethod -Method Get -Uri $uri -Headers $headers
    $AddMemberParams = @{
        MemberType = 'NoteProperty'
        Name = 'ReleaseID'
        Value = $response.id
        TypeName = 'GitHub.Release'
        ErrorAction = 'SilentlyContinue'
    }
    $response | Add-Member @AddMemberParams

    return $response
}

function Set-GitHubRelease {
    [CmdletBinding(DefaultParameterSetName = 'ByTagName')]
    param(
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [string]
        $Owner,

        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [string]
        $Repository,

        [Parameter(Mandatory, ParameterSetName = 'ByTagName', ValueFromPipelineByPropertyName)]
        [string]
        $Tag,

        [Parameter(ValueFromPipelineByPropertyName)]
        [string]
        $NewTagName,

        [Parameter(Mandatory, ParameterSetName = 'ByReleaseID', ValueFromPipelineByPropertyName)]
        [string]
        $ReleaseID,

        [Parameter(ValueFromPipelineByPropertyName)]
        [string]
        $ReleaseName,

        [Parameter(ValueFromPipelineByPropertyName)]
        [string]
        $Description,

        [Parameter(Mandatory)]
        [string]
        $GitHubToken,

        [Parameter(ValueFromPipelineByPropertyName)]
        [Alias('Branch', 'Commit')]
        [string]
        $Commitish,

        [Parameter(ValueFromPipelineByPropertyName)]
        [switch]
        $Draft,

        [Parameter(ValueFromPipelineByPropertyName)]
        [switch]
        $Prerelease
    )

    if ($PSCmdlet.ParameterSetName -eq 'ByTagName') {
        $GetReleaseFromTagParams = @{
            ErrorAction = 'Stop'
            Owner       = $Owner
            Repository  = $Repository
            Tag         = $Tag
            GitHubToken = $GitHubToken
        }
        $ReleaseID = (Get-GitHubReleaseFromTagName @GetReleaseFromTagParams).id
    }


    $restParams = @{}
    switch ($PSBoundParameters.keys) {
        'NewTagName'    { $restParams.tag_name   = $NewTagName       }
        'ReleaseName'   { $restParams.name       = $ReleaseName      }
        'Description'   { $restParams.body       = $Description      }
        'Draft'         { $restParams.draft      = [bool]$Draft      }
        'Prerelease'    { $restParams.prerelease = [bool]$Prerelease }
        'Commitish'     { $restParams.target_commitish = $Commitish  }
    }

    $restBody = ConvertTo-Json -InputObject $restParams
    $uri = "https://api.github.com/repos/$Owner/$Repository/releases/$ReleaseID"
    $headers = @{
        Accept        = 'application/vnd.github.v3+json'
        Authorization = "token $GitHubToken"
    }

    $response = Invoke-RestMethod -Method Patch -Uri $uri -Body $restBody -Headers $headers
    $AddMemberParams = @{
        MemberType = 'NoteProperty'
        Name = 'ReleaseID'
        Value = $response.id
        TypeName = 'GitHub.Release'
        ErrorAction = 'SilentlyContinue'
    }
    $response | Add-Member @AddMemberParams

    return $response
}

function Add-GitHubAssetToRelease {
    [CmdletBinding(DefaultParameterSetName = 'ByTagName')]
    [OutputType([PSObject])]
    param(
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [string]
        $Owner,

        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [string]
        $Repository,

        [Parameter(Mandatory, ParameterSetName = 'ByTagName', ValueFromPipelineByPropertyName)]
        [string]
        $Tag,

        [Parameter(Mandatory, ParameterSetName = 'ByReleaseID', ValueFromPipelineByPropertyName)]
        [string]
        $ReleaseID,

        [Parameter(Mandatory)]
        [string]
        $GitHubToken,

        [Parameter()]
        [string[]]
        $AssetPath
    )

    if ($PSCmdlet.ParameterSetName -eq 'ByTagName') {
        $GetReleaseFromTagParams = @{
            ErrorAction = 'Stop'
            Owner       = $Owner
            Repository  = $Repository
            Tag         = $Tag
            GitHubToken = $GitHubToken
        }
        $ReleaseID = (Get-GitHubReleaseFromTagName @GetReleaseFromTagParams).id
    }

    $assetBaseUri = "https://uploads.github.com/repos/$Owner/$Repository/releases/$releaseId/assets"
    foreach ($asset in $AssetPath) {
        if (-Not (Split-Path -IsAbsolute $asset)) {
            $asset = Convert-Path $asset -ErrorAction Stop
        }
        $extension = [System.IO.Path]::GetExtension($asset)
        $fileName = [uri]::EscapeDataString([System.IO.Path]::GetFileName($asset))
        $contentType = 'text/plain'
        switch ($extension) {
            { $_ -in '.zip', '.vsix', 'nupkg' } {
                $contentType = 'application/zip'
                break
            }

            '.json' {
                $contentType = 'application/json'
                break
            }
        }

        $assetUri = "${assetBaseUri}?name=$fileName"
        $headers = @{
            Authorization = "token $GitHubToken"
        }
        # This can be very slow, but it does work
        $null = Invoke-RestMethod -Method Post -Uri $assetUri -InFile $asset -ContentType $contentType -Headers $headers
    }

    $GetGitHubReleaseParams = @{
        Owner       = $Owner
        Repository  = $Repository
        ReleaseID   = $ReleaseID
        GitHubToken = $GitHubToken
    }
    $response = Get-GitHubReleaseFromReleaseID @GetGitHubReleaseParams

    return $response
}

# from https://github.com/PowerShell/vscode-powershell/blob/master/tools/GitHubTools.psm1
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
filter GetHumanishRepositoryDetails {
    param(
        [string]
        $RemoteUrl
    )

    if ($RemoteUrl.EndsWith('.git')) {
        $RemoteUrl = $RemoteUrl.Substring(0, $RemoteUrl.Length - 4)
    }
    else {
        $RemoteUrl = $RemoteUrl.Trim('/')
    }

    $lastSlashIdx = $RemoteUrl.LastIndexOf('/')
    $repository = $RemoteUrl.Substring($lastSlashIdx + 1)
    $secondLastSlashIdx = $RemoteUrl.LastIndexOfAny(('/', ':'), $lastSlashIdx - 1)
    $Owner = $RemoteUrl.Substring($secondLastSlashIdx + 1, $lastSlashIdx - $secondLastSlashIdx - 1)

    return @{
        Owner      = $Owner
        Repository = $repository
    }
}

function New-GitHubPullRequest {
    param(
        [Parameter(Mandatory)]
        [string]
        $Branch,

        [Parameter(Mandatory)]
        [string]
        $Title,

        [Parameter(Mandatory)]
        [string]
        $GitHubToken,

        [Parameter(Mandatory)]
        [string]
        $Owner,

        [Parameter(Mandatory)]
        $Repository,

        [Parameter()]
        [string]
        $TargetBranch = 'master',

        [Parameter()]
        [string]
        $Description = '',

        [Parameter()]
        [string]
        $FromOwner
    )

    $uri = "https://api.github.com/repos/$Owner/$Repository/pulls"

    if ($FromOwner -and $FromOwner -ne $Owner) {
        $Branch = "${FromOwner}:${Branch}"
    }

    $body = @{
        title                 = $Title
        body                  = $Description
        head                  = $Branch
        base                  = $TargetBranch
        maintainer_can_modify = $true
    } | ConvertTo-Json

    $headers = @{
        Accept        = 'application/vnd.github.v3+json'
        Authorization = "token $GitHubToken"
    }

    Write-Verbose "Opening new GitHub pull request on '$Owner/$Repository' with title '$Title'"
    $response = Invoke-RestMethod -Method Post -Uri $uri -Body $body -Headers $headers

    return $response
}