Main.psm1

$ErrorActionPreference = "Stop"

# Entry point of the module. Functions listed here can be exported to the user if declared in manifest.

# Adds a comment to a GitHub pull request that contains links to the published output ready for review.
# If a comment that looks like a build report already exists, it is edited instead.
function New-SucceedingGitHubBuildReport() {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$organization,

        [Parameter(Mandatory = $true)]
        [string]$repository,

        [Parameter(Mandatory = $true)]
        [int]$issueNumber,

        # URL to the PDF output.
        [Parameter(Mandatory = $true)]
        [string]$pdfUrl,

        # URL to the HTML output.
        [Parameter(Mandatory = $true)]
        [string]$htmlUrl,

        # URL to the HTML output of the previous version (to compare against).
        [Parameter(Mandatory = $true)]
        [string]$oldHtmlUrl,

        # URL to the ZIP with all the output.
        [Parameter(Mandatory = $true)]
        [string]$zipUrl,

        [Parameter(Mandatory = $true)]
        [string]$authorizationToken,

        # The username of the bot is used to look for an existing build report to edit (instead of posting a new one).
        [Parameter(Mandatory = $true)]
        [string]$botUsername
    )

    $encodedHtmlUrl = [Uri]::EscapeDataString($htmlUrl)
    $encodedOldHtmlUrl = [Uri]::EscapeDataString($oldHtmlUrl)
    $diffUrl = "https://services.w3.org/htmldiff?doc1=$encodedOldHtmlUrl&doc2=$encodedHtmlUrl"

    $body = $buildReportTag + "`r`n`r`n"
    $body += "Build was successful. Outputs:`r`n`r`n"
    $body += "* [HTML document]($htmlUrl)`r`n"
    $body += "* [HTML diff with target branch]($diffUrl)`r`n"
    $body += "* [PDF document]($pdfUrl)`r`n"
    $body += "* [Download PDF + HTML as archive]($zipUrl)`r`n`r`n"

    $nowString = (Get-Date).ToString("u")
    $body += "This comment will be updated after each build. Last update was at $nowString."

    $requestBody = @{
        body = $body
    } | ConvertTo-Json

    $requestHeaders = @{
        Authorization = "token $authorizationToken"
    }

    $existingBuildReportId = FindBuildReportCommentId -organization $organization -repository $repository -issueNumber $issueNumber -authorizationToken $authorizationToken -botUsername $botUsername

    if ($existingBuildReportId) {
        $url = "https://api.github.com/repos/$organization/$repository/issues/comments/$existingBuildReportId"

        Invoke-WebRequest -Uri $url -UseBasicParsing -Method Patch -Body $requestBody -ContentType "application/json" -Headers $requestHeaders
    }
    else {
        $url = "https://api.github.com/repos/$organization/$repository/issues/$issueNumber/comments"

        Invoke-WebRequest -Uri $url -UseBasicParsing -Method Post -Body $requestBody -ContentType "application/json" -Headers $requestHeaders
    }
}

# Adds a comment to a GitHub pull request that informs the user of a failed build.
# If a comment that looks like a build report already exists, it is edited instead.
function New-FailingGitHubBuildReport() {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$organization,

        [Parameter(Mandatory = $true)]
        [string]$repository,

        [Parameter(Mandatory = $true)]
        [int]$issueNumber,

        [Parameter(Mandatory = $true)]
        [string]$authorizationToken,

        # The username of the bot is used to look for an existing build report to edit (instead of posting a new one).
        [Parameter(Mandatory = $true)]
        [string]$botUsername
    )

    $buildResultsLink = $env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI + $env:SYSTEM_TEAMPROJECT + "/_build/results?buildId=" + $env:BUILD_BUILDID

    $body = $buildReportTag + "`r`n`r`n"
    $body += "Build failed. Outputs are not available if the build fails.`r`n`r`n"

    if ($env:BIKESHEDERROR) {
        # If there was a Bikeshed error, we embed it right in the comment.
        $body += $env:BIKESHEDERROR
        $body += "`r`n`r`n"
        $body += "[Explore the build logs]($buildResultsLink) for more details.`r`n`r`n"
    }
    else {
        # Otherwise, we leave info to check the link for details and dig in logs.
        $body += "[Explore the build logs]($buildResultsLink) to determine the reason for the failure.`r`n`r`n"
    }

    $nowString = (Get-Date).ToString("u")
    $body += "This comment will be updated after each build. Last update was at $nowString."

    $requestBody = @{
        body = $body
    } | ConvertTo-Json

    $requestHeaders = @{
        Authorization = "token $authorizationToken"
    }

    $existingBuildReportId = FindBuildReportCommentId -organization $organization -repository $repository -issueNumber $issueNumber -authorizationToken $authorizationToken -botUsername $botUsername

    if ($existingBuildReportId) {
        $url = "https://api.github.com/repos/$organization/$repository/issues/comments/$existingBuildReportId"

        Invoke-WebRequest -Uri $url -UseBasicParsing -Method Patch -Body $requestBody -ContentType "application/json" -Headers $requestHeaders
    }
    else {
        $url = "https://api.github.com/repos/$organization/$repository/issues/$issueNumber/comments"

        Invoke-WebRequest -Uri $url -UseBasicParsing -Method Post -Body $requestBody -ContentType "application/json" -Headers $requestHeaders
    }
}

# Determines the correct publishing directory to name for the outputs from this build, based on
# the current branch and whether this is a PR. This only works if called form a VSTS build process.
function Get-OutputPublishDirectoryName() {
    if ($env:BUILD_REASON -eq "PullRequest") {
        return "pull/$env:SYSTEM_PULLREQUEST_PULLREQUESTNUMBER"
    }
    else {
        return $env:BUILD_SOURCEBRANCHNAME
    }
}

### Below this line is internal logic not exported to user. ###

# PowerShell defaults to TLS 1.0 which is not really accepted these days.
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

# A post by the bot user that starts with this string is considered a build report.
$buildReportTag = "# Automated build report"

function FindBuildReportCommentId() {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$organization,

        [Parameter(Mandatory = $true)]
        [string]$repository,

        [Parameter(Mandatory = $true)]
        [int]$issueNumber,

        [Parameter(Mandatory = $true)]
        [string]$authorizationToken,

        # The username of the bot is used to look for an existing build report to edit (instead of posting a new one).
        [Parameter(Mandatory = $true)]
        [string]$botUsername
    )

    # We only look on the first page (however many GitHub returns to us) for simplicity.
    # The assumption is that the first build should have made a comment pretty quickly,
    # so any existing build report should always be among the first comments.
    $url = "https://api.github.com/repos/$organization/$repository/issues/$issueNumber/comments"

    $requestHeaders = @{
        Authorization = "token $authorizationToken"
    }

    $response = Invoke-WebRequest -Uri $url -UseBasicParsing -Headers $requestHeaders
    $comments = $response.Content | ConvertFrom-Json

    $buildReport = $comments | ? { $_.user.login -eq $botUsername -and $_.body -like "$buildReportTag*" } | Select-Object -First 1

    if ($buildReport) {
        return $buildReport.id
    }
    else {
        return $null
    }
}