diff --git a/.github/workflows/after-release.yml b/.github/workflows/after-release.yml new file mode 100644 index 0000000000..16fdbd663f --- /dev/null +++ b/.github/workflows/after-release.yml @@ -0,0 +1,159 @@ +name: after-release + +on: + release: + types: [ published ] + +permissions: {} + +env: + RELEASE_DATE: ${{ github.event.release.published_at }} + RELEASE_NOTES: ${{ github.event.release.body }} + RELEASE_URL: ${{ github.event.release.html_url }} + RELEASE_VERSION: ${{ github.event.release.tag_name }} + +jobs: + + update-changelog: + runs-on: [ ubuntu-latest ] + if: github.event_name == 'release' + + concurrency: + group: '${{ github.workflow }}-changelog' + cancel-in-progress: false + + steps: + + - name: Generate GitHub application token + id: generate-application-token + uses: peter-murray/workflow-application-token-action@dc0413987a085fa17d19df9e47d4677cf81ffef3 # v3.0.0 + with: + application_id: ${{ secrets.POLLY_UPDATER_BOT_APP_ID }} + application_private_key: ${{ secrets.POLLY_UPDATER_BOT_KEY }} + permissions: 'contents:write, pull_requests:write' + + - name: Checkout code + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + with: + token: ${{ steps.generate-application-token.outputs.token }} + + - name: Update Polly version + shell: pwsh + run: ./eng/bump-version.ps1 ${env:RELEASE_VERSION} + + - name: Update CHANGELOG + shell: pwsh + run: ./eng/update-changelog.ps1 ${env:RELEASE_VERSION} ${env:RELEASE_NOTES} ${env:GITHUB_SERVER_URL} + + - name: Update public API baselines + shell: pwsh + run: ./eng/update-baselines.ps1 + + - name: Push changes to GitHub + id: push-changes + shell: pwsh + env: + GIT_COMMIT_USER_EMAIL: '138034000+polly-updater-bot[bot]@users.noreply.github.com' + GIT_COMMIT_USER_NAME: 'polly-updater-bot[bot]' + run: | + $gitStatus = (git status --porcelain) + + if ([string]::IsNullOrEmpty($gitStatus)) { + throw "No changes to commit." + } + + git config color.diff always + git --no-pager diff + + $branchName = "changelog-${env:RELEASE_VERSION}" + + git config user.email ${env:GIT_COMMIT_USER_EMAIL} | Out-Null + git config user.name ${env:GIT_COMMIT_USER_NAME} | Out-Null + git fetch origin --no-tags | Out-Null + git rev-parse --verify --quiet "remotes/origin/${branchName}" | Out-Null + + if ($LASTEXITCODE -eq 0) { + Write-Host "Branch ${branchName} already exists." + exit 0 + } + + git checkout -b $branchName + git add . + git commit -m "Update CHANGELOG`n`nUpdate CHANGELOG and samples for v${env:RELEASE_VERSION}." + git push -u origin $branchName + + "branch-name=${branchName}" >> $env:GITHUB_OUTPUT + "updated-version=true" >> $env:GITHUB_OUTPUT + + - name: Create pull request + if: steps.push-changes.outputs.updated-version == 'true' + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + env: + BASE_BRANCH: ${{ github.event.repository.default_branch }} + HEAD_BRANCH: ${{ steps.push-changes.outputs.branch-name }} + with: + github-token: ${{ steps.generate-application-token.outputs.token }} + script: | + const version = process.env.RELEASE_VERSION; + const { repo, owner } = context.repo; + + const releaseUrl = process.env.RELEASE_URL; + const workflowUrl = `${process.env.GITHUB_SERVER_URL}/${owner}/${repo}/actions/runs/${process.env.GITHUB_RUN_ID}`; + + const { data: pr } = await github.rest.pulls.create({ + title: 'Bump version and update CHANGELOG', + owner, + repo, + head: process.env.HEAD_BRANCH, + base: process.env.BASE_BRANCH, + draft: true, + body: [ + `Update CHANGELOG and samples version for [v${version}](${releaseUrl}).`, + '', + `This pull request was generated by [GitHub Actions](${workflowUrl}).` + ].join('\n') + }); + + core.notice(`Created pull request ${owner}/${repo}#${pr.number}: ${pr.html_url}`); + + update-milestone: + runs-on: [ ubuntu-latest ] + + concurrency: + group: '${{ github.workflow }}-milestone' + cancel-in-progress: false + + permissions: + issues: write + + steps: + + - name: Close milestone + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + script: | + const { repo, owner } = context.repo; + + const { data: milestones } = await github.rest.issues.listMilestones({ + owner, + repo, + state: 'open', + }); + + const milestone = milestones.find((p) => p.title === process.env.RELEASE_VERSION); + + if (!milestone) { + return; + } + + try { + await github.rest.issues.updateMilestone({ + owner, + repo, + milestone_number: milestone.number, + state: 'closed', + due_on: process.env.RELEASE_DATE, + }); + } catch (error) { + // Ignore + } diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..eb3ad21298 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,71 @@ +name: release + +on: + workflow_dispatch: + inputs: + version: + description: 'The version to create the release for.' + required: true + type: string + +permissions: {} + +jobs: + release: + runs-on: [ ubuntu-latest ] + + concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + + permissions: + contents: write + issues: write + + steps: + + - name: Checkout code + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + + - name: Create release + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + VERSION: ${{ inputs.version }} + with: + script: | + const { repo, owner } = context.repo; + const draft = true; + let version = process.env.VERSION; + + if (version.startsWith('v')) { + version = version.slice(1); + } + + const tag_name = version; + const name = tag_name; + + const { data: notes } = await github.rest.repos.generateReleaseNotes({ + owner, + repo, + tag_name, + target_commitish: process.env.DEFAULT_BRANCH, + }); + + const body = notes.body + .split('\n') + .filter((line) => !line.includes(' @dependabot ')) + .filter((line) => !line.includes(' @github-actions ')) + .filter((line) => !line.includes(' @polly-updater-bot ')) + .join('\n'); + + const { data: release } = await github.rest.repos.createRelease({ + owner, + repo, + tag_name, + name, + body, + draft, + }); + + core.notice(`Created release ${release.name}: ${release.html_url}`); diff --git a/CHANGELOG.md b/CHANGELOG.md index eeff3d9092..3860a6e6dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ + + ## 8.3.1 * Add example for chaos engineering by [@martintmk](https://github.com/martintmk) in https://github.com/App-vNext/Polly/pull/1956 diff --git a/eng/bump-version.ps1 b/eng/bump-version.ps1 new file mode 100644 index 0000000000..b657905460 --- /dev/null +++ b/eng/bump-version.ps1 @@ -0,0 +1,40 @@ +#! /usr/bin/env pwsh +param( + [Parameter(Mandatory = $true)][string] $ReleaseVersion +) + +$ErrorActionPreference = "Stop" + +$repo = Join-Path $PSScriptRoot ".." +$properties = Join-Path $repo "Directory.Packages.props" + +$xml = [xml](Get-Content $properties) +$pollyVersion = $xml.SelectSingleNode('Project/PropertyGroup/PollyVersion') + +if ($ReleaseVersion.StartsWith("v")) { + $ReleaseVersion = $ReleaseVersion.Substring(1) +} + +$version = [System.Version]::new($ReleaseVersion) +$releasedVersion = $version.ToString() + +Write-Host "Bumping version from $($pollyVersion.InnerText) to $releasedVersion" + +$pollyVersion.InnerText = $releasedVersion + +$settings = New-Object System.Xml.XmlWriterSettings +$settings.Encoding = New-Object System.Text.UTF8Encoding($false) +$settings.Indent = $true +$settings.OmitXmlDeclaration = $true + +try { + $writer = [System.Xml.XmlWriter]::Create($properties, $settings) + $xml.Save($writer) +} finally { + if ($writer) { + $writer.Flush() + $writer.Dispose() + "" >> $properties + } + $writer = $null +} diff --git a/eng/update-baselines.ps1 b/eng/update-baselines.ps1 new file mode 100644 index 0000000000..61b3d55d83 --- /dev/null +++ b/eng/update-baselines.ps1 @@ -0,0 +1,56 @@ +#! /usr/bin/env pwsh +param() + +$ErrorActionPreference = "Stop" + +$encoding = New-Object System.Text.UTF8Encoding($true) +$releasedName = "PublicAPI.Shipped.txt" +$unshippedName = "PublicAPI.Unshipped.txt" + +$repo = Join-Path $PSScriptRoot ".." +$src = Join-Path $repo "src" +$files = Get-ChildItem -Path $src -Filter $unshippedName -Recurse + +# See https://github.com/dotnet/roslyn-analyzers/blob/b07c100bfc66013a8444172d00cfa04c9ceb5a97/src/PublicApiAnalyzers/Core/CodeFixes/DeclarePublicApiFix.cs#L373-L390 +$comparer = { + param( + [string]$x, + [string]$y + ) + $comparison = [System.StringComparer]::OrdinalIgnoreCase.Compare($x, $y) + if ($comparison -eq 0) { + $comparison = [System.StringComparer]::Ordinal.Compare($x, $y) + } + return $comparison; +} + +foreach ($file in $files) { + $directory = [System.IO.Path]::GetDirectoryName($file) + $changesPath = Join-Path $directory $unshippedName + $baselinePath = Join-Path $directory $releasedName + + $baseline = Get-Content $baselinePath + $baseline = [System.Collections.Generic.List[string]]$baseline + + $additions = Get-Content $changesPath + $additions = [System.Collections.Generic.List[string]]$additions + + $edited = $false + + # Skip the "#nullable enable" header + $index = (($additions.Count -gt 0) -And ($additions[0] -eq "#nullable enable")) ? 1 : 0 + while ($additions.Count -gt $index) { + $addition = $additions[$index] + $additions.RemoveAt($index) + $baseline.Add($addition) + $edited = $true + } + + if ($edited) { + $additions.Sort($comparer) + $baseline.Sort($comparer) + + $additions | Set-Content $changesPath -Encoding $encoding + $baseline | Set-Content $baselinePath -Encoding $encoding + } +} diff --git a/eng/update-changelog.ps1 b/eng/update-changelog.ps1 new file mode 100644 index 0000000000..21c9b92f75 --- /dev/null +++ b/eng/update-changelog.ps1 @@ -0,0 +1,49 @@ +#! /usr/bin/env pwsh +param( + [Parameter(Mandatory = $true)][string] $ReleaseVersion, + [Parameter(Mandatory = $true)][string] $ReleaseNotesText, + [Parameter(Mandatory = $true)][string] $GitHubServerUrl +) + +$ErrorActionPreference = "Stop" + +if ($ReleaseVersion.StartsWith("v")) { + $ReleaseVersion = $ReleaseVersion.Substring(1) +} + +Write-Host "Updating CHANGELOG for v$ReleaseVersion" + +$repo = Join-Path $PSScriptRoot ".." +$changelog = Join-Path $repo "CHANGELOG.md" + +$lines = Get-Content $changelog +$lines = [System.Collections.Generic.List[string]]$lines + +$entry = [System.Collections.Generic.List[string]]@( + "", + "## ${ReleaseVersion}", + "" +) + +$releaseNotes = $ReleaseNotesText.Split("`n") | Select-Object -Skip 1 +foreach ($line in $releaseNotes) { +if ($line -eq "") { + continue +} +if ($line.StartsWith("##")) { + break +} +if (!$line.StartsWith("* ")) { + continue +} + +# Update the user's login to link to their GitHub profile +$line = $line -Replace "\@(([a-zA-Z0-9\-]+))", ('[@$1](' + $GitHubServerUrl + '/$1)') + +$entry.Add($line) +} + +$index = $lines.IndexOf("") +$lines.InsertRange($index + 1, $entry) + +$lines | Set-Content $changelog