diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 57feccffd..f2c5fe9f5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -56,7 +56,7 @@ jobs: $actualBranchName = $refName $branchLink = "" $prLink = "" - + # Check if this is a PR merge ref (e.g., "3061/merge") if ($refName -match '^(\d+)/(merge|head)$') { $prNumber = $Matches[1] @@ -196,7 +196,7 @@ jobs: run: | # Source utility functions . scripts/utils.ps1 - + $summary = @" ### đŸ—ƒī¸ Artifacts @@ -208,7 +208,7 @@ jobs: # Get all files from the build directory (excluding directories and hidden files) if (Test-Path "build") { $buildFiles = Get-ChildItem -Path "build" -File | Where-Object { -not $_.Name.StartsWith('.') } | Sort-Object Name - + foreach ($file in $buildFiles) { $artifact = $file.Name $path = $file.FullName diff --git a/.github/workflows/vendor.yml b/.github/workflows/vendor.yml index b464a5370..14fa7ed5a 100644 --- a/.github/workflows/vendor.yml +++ b/.github/workflows/vendor.yml @@ -6,6 +6,10 @@ on: # At 13:37 UTC every day. - cron: '37 13 * * *' +concurrency: + group: vendor-update + cancel-in-progress: false + defaults: run: shell: pwsh @@ -45,64 +49,223 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - $currentVersion = (Get-Content -Raw .\vendor\sources.json | ConvertFrom-Json) - . .\scripts\update.ps1 -verbose - Set-GHVariable -Name COUNT_UPDATED -Value $count - $newVersion = (Get-Content -Raw .\vendor\sources.json | ConvertFrom-Json) - $listUpdated = "" - $updateMessage = "| Name | Old Version | New Version |`n| :--- | ---- | ---- |`n" - foreach ($s in $newVersion) { - $oldVersion = ($currentVersion | Where-Object {$_.name -eq $s.name}).version - if ($s.version -ne $oldVersion) { - $repoUrl = ($repoUrl = $s.Url.Replace("/archive/", "/releases/")).Substring(0, $repoUrl.IndexOf("/releases/")) + "/releases" - $listUpdated += "$($s.name) v$($s.version), " - $updateMessage += "| **[$($s.name)]($repoUrl)** | $oldVersion | **$($s.version)** |`n" + $currentVersion = (Get-Content -Raw .\vendor\sources.json | ConvertFrom-Json) + . .\scripts\update.ps1 -verbose + + # Export count of updated packages (update.ps1 is expected to set $count) + if (-not ($count)) { $count = 0 } + Set-GHVariable -Name COUNT_UPDATED -Value $count + + $newVersion = (Get-Content -Raw .\vendor\sources.json | ConvertFrom-Json) + # Source utility functions + . scripts/utils.ps1 + + $listUpdated = "" + $updateMessage = "| Name | Old Version | New Version |`n| :--- | :---: | :---: |`n" + $majorUpdates = @() + $singleDepName = "" + $singleDepOldVersion = "" + $singleDepNewVersion = "" + foreach ($s in $newVersion) { + $oldVersion = ($currentVersion | Where-Object {$_.name -eq $s.name}).version + if ($s.version -ne $oldVersion) { + $repoUrl = ($repoUrl = $s.Url.Replace("/archive/", "/releases/")).Substring(0, $repoUrl.IndexOf("/releases/")) + "/releases" + + # Store single dependency info for messages (only if this is the only update) + if ($count -eq 1) { + $singleDepName = $s.name + $singleDepOldVersion = $oldVersion + $singleDepNewVersion = $s.version + } + + # Determine change type and emoji using shared function + $result = Get-VersionChangeType -OldVersion $oldVersion -NewVersion $s.version + $changeType = $result.ChangeType + $emoji = $result.Emoji + $isMajor = $result.IsMajor + + # Track major updates for changelog section + if ($isMajor) { + $compareUrl = "$repoUrl/compare/v$oldVersion...v$($s.version)" + $majorUpdates += @{ + name = $s.name + oldVersion = $oldVersion + newVersion = $s.version + compareUrl = $compareUrl + repoUrl = $repoUrl + } + } + + $listUpdated += "$($s.name) v$($s.version), " + $updateMessage += "| $emoji **[$($s.name)]($repoUrl)** | \`$oldVersion\` | **\`$($s.version)\`** |`n" + } + } + + if ($count -eq 0) { return } + + Set-GHVariable -Name LIST_UPDATED -Value $listUpdated.Trim(', ') + # Set single dependency variables (they will only be used if COUNT_UPDATED is 1) + # Use safe fallback values in case variables weren't set (shouldn't happen but prevents errors) + if ([string]::IsNullOrEmpty($singleDepName) -and $count -eq 1) { + # This shouldn't happen, but if it does, log a warning + Write-Warning "Single dependency name not set despite count being 1" + $singleDepName = "unknown-package" + $singleDepOldVersion = "unknown" + $singleDepNewVersion = "unknown" + } elseif ([string]::IsNullOrEmpty($singleDepName)) { + # For multiple dependencies, set placeholder values (won't be used) + $singleDepName = "" + $singleDepOldVersion = "" + $singleDepNewVersion = "" } - } - if ($count -eq 0) { return } - Set-GHVariable -Name LIST_UPDATED -Value $listUpdated.Trim(', ') - echo "UPDATE_MESSAGE< 0 && env.HAS_BREAKING_CHANGES != 'True' + shell: pwsh + run: | + try { + echo "### 🚀 Auto-merging Updates" >> $env:GITHUB_STEP_SUMMARY + echo "" >> $env:GITHUB_STEP_SUMMARY + echo "Attempting to automatically merge non-breaking changes to master..." >> $env:GITHUB_STEP_SUMMARY + + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + + # Commit the changes + git add vendor/sources.json + $commitResult = git commit -m "âŦ†ī¸ Update dependencies ($env:LIST_UPDATED)" + $commitSuccess = $LASTEXITCODE -eq 0 + + if ($commitSuccess) { + # Push directly to master + git push origin HEAD:master + $pushSuccess = $LASTEXITCODE -eq 0 + + if ($pushSuccess) { + echo "" >> $env:GITHUB_STEP_SUMMARY + echo "✅ **Success!** Updates have been automatically merged to master." >> $env:GITHUB_STEP_SUMMARY + echo "" >> $env:GITHUB_STEP_SUMMARY + echo "**Updated dependencies:** $env:LIST_UPDATED" >> $env:GITHUB_STEP_SUMMARY + + # Set a flag to skip PR creation + echo "AUTO_MERGED=true" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 + } else { + throw "Failed to push to master (exit code: $LASTEXITCODE)" + } + } else { + throw "Failed to commit changes (exit code: $LASTEXITCODE)" + } + } catch { + echo "" >> $env:GITHUB_STEP_SUMMARY + echo "âš ī¸ **Warning:** Unable to automatically merge updates." >> $env:GITHUB_STEP_SUMMARY + echo "" >> $env:GITHUB_STEP_SUMMARY + echo "**Error:** $($_.Exception.Message)" >> $env:GITHUB_STEP_SUMMARY + echo "" >> $env:GITHUB_STEP_SUMMARY + echo "Falling back to creating a pull request..." >> $env:GITHUB_STEP_SUMMARY + + Write-Warning "Failed to auto-merge: $($_.Exception.Message)" + + # Only reset if commit was successful but push failed + if ($commitSuccess -and -not $pushSuccess) { + try { + git reset --hard HEAD~1 + if ($LASTEXITCODE -ne 0) { + Write-Warning "Failed to reset commit (exit code: $LASTEXITCODE), continuing with PR creation" + } + } catch { + Write-Warning "Failed to reset commit: $($_.Exception.Message), continuing with PR creation" + } + } + + # Set flag to create PR instead + echo "AUTO_MERGED=false" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 + } + - uses: peter-evans/create-pull-request@v8 - if: env.COUNT_UPDATED > 0 + if: fromJSON(env.COUNT_UPDATED) > 0 && (env.HAS_BREAKING_CHANGES == 'True' || env.AUTO_MERGED == 'false') with: - title: 'Updates to `${{ env.COUNT_UPDATED }}` vendored dependencies' + title: ${{ env.COUNT_UPDATED == 1 && format('âŦ†ī¸ Update {0}', env.LIST_UPDATED) || format('âŦ†ī¸ Update {0} vendored dependencies', env.COUNT_UPDATED) }} body: | - ### Automatically updated `${{ env.COUNT_UPDATED }}` dependencies: + ### ${{ env.COUNT_UPDATED == 1 && format('đŸ“Ļ Updated {0} from `{1}` to `{2}`', env.SINGLE_DEP_NAME, env.SINGLE_DEP_OLD_VERSION, env.SINGLE_DEP_NEW_VERSION) || format('đŸ“Ļ Automatically updated `{0}` dependencies', env.COUNT_UPDATED) }} + ${{ env.UPDATE_MESSAGE }} + + ${{ env.CHANGELOG_SECTION }} + --- - Please verify and then **Merge** the pull request to update. + + ${{ env.HAS_BREAKING_CHANGES == 'True' && 'âš ī¸ **This update contains major version changes that may include breaking changes.**' || 'â„šī¸ This update only contains minor or patch changes.' }} + + Please verify and then **Merge** the pull request to apply the updates. commit-message: 'âŦ†ī¸ Update dependencies (${{ env.LIST_UPDATED }})' branch: update-vendor base: master - name: Summary - Pull request created - if: env.COUNT_UPDATED > 0 + if: env.COUNT_UPDATED > 0 && (env.HAS_BREAKING_CHANGES == 'True' || env.AUTO_MERGED == 'false') shell: pwsh run: | $summary = @" @@ -112,9 +275,19 @@ jobs: **Branch:** ``update-vendor`` - **Updated dependencies:** $env:LIST_UPDATED + $(if (-not [string]::IsNullOrEmpty($env:LIST_UPDATED)) { "**Updated dependencies:** $env:LIST_UPDATED" } else { "**Updated dependencies:** " }) + "@ + + if ($env:HAS_BREAKING_CHANGES -eq 'True') { + $summary += "> âš ī¸ **Manual review required:** This update contains major version changes." + } else { + $summary += "> â„šī¸ **Note:** Auto-merge failed, manual review required." + } + + $summary += @" > Please review and merge the pull request to apply the updates. + "@ $summary | Add-Content -Path $env:GITHUB_STEP_SUMMARY -Encoding utf8 diff --git a/scripts/update.ps1 b/scripts/update.ps1 index c6b982d7c..7da623a92 100644 --- a/scripts/update.ps1 +++ b/scripts/update.ps1 @@ -259,6 +259,8 @@ function Fetch-DownloadUrl { } $count = 0 +$hasBreakingChanges = $false +$updateDetails = @() # Read the current sources content $sources = Get-Content $sourcesPath | Out-String | ConvertFrom-Json @@ -301,6 +303,26 @@ foreach ($s in $sources) { # } $count++ + + # Analyze version change type using shared function + $result = Get-VersionChangeType -OldVersion $s.version -NewVersion $version + $changeType = $result.ChangeType + + # Determine if this is a breaking change + if ($changeType -eq "downgrade" -or $changeType -eq "major") { + $hasBreakingChanges = $true + } elseif ($changeType -eq "unknown") { + # If version parsing failed, treat as potentially breaking + $hasBreakingChanges = $true + Write-Verbose "Could not parse version as semantic version for dependency '$($s.name)' (old: '$($s.version)', new: '$version'), treating as potentially breaking" + } + + $updateDetails += @{ + name = $s.name + oldVersion = $s.version + newVersion = $version + changeType = $changeType + } } $s.url = $downloadUrl @@ -314,12 +336,16 @@ if ($count -eq 0) { return } -if ($Env:APPVEYOR -eq 'True') { - Add-AppveyorMessage -Message "Successfully updated $count dependencies." -Category Information -} - +# Export update details for GitHub Actions if ($Env:GITHUB_ACTIONS -eq 'true') { + $updateDetailsJson = $updateDetails | ConvertTo-Json -Compress + Write-Output "UPDATE_DETAILS=$updateDetailsJson" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 + Write-Output "HAS_BREAKING_CHANGES=$hasBreakingChanges" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 Write-Output "::notice title=Task Complete::Successfully updated $count dependencies." } +if ($Env:APPVEYOR -eq 'True') { + Add-AppveyorMessage -Message "Successfully updated $count dependencies." -Category Information +} + Write-Host -ForegroundColor green "Successfully updated $count dependencies." diff --git a/scripts/utils.ps1 b/scripts/utils.ps1 index f1a175221..d74a5da39 100644 --- a/scripts/utils.ps1 +++ b/scripts/utils.ps1 @@ -285,6 +285,100 @@ function Format-FileSize { } } +function Get-VersionChangeType { + <# + .SYNOPSIS + Analyzes version changes using semantic versioning to determine the type of update and appropriate emoji. + + .DESCRIPTION + Parses old and new version strings, compares them using semantic versioning rules, + and returns information about the change type, emoji indicator, and whether it's a major update. + + .PARAMETER OldVersion + The previous version string (e.g., "1.2.3" or "2.52.0.windows.1"). + + .PARAMETER NewVersion + The new version string (e.g., "1.3.0" or "3.0.0.windows.1"). + + .OUTPUTS + Returns a hashtable with the following keys: + - ChangeType: "major", "minor", "patch", "downgrade", or "unknown" + - Emoji: The corresponding emoji indicator (đŸ”Ĩ, 🚀, âŦ†ī¸, or 🔄) + - IsMajor: Boolean indicating if this is a major version update + + .EXAMPLE + $result = Get-VersionChangeType -OldVersion "1.2.3" -NewVersion "2.0.0" + # Returns: @{ ChangeType = "major"; Emoji = "đŸ”Ĩ"; IsMajor = $true } + + .EXAMPLE + $result = Get-VersionChangeType -OldVersion "1.2.3" -NewVersion "1.3.0" + # Returns: @{ ChangeType = "minor"; Emoji = "🚀"; IsMajor = $false } + #> + param( + [Parameter(Mandatory = $true)] + [string]$OldVersion, + + [Parameter(Mandatory = $true)] + [string]$NewVersion + ) + + $changeType = "unknown" + $emoji = "🔄" + $isMajor = $false + + try { + # Handle versions with more than 4 parts and strip pre-release identifiers + $oldVerStr = $OldVersion.Split('-')[0] + $newVerStr = $NewVersion.Split('-')[0] + + # Split by dots and take only numeric parts, first 4 max + $oldParts = $oldVerStr.Split('.') | Where-Object { $_ -match '^\d+$' } | Select-Object -First 4 + $newParts = $newVerStr.Split('.') | Where-Object { $_ -match '^\d+$' } | Select-Object -First 4 + + # Ensure we have at least 2 parts (major.minor) for semantic versioning + if ($oldParts.Count -ge 2 -and $newParts.Count -ge 2) { + $oldVerParseable = $oldParts -join '.' + $newVerParseable = $newParts -join '.' + + $oldVer = [System.Version]::Parse($oldVerParseable) + $newVer = [System.Version]::Parse($newVerParseable) + + if ($newVer -lt $oldVer) { + $changeType = "downgrade" + # Don't set emoji for downgrades in this function - caller handles it + } elseif ($newVer.Major -gt $oldVer.Major) { + $changeType = "major" + $emoji = "đŸ”Ĩ" + $isMajor = $true + } elseif ($newVer.Minor -gt $oldVer.Minor) { + $changeType = "minor" + $emoji = "🚀" + } elseif ($newVer -gt $oldVer) { + $changeType = "patch" + $emoji = "âŦ†ī¸" + } else { + # No version increase detected (versions are equal) + $changeType = "unknown" + $emoji = "🔄" + } + } else { + # Not enough numeric parts for semantic versioning + throw "Not enough numeric version parts" + } + } catch { + # If semantic versioning fails, return unknown + $changeType = "unknown" + $emoji = "🔄" + $isMajor = $false + } + + return @{ + ChangeType = $changeType + Emoji = $emoji + IsMajor = $isMajor + } +} + function Get-ArtifactDownloadUrl { <# .SYNOPSIS