@@ -77,16 +77,219 @@ jobs:
7777 # Source utility functions
7878 . scripts/utils.ps1
7979
80+ function Add-GitHubEnvMultiline {
81+ param(
82+ [Parameter(Mandatory = $true)]
83+ [string]$Name,
84+
85+ [AllowEmptyString()]
86+ [string]$Value
87+ )
88+
89+ $delimiter = [System.Guid]::NewGuid().ToString('N')
90+ Add-Content -Path $env:GITHUB_ENV -Value "$Name<<$delimiter"
91+ Add-Content -Path $env:GITHUB_ENV -Value $Value
92+ Add-Content -Path $env:GITHUB_ENV -Value $delimiter
93+ }
94+
95+ function Get-GitHubReleaseInfo {
96+ param(
97+ [Parameter(Mandatory = $true)]
98+ [string]$DownloadUrl,
99+
100+ [Parameter(Mandatory = $true)]
101+ [string]$OldVersion,
102+
103+ [Parameter(Mandatory = $true)]
104+ [string]$NewVersion
105+ )
106+
107+ $uri = [uri]$DownloadUrl
108+ $segments = $uri.AbsolutePath.Trim('/').Split('/')
109+ if ($uri.Host -ne 'github.com' -or $segments.Count -lt 2) {
110+ return $null
111+ }
112+
113+ $owner = $segments[0]
114+ $repo = $segments[1]
115+ $repoPath = "$owner/$repo"
116+ $repoUrl = "https://github.com/$repoPath"
117+ $tagName = $null
118+
119+ for ($i = 0; $i -lt $segments.Count; $i++) {
120+ if ($segments[$i] -eq 'download' -and $i + 1 -lt $segments.Count) {
121+ $tagName = $segments[$i + 1]
122+ break
123+ }
124+
125+ if ($segments[$i] -eq 'archive' -and $i + 1 -lt $segments.Count) {
126+ $tagName = $segments[$i + 1] -replace '\.tar\.gz$', '' -replace '\.zip$', ''
127+ break
128+ }
129+ }
130+
131+ if ([string]::IsNullOrWhiteSpace($tagName)) {
132+ $tagName = "v$NewVersion"
133+ }
134+
135+ if ($tagName -match [regex]::Escape($NewVersion)) {
136+ $oldTagName = $tagName -replace [regex]::Escape($NewVersion), $OldVersion
137+ } else {
138+ $oldTagName = "v$OldVersion"
139+ }
140+
141+ return @{
142+ RepoPath = $repoPath
143+ RepoUrl = $repoUrl
144+ ReleasesUrl = "$repoUrl/releases"
145+ ReleaseUrl = "$repoUrl/releases/tag/$tagName"
146+ CompareUrl = "$repoUrl/compare/$oldTagName...$tagName"
147+ OldTagName = $oldTagName
148+ TagName = $tagName
149+ }
150+ }
151+
152+ function Invoke-GitHubApi {
153+ param(
154+ [Parameter(Mandatory = $true)]
155+ [string]$Uri,
156+
157+ [string]$Method = 'Get',
158+
159+ [object]$Body
160+ )
161+
162+ $headers = @{
163+ Accept = 'application/vnd.github+json'
164+ 'X-GitHub-Api-Version' = '2022-11-28'
165+ }
166+
167+ if (-not [string]::IsNullOrWhiteSpace($env:GITHUB_TOKEN)) {
168+ $headers.Authorization = "Bearer $env:GITHUB_TOKEN"
169+ }
170+
171+ $params = @{
172+ Uri = $Uri
173+ Method = $Method
174+ Headers = $headers
175+ ErrorAction = 'Stop'
176+ }
177+
178+ if ($null -ne $Body) {
179+ $params.Body = ($Body | ConvertTo-Json -Depth 8)
180+ $params.ContentType = 'application/json'
181+ }
182+
183+ return Invoke-RestMethod @params
184+ }
185+
186+ function Get-ReleaseNotes {
187+ param(
188+ [Parameter(Mandatory = $true)]
189+ [hashtable]$ReleaseInfo
190+ )
191+
192+ try {
193+ $release = Invoke-GitHubApi -Uri "https://api.github.com/repos/$($ReleaseInfo.RepoPath)/releases/tags/$($ReleaseInfo.TagName)"
194+ if (-not [string]::IsNullOrWhiteSpace($release.body)) {
195+ return $release.body.Trim()
196+ }
197+ } catch {
198+ Write-Verbose "Unable to fetch release notes for $($ReleaseInfo.RepoPath) $($ReleaseInfo.TagName): $($_.Exception.Message)" -Verbose
199+ }
200+
201+ try {
202+ $comparison = Invoke-GitHubApi -Uri "https://api.github.com/repos/$($ReleaseInfo.RepoPath)/compare/$($ReleaseInfo.OldTagName)...$($ReleaseInfo.TagName)"
203+ if ($comparison.commits.Count -gt 0) {
204+ $summary = "No release notes were found. Recent commits in the compare range:`n"
205+ foreach ($commit in ($comparison.commits | Select-Object -First 12)) {
206+ $subject = ($commit.commit.message -split "`r?`n" | Select-Object -First 1)
207+ $summary += "- [$($commit.sha.Substring(0, 7))]($($commit.html_url)) $subject`n"
208+ }
209+
210+ if ($comparison.commits.Count -gt 12) {
211+ $summary += "- ...and $($comparison.commits.Count - 12) more commits.`n"
212+ }
213+
214+ return $summary.Trim()
215+ }
216+ } catch {
217+ Write-Verbose "Unable to fetch compare details for $($ReleaseInfo.RepoPath) $($ReleaseInfo.OldTagName)...$($ReleaseInfo.TagName): $($_.Exception.Message)" -Verbose
218+ }
219+
220+ return "No release notes were found. See the release and compare links for details."
221+ }
222+
223+ function ConvertTo-BlockQuote {
224+ param(
225+ [AllowEmptyString()]
226+ [string]$Markdown,
227+
228+ [int]$MaxLength = 2500
229+ )
230+
231+ $text = if ([string]::IsNullOrWhiteSpace($Markdown)) {
232+ "No release notes were found."
233+ } else {
234+ $Markdown.Trim()
235+ }
236+
237+ if ($text.Length -gt $MaxLength) {
238+ $text = $text.Substring(0, $MaxLength).TrimEnd() + "`n`n_Release notes truncated; open the release link for the full text._"
239+ }
240+
241+ return (($text -split "`r?`n") | ForEach-Object { "> $_" }) -join "`n"
242+ }
243+
244+ function Get-VersionFromSources {
245+ param(
246+ [object[]]$Sources,
247+
248+ [Parameter(Mandatory = $true)]
249+ [string]$Name
250+ )
251+
252+ return ($Sources | Where-Object { $_.name -eq $Name } | Select-Object -First 1).version
253+ }
254+
255+ $previousPrVersion = $null
256+ $fetchPreviousPrBranchOutput = & git fetch origin update-vendor 2>&1
257+ if ($LASTEXITCODE -eq 0) {
258+ $previousPrSourcesJson = & git show FETCH_HEAD:vendor/sources.json 2>$null
259+ if ($LASTEXITCODE -eq 0 -and -not [string]::IsNullOrWhiteSpace($previousPrSourcesJson)) {
260+ $previousPrVersion = $previousPrSourcesJson | ConvertFrom-Json
261+ }
262+ } else {
263+ Write-Verbose "No existing update-vendor branch was found to use as notification baseline: $fetchPreviousPrBranchOutput" -Verbose
264+ }
265+
266+ $notificationBaselineVersion = if ($null -ne $previousPrVersion) { $previousPrVersion } else { $currentVersion }
80267 $listUpdated = ""
81268 $updateMessage = "| Name | Old Version | New Version |`n| :--- | :---: | :---: |`n"
82- $majorUpdates = @()
269+ $changelogSection = ""
270+ $hasBreakingChanges = $false
271+ $hasMinorOrMajorSinceLastPrUpdate = $false
272+ $usedEmojis = [System.Collections.Generic.List[string]]::new()
273+ $emojiDescriptions = [ordered]@{
274+ '🔥' = 'Major version update.'
275+ '🚀' = 'Minor version update.'
276+ '⬆️' = 'Patch version update.'
277+ '🔄' = 'Version change could not be classified.'
278+ }
83279 $singleDepName = ""
84280 $singleDepOldVersion = ""
85281 $singleDepNewVersion = ""
86282 foreach ($s in $newVersion) {
87283 $oldVersion = ($currentVersion | Where-Object {$_.name -eq $s.name}).version
88284 if ($s.version -ne $oldVersion) {
89- $repoUrl = ($repoUrl = $s.Url.Replace("/archive/", "/releases/")).Substring(0, $repoUrl.IndexOf("/releases/")) + "/releases"
285+ $releaseInfo = Get-GitHubReleaseInfo -DownloadUrl $s.url -OldVersion $oldVersion -NewVersion $s.version
286+ if ($null -ne $releaseInfo) {
287+ $repoUrl = $releaseInfo.ReleasesUrl
288+ $releaseUrl = $releaseInfo.ReleaseUrl
289+ } else {
290+ $repoUrl = ($repoUrl = $s.Url.Replace("/archive/", "/releases/")).Substring(0, $repoUrl.IndexOf("/releases/")) + "/releases"
291+ $releaseUrl = $repoUrl
292+ }
90293
91294 # Store single dependency info for messages (only if this is the only update)
92295 if ($count -eq 1) {
@@ -101,20 +304,31 @@ jobs:
101304 $emoji = $result.Emoji
102305 $isMajor = $result.IsMajor
103306
104- # Track major updates for changelog section
105- if ($isMajor) {
106- $compareUrl = "$repoUrl/compare/v$oldVersion...v$($s.version)"
107- $majorUpdates += @{
108- name = $s.name
109- oldVersion = $oldVersion
110- newVersion = $s.version
111- compareUrl = $compareUrl
112- repoUrl = $repoUrl
307+ if (-not $usedEmojis.Contains($emoji)) {
308+ $usedEmojis.Add($emoji)
309+ }
310+
311+ if ($changeType -in @('major', 'downgrade', 'unknown')) {
312+ $hasBreakingChanges = $true
313+ }
314+
315+ $previousPrUpdateVersion = Get-VersionFromSources -Sources $notificationBaselineVersion -Name $s.name
316+ if (-not [string]::IsNullOrWhiteSpace($previousPrUpdateVersion) -and $previousPrUpdateVersion -ne $s.version) {
317+ $notificationChangeType = Get-VersionChangeType -OldVersion $previousPrUpdateVersion -NewVersion $s.version
318+ if ($notificationChangeType.ChangeType -in @('major', 'minor')) {
319+ $hasMinorOrMajorSinceLastPrUpdate = $true
113320 }
114321 }
115322
323+ if ($null -ne $releaseInfo) {
324+ $releaseNotes = Get-ReleaseNotes -ReleaseInfo $releaseInfo
325+ $changelogSection += "### $($s.name) $oldVersion → $($s.version)`n`n"
326+ $changelogSection += "[Release notes]($($releaseInfo.ReleaseUrl)) · [Compare changes]($($releaseInfo.CompareUrl))`n`n"
327+ $changelogSection += (ConvertTo-BlockQuote -Markdown $releaseNotes) + "`n`n"
328+ }
329+
116330 $listUpdated += "$($s.name) v$($s.version), "
117- $updateMessage += "| $emoji **[$($s.name)]($repoUrl)** | ``$oldVersion`` | **``$($s.version)``** |`n"
331+ $updateMessage += "| $emoji **[$($s.name)]($repoUrl)** | ``$oldVersion`` | **[ ``$($s.version)``]($releaseUrl) ** |`n"
118332 }
119333 }
120334
@@ -140,31 +354,33 @@ jobs:
140354 Set-GHVariable -Name SINGLE_DEP_OLD_VERSION -Value $singleDepOldVersion
141355 Set-GHVariable -Name SINGLE_DEP_NEW_VERSION -Value $singleDepNewVersion
142356
143- # Write multiline UPDATE_MESSAGE to GITHUB_ENV
144- ## echo "UPDATE_MESSAGE<<EOF`n$updateMessage`nEOF" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
145- Add-Content -Path $env:GITHUB_ENV -Value "UPDATE_MESSAGE<<EOF"
146- Add-Content -Path $env:GITHUB_ENV -Value $updateMessage
147- Add-Content -Path $env:GITHUB_ENV -Value "EOF"
148-
149- # Generate major updates changelog section and export
150- if ($majorUpdates.Count -gt 0) {
151- $changelogSection = "`n<details>`n<summary>🔥 Major version updates - View changelog</summary>`n`n"
152- foreach ($update in $majorUpdates) {
153- $changelogSection += "### [$($update.name)]($($update.repoUrl))`n"
154- $changelogSection += "**$($update.oldVersion)** → **$($update.newVersion)**`n`n"
155- $changelogSection += "- [View full changelog]($($update.compareUrl))`n"
156- $changelogSection += "- [Release notes]($($update.repoUrl)/tag/v$($update.newVersion))`n`n"
157- }
158- $changelogSection += "</details>`n"
357+ $emojiLegend = "**Legend**`n`n"
358+ foreach ($usedEmoji in $usedEmojis) {
359+ $emojiLegend += "$usedEmoji $($emojiDescriptions[$usedEmoji])`n"
360+ }
361+
362+ if ([string]::IsNullOrWhiteSpace($changelogSection)) {
363+ $changelogSection = ""
364+ } else {
365+ $changelogSection = "`n<details>`n<summary>Release notes for updated vendors</summary>`n`n$changelogSection</details>`n"
366+ }
367+
368+ $reviewTeam = ""
369+ if ($hasMinorOrMajorSinceLastPrUpdate -and $env:GITHUB_REPOSITORY_OWNER -eq 'cmderdev') {
370+ $reviewTeam = "trusted-contributors"
371+ }
159372
160- Add-Content -Path $env:GITHUB_ENV -Value "CHANGELOG_SECTION<<EOF"
161- Add-Content -Path $env:GITHUB_ENV -Value $changelogSection
162- Add-Content -Path $env:GITHUB_ENV -Value "EOF"
373+ Add-GitHubEnvMultiline -Name UPDATE_MESSAGE -Value $updateMessage
374+ Add-GitHubEnvMultiline -Name CHANGELOG_SECTION -Value $changelogSection
375+ Add-GitHubEnvMultiline -Name EMOJI_LEGEND -Value $emojiLegend
376+
377+ if ($hasBreakingChanges) {
163378 Add-Content -Path $env:GITHUB_ENV -Value "HAS_BREAKING_CHANGES=True"
164379 } else {
165- Add-Content -Path $env:GITHUB_ENV -Value "CHANGELOG_SECTION="
166380 Add-Content -Path $env:GITHUB_ENV -Value "HAS_BREAKING_CHANGES=False"
167381 }
382+ Add-Content -Path $env:GITHUB_ENV -Value "HAS_MINOR_OR_MAJOR_SINCE_LAST_PR_UPDATE=$hasMinorOrMajorSinceLastPrUpdate"
383+ Add-Content -Path $env:GITHUB_ENV -Value "REVIEW_TEAM=$reviewTeam"
168384
169385 - name : Summary - Update check results
170386 shell : pwsh
@@ -301,12 +517,15 @@ jobs:
301517
302518 ---
303519
304- ${{ env.HAS_BREAKING_CHANGES == 'True' && '⚠️ **This update contains major version changes that may include breaking changes.**' || 'ℹ️ This update only contains minor or patch changes.' }}
520+ ${{ env.EMOJI_LEGEND }}
521+
522+ ${{ env.HAS_BREAKING_CHANGES == 'True' && '**Review note:** This update contains version changes that may require manual review.' || '**Review note:** This update only contains minor or patch changes.' }}
305523
306524 Please verify and then **Merge** the pull request to apply the updates.
307525 commit-message : ' ⬆️ Update dependencies (${{ env.LIST_UPDATED }})'
308526 branch : update-vendor
309527 base : master
528+ team-reviewers : ${{ env.REVIEW_TEAM }}
310529
311530 - name : Summary - Pull request result
312531 if : env.COUNT_UPDATED > 0 && env.AUTO_MERGED != 'true'
0 commit comments