utilities/tools/Get-FormattedGitHubRelease.ps1 (155 lines of code) (raw):

<# .SYNOPSIS Get a formatted list of all Pull-Request titles that contributed to a given release. .DESCRIPTION Get a formatted list of all Pull-Request titles that contributed to a given release. Pull Request titles should have the format '[<Category>] <Title>'. Any Pull Request that does not followed this format is printed to the invoking terminal with a warning. .PARAMETER TargetReleaseTag Mandatory. The intended release tag. This Tag must be newer than the latest in the target repository. For example 'v1.0.0'. .PARAMETER PersonalAccessToken Mandatory. The Token used to fetch the Pull Request information from GitHub. .PARAMETER RepositoryOwner Optional. The owning organization of the target repository. For example 'Azure'. .PARAMETER RepositoryName Optional. The target repository name. For example 'Resource Modules'. .PARAMETER TargetBranch Optional. The target branch to fetch the Pull Request information for. For example 'main'. .PARAMETER PreviousReleaseTag Optional. The previous release tag to get the diff in Pull Requests for. Defaults to the latest release. For eaxample 'v0.0.0' .EXAMPLE Get-FormattedGitHubRelease -TargetReleaseTag 'v0.6.0' -PersonalAccessToken '<A PAT>' Get the formatted release notes for a future release with tag 'v0.6.0'. .EXAMPLE Get-FormattedGitHubRelease -TargetReleaseTag 'v1.0.0' -PreviousReleaseTag 'v0.4.0' -PersonalAccessToken '<A PAT>' Get the formatted release notes for a future release with tag 'v1.0.0' - containing all the Pull Requests in between tag 'v0.4.0' and 'v1.0.0'. #> function Get-FormattedGitHubRelease { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $TargetReleaseTag, [Parameter(Mandatory = $true)] [string] $PersonalAccessToken, [Parameter(Mandatory = $false)] [string] $RepositoryOwner = 'Azure', [Parameter(Mandatory = $false)] [string] $RepositoryName = 'ResourceModules', [Parameter(Mandatory = $false)] [string] $TargetBranch = 'main', [Parameter(Mandatory = $false)] [string] $PreviousReleaseTag = '' ) # =============================== # # Get generated release notes # # =============================== # $requestInputObject = @{ Method = 'POST' Uri = "https://api.github.com/repos/$RepositoryOwner/$RepositoryName/releases/generate-notes" Headers = @{ Authorization = "Bearer $PersonalAccessToken" } Body = @{ tag_name = $TargetReleaseTag target_commitish = $TargetBranch previous_tag_name = $PreviousReleaseTag } | ConvertTo-Json } $response = Invoke-RestMethod @requestInputObject if (-not $response.Body) { Write-Error "Request failed. Reponse: [$response]" } $changedContent = $response.Body -split '\n' | Where-Object { $_ -like '`**' -and # For example: * [Modules] Update scope @carml in https://github.com/Azure/ResourceModules/pull/0 $_ -notlike '`* @*' -and # For example: @carml made their first contribution in https://github.com/Azure/ResourceModules/pull/0 $_ -notlike '`*`**' # For example: **Full Changelog**: https://github.com/Azure/ResourceModules/compare/v0.0.0...v1.0.0 } $newContributorsContent = $response.Body -split '\n' | Where-Object { $_ -like '`* @*' # For example: @carml made their first contribution in https://github.com/Azure/ResourceModules/pull/0 } # =================== # # Analyze content # # =================== # $correctlyFormatted = $changedContent | Where-Object { $_ -match '$* \[.*' } $incorrectlyFormatted = $changedContent | Where-Object { $_ -notmatch '$* \[.*' } if ($incorrectlyFormatted.Count -gt 0) { Write-Verbose '#############################' -Verbose Write-Verbose '# Incorrectly formatted #' -Verbose Write-Verbose '#############################' -Verbose Write-Verbose ($incorrectlyFormatted | Out-String) -Verbose Write-Verbose '#############################' -Verbose } # =================== # # Process content # # =================== # $categories = @() $contributors = @() foreach ($line in $correctlyFormatted) { $matchCategory = [regex]::Match($line, '\[(.+?)\].+') $categories += $matchCategory.Captures.Groups[1].Value $matchContributor = [regex]::Match($line, 'by \@(.*?)\s') $contributors += $matchContributor.Value } $foundCategories = $categories | Select-Object -Unique $foundContributors = $contributors | Where-Object { -not [String]::IsNullOrEmpty($_) } | Select-Object -Unique $newContributors = @() foreach ($line in $newContributorsContent) { $matchNewContributor = [regex]::Match($line, '\@(.*?)\s') $newContributors += $matchNewContributor.Value } $output = @() # PRs by category # # =================== # $output += '' $output += '### Highlights' $output += '' foreach ($category in $foundCategories) { $output += "***$category***" $categoryItems = $correctlyFormatted | Where-Object { $_ -imatch ".+\[$category\].+" } foreach ($categoryItem in $categoryItems) { $simplifiedItem = $categoryItem -replace "\* \[$category\]" $simplifiedItem = $simplifiedItem -replace 'by @.*', '' if ($simplifiedItem -like ':*') { $simplifiedItem = $simplifiedItem.Substring(1, ($simplifiedItem.Length - 1)) } $output += '* {0}' -f $simplifiedItem.Trim() } $output += '' } # Contributors # # ================ # $output += '' $output += '### Contributors' $output += '' $output += '| GH handle | GH name |' $output += '| :-- | :-- |' foreach ($contributor in $foundContributors) { $contributorHandle = $contributor -replace 'by @', '' $requestInputObject = @{ Method = 'GET' Uri = "https://api.github.com/users/$contributorHandle" Headers = @{ Authorization = "Bearer $PersonalAccessToken" } } try { $response = Invoke-RestMethod @requestInputObject $contributorName = $response.name $contributorNames += $contributorName + ', ' $output += ('| {0} | {1} |' -f $contributorHandle, $contributorName) } catch { Write-Error ("Failed for [$contributor]. Error: $_") } } $output += '' $output += '* CC: ' + $contributorNames # Statistics # # ================ # $output += '' $output += '### Statistics' $output += '' $output += "* Count of Merged PRs: $($changedContent.count)" $output += "* Count of Contributors: $($foundContributors.count)" $output += "* Count of New Contributors: $($newContributors.count)" $output += '' return $output }