-
Notifications
You must be signed in to change notification settings - Fork 38
S⚠️ ◾ Automated Package Update #820
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
40 commits
Select commit
Hold shift + click to select a range
5a23446
Add GitHub Actions workflow for package updates
neilr81 b7f8e53
Add script to update NuGet package versions
neilr81 c0d9eb7
Update script paths in package-update.yml
neilr81 bfb26c8
Apply suggestion from @Copilot
neilr81 88eb82c
Update package-update workflow to use cron schedule
neilr81 671dd97
Update .github/scripts/Update-NuGetPackageVersions.ps1
neilr81 1c56edc
Gate PR creation on commit detection in package-update workflow (#796)
Copilot 4b27d31
Update .github/scripts/Update-NuGetPackageVersions.ps1
neilr81 8c3190a
Apply suggestion from @Copilot
neilr81 287fbe0
Apply suggestion from @Copilot
neilr81 c56db37
Apply suggestion from @Copilot
neilr81 1a02929
Add pull_request_target trigger to workflow
neilr81 1cf02c9
Apply suggestion from @Copilot
neilr81 331b09d
Update package-update.yml
neilr81 07d2d47
Update package-update.yml
neilr81 0455764
Rename checkout step for clarity
neilr81 1cbd892
Remove GitHub CLI installation from package-update.yml
neilr81 3e92781
Update Update-NuGetPackageVersions.ps1
neilr81 fc9b6c7
Fix label assignment in package-update workflow
neilr81 527230e
Remove 'powershell' label from package update PR
neilr81 34493b9
Add label to automated package update PR
neilr81 f2afff8
Fix label formatting in package-update.yml
neilr81 f5431d8
Add token input for create pull request action
neilr81 cd5081d
Update GH_TOKEN to use secrets for PR creation
neilr81 852325b
Update package-update.yml
neilr81 40ba9dd
Merge 852325b05f01c52e46146df66f5ff51cbc6e3dba into 60e237869ccaee744…
neilr81 379cba4
chore(automation): apply PowerShell updates
github-actions[bot] 55e1c51
Merge 379cba4898327542c90f9628dcad07929844ea34 into 60e237869ccaee744…
neilr81 5d69300
Merge 55e1c51e667baa7dc0d157db902ce43afc7b7123 into 60e237869ccaee744…
neilr81 6f35d1d
Merge 5d69300647c7452ac6e3b3275061869bbe4b49d9 into 60e237869ccaee744…
neilr81 bbc97ca
Merge 6f35d1d7d1fd0933ff64bf60a59f4f35e2ecc4f7 into 60e237869ccaee744…
neilr81 6137e99
Merge bbc97ca5a35cbd41c5ff03b6bc399b0a05743565 into 60e237869ccaee744…
neilr81 e4ab9fa
Merge 6137e9954e265f9659000055ffa79a1cde860dcf into 60e237869ccaee744…
neilr81 929f317
Merge e4ab9fa5b398881982bf4213959a33b35d8915ca into 60e237869ccaee744…
neilr81 bb4f02f
Merge 929f3177cb4b6aa66a1a7d60352c247bf9cc4406 into 60e237869ccaee744…
neilr81 9a99f0b
Merge bb4f02fd618d38ae014459480b6845ff7494b1a3 into 60e237869ccaee744…
neilr81 92be031
Merge 9a99f0b54f056443b45f0f36de40be0ccf332de2 into 60e237869ccaee744…
neilr81 a821e33
Merge 92be0315b106814bb4a4f714c60d73767dc99c6c into 60e237869ccaee744…
neilr81 f897fe9
Merge a821e33fe25d2de25ff56b5548da0241e6726738 into 60e237869ccaee744…
neilr81 bf58d05
Merge f897fe9e0c252867e8768a9ff081b221cdfddbec into 60e237869ccaee744…
neilr81 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,350 @@ | ||
| <# | ||
| .SYNOPSIS | ||
| Updates NuGet package versions in Directory.Packages.props file. | ||
|
|
||
| .DESCRIPTION | ||
| Scans Directory.Packages.props for PackageVersion elements in ItemGroups labeled 'AutoUpdate', | ||
| queries the latest available versions using 'dotnet package search', and updates the Version | ||
| attributes accordingly. Respects PreserveMajor attribute and pre-release version detection. | ||
|
|
||
| .PARAMETER PropsFilePath | ||
| Relative path to the Directory.Packages.props file from the source directory. | ||
| Default: "Directory.Packages.props" | ||
|
|
||
| .PARAMETER SourcesDirectory | ||
| The root source directory containing the props file. | ||
| Default: $env:BUILD_SOURCESDIRECTORY (Azure Pipelines variable) | ||
|
|
||
| .PARAMETER FailOnError | ||
| If $true, throws an exception on package lookup failures. If $false, logs warnings and continues. | ||
| Default: $false | ||
|
|
||
| .NOTES | ||
| Verbose logging is automatically enabled when Azure Pipelines System.Debug is set to 'true'. | ||
| To enable verbose logging, set the system.debug variable in your pipeline or run with: | ||
| variables: | ||
| system.debug: true | ||
|
|
||
| .EXAMPLE | ||
| .\Update-NuGetPackageVersions.ps1 | ||
|
|
||
| .EXAMPLE | ||
| .\Update-NuGetPackageVersions.ps1 -PropsFilePath "Directory.Packages.props" -FailOnError $true | ||
|
|
||
| .EXAMPLE | ||
| $env:SYSTEM_DEBUG = 'true'; .\Update-NuGetPackageVersions.ps1 | ||
| #> | ||
|
|
||
| [CmdletBinding()] | ||
| param( | ||
| [Parameter(Mandatory = $false)] | ||
| [string]$PropsFilePath = "Directory.Packages.props", | ||
|
|
||
| [Parameter(Mandatory = $false)] | ||
| [string]$SourcesDirectory = $( | ||
| if ($env:BUILD_SOURCESDIRECTORY) { | ||
| $env:BUILD_SOURCESDIRECTORY | ||
| } | ||
| elseif ($env:GITHUB_WORKSPACE) { | ||
| $env:GITHUB_WORKSPACE | ||
| } | ||
| else { | ||
| (Get-Location).Path | ||
| } | ||
| ), | ||
|
|
||
| [Parameter(Mandatory = $false)] | ||
| [bool]$FailOnError = $false | ||
| ) | ||
|
|
||
| # Normalize and validate SourcesDirectory so it is always a valid root directory | ||
| if ([string]::IsNullOrWhiteSpace($SourcesDirectory)) { | ||
| # GitHub Actions default | ||
| $SourcesDirectory = $env:GITHUB_WORKSPACE | ||
| } | ||
|
|
||
| if ([string]::IsNullOrWhiteSpace($SourcesDirectory)) { | ||
| # Local or generic PowerShell fallback | ||
| $SourcesDirectory = (Get-Location).Path | ||
| } | ||
|
|
||
| if (-not (Test-Path -LiteralPath $SourcesDirectory -PathType Container)) { | ||
| throw "SourcesDirectory '$SourcesDirectory' does not exist or is not a directory. Specify a valid -SourcesDirectory path." | ||
| } | ||
|
|
||
| # Resolve to a fully qualified, normalized path | ||
| $SourcesDirectory = (Resolve-Path -LiteralPath $SourcesDirectory).ProviderPath | ||
| # Determine whether verbose logging should be enabled: | ||
| # - Prefer the standard -Verbose common parameter when explicitly passed | ||
| # - Fall back to Azure Pipelines System.Debug variable for backwards compatibility | ||
| $isVerboseParameterSet = $PSBoundParameters.ContainsKey('Verbose') -and $PSBoundParameters['Verbose'] | ||
| $EnableVerboseLogging = $isVerboseParameterSet -or ($env:SYSTEM_DEBUG -eq 'true') -or ($env:SYSTEM_DEBUG -eq '1') | ||
|
|
||
| if ($EnableVerboseLogging) { | ||
| $VerbosePreference = 'Continue' | ||
| } | ||
| $ErrorActionPreference = if ($FailOnError) { "Stop" } else { "Continue" } | ||
|
|
||
| # Helper function for version comparison (from VersionUtils.ps1) | ||
| function Get-LatestVersionFromString { | ||
| param ( | ||
| [string]$First, | ||
| [string]$Second | ||
| ) | ||
|
|
||
| if (-not $First) { return $Second } | ||
| if (-not $Second) { return $First } | ||
|
|
||
| function Get-VersionFromString { | ||
| param ([string]$Value) | ||
|
|
||
| $splitIndex = $Value.IndexOf('-') | ||
| if ($splitIndex -eq -1) { | ||
| $versionString = $Value | ||
| $suffix = '' | ||
| } else { | ||
| $versionString = $Value.Substring(0, $splitIndex) | ||
| $suffix = $Value.Substring($splitIndex) | ||
| } | ||
|
|
||
| $version = $null | ||
| if (-not [System.Version]::TryParse($versionString, [ref]$version)) { | ||
| $version = $versionString | ||
| } | ||
|
|
||
| return [PSCustomObject]@{ Version = $version; Suffix = $suffix } | ||
| } | ||
|
|
||
| $firstVersionObject = Get-VersionFromString $First | ||
| $secondVersionObject = Get-VersionFromString $Second | ||
|
|
||
| if ($firstVersionObject.Version -eq $secondVersionObject.Version) { | ||
| if (-not $firstVersionObject.Suffix) { return $First } | ||
| if (-not $secondVersionObject.Suffix) { return $Second } | ||
| if ($firstVersionObject.Suffix -lt $secondVersionObject.Suffix) { return $Second } | ||
| return $First | ||
| } | ||
|
|
||
| if ($firstVersionObject.Version -lt $secondVersionObject.Version) { return $Second } | ||
| return $First | ||
| } | ||
|
|
||
| function Test-PreReleaseVersion { | ||
| param ([string]$Version) | ||
| return $Version.Contains('-') | ||
| } | ||
|
|
||
| function Get-LatestPackageVersion { | ||
| param ( | ||
| [string]$PackageId, | ||
| [bool]$IncludePrerelease, | ||
| [string]$MajorVersion = "" | ||
| ) | ||
|
|
||
| try { | ||
| $prereleaseFlag = if ($IncludePrerelease) { "--prerelease" } else { "" } | ||
|
|
||
| # Prefer NuGet-GitHub.Config (used on GitHub runners) when present, otherwise fall back to NuGet.config | ||
| $nugetGithubConfigPath = Join-Path $SourcesDirectory "NuGet-GitHub.Config" | ||
| $nugetConfigPath = Join-Path $SourcesDirectory "NuGet.config" | ||
| $configSourceFlag = "" | ||
| if (Test-Path $nugetGithubConfigPath) { | ||
| $configSourceFlag = "--configfile `"$nugetGithubConfigPath`"" | ||
| if ($EnableVerboseLogging) { | ||
| Write-Host " [VERBOSE] Using NuGet-GitHub.Config from: $nugetGithubConfigPath" | ||
| } | ||
| } | ||
| elseif (Test-Path $nugetConfigPath) { | ||
| $configSourceFlag = "--configfile `"$nugetConfigPath`"" | ||
| if ($EnableVerboseLogging) { | ||
| Write-Host " [VERBOSE] Using NuGet.config from: $nugetConfigPath" | ||
| } | ||
| } | ||
| else { | ||
| Write-Warning "NuGet-GitHub.Config or NuGet.config not found at: $SourcesDirectory - search may not find required feeds" | ||
| } | ||
|
Comment on lines
+147
to
+165
|
||
|
|
||
| $searchCmd = "dotnet package search `"$PackageId`" --exact-match --format json $prereleaseFlag $configSourceFlag" | ||
|
|
||
| if ($EnableVerboseLogging) { | ||
| Write-Host " [VERBOSE] Executing: $searchCmd" | ||
| } | ||
|
|
||
| Write-Host ("Searching for package: {0} {1}" -f $PackageId, ($MajorVersion ? ('(major version {0}.*)' -f $MajorVersion) : '')) | ||
|
|
||
| $output = Invoke-Expression $searchCmd 2>&1 | Out-String | ||
|
|
||
| if ($LASTEXITCODE -ne 0) { | ||
| $errorMsg = "Failed to search for package '$PackageId': $output" | ||
| if ($FailOnError) { | ||
| throw $errorMsg | ||
| } else { | ||
| Write-Warning $errorMsg | ||
| return $null | ||
| } | ||
| } | ||
|
|
||
| if ($EnableVerboseLogging) { | ||
| Write-Host " [VERBOSE] Raw output: $output" | ||
| } | ||
|
|
||
| $result = $output | ConvertFrom-Json | ||
|
|
||
| if (-not $result.searchResult -or $result.searchResult.Count -eq 0) { | ||
| $errorMsg = "Package '$PackageId' not found in any configured feed" | ||
| if ($FailOnError) { | ||
| throw $errorMsg | ||
| } else { | ||
| Write-Warning $errorMsg | ||
| return $null | ||
| } | ||
| } | ||
|
|
||
| # Iterate through all sources and packages to find the latest version | ||
| # The JSON structure is: searchResult[].packages[].version | ||
| $latestVersion = $null | ||
| foreach ($source in $result.searchResult) { | ||
| if ($source.packages) { | ||
| foreach ($package in $source.packages) { | ||
| if ($package.id -eq $PackageId) { | ||
| # If major version filtering is needed, skip non-matching versions | ||
| if ($MajorVersion -and -not ($package.version -match "^$MajorVersion\.")) { | ||
| continue | ||
| } | ||
| $latestVersion = Get-LatestVersionFromString -First $latestVersion -Second $package.version | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if ($EnableVerboseLogging) { | ||
| Write-Host " [VERBOSE] Found latest version: $latestVersion" | ||
| } | ||
|
|
||
| return $latestVersion | ||
| } | ||
| catch { | ||
| $errorMsg = "Error searching for package '$PackageId': $_" | ||
| if ($FailOnError) { | ||
| throw $errorMsg | ||
| } else { | ||
| Write-Warning $errorMsg | ||
| return $null | ||
| } | ||
| } | ||
| } | ||
|
|
||
| # Main script execution | ||
| Write-Host "===============================================================================" | ||
| Write-Host "NuGet Package Version Update Script" | ||
| Write-Host "===============================================================================" | ||
| Write-Host "Props File Path: $PropsFilePath" | ||
| Write-Host "Sources Directory: $SourcesDirectory" | ||
| Write-Host "Verbose Logging: $EnableVerboseLogging" | ||
| Write-Host "Fail On Error: $FailOnError" | ||
| Write-Host "===============================================================================" | ||
|
|
||
| $propsFile = Join-Path $SourcesDirectory $PropsFilePath | ||
| Write-Host "Full path to props file: $propsFile" | ||
|
|
||
| if (-not (Test-Path $propsFile)) { | ||
| $errorMsg = "Props file not found at: $propsFile" | ||
| if ($FailOnError) { | ||
| throw $errorMsg | ||
| } else { | ||
| Write-Warning $errorMsg | ||
| exit 1 | ||
| } | ||
| } | ||
|
|
||
| Write-Host "Updating NuGet packages in: $propsFile" | ||
|
|
||
| # Load XML with whitespace preservation | ||
| $xml = New-Object System.Xml.XmlDocument | ||
| $xml.PreserveWhitespace = $true | ||
| $xml.Load($propsFile) | ||
|
|
||
| $updateCount = 0 | ||
|
|
||
| # Find all ItemGroups with AutoUpdate label | ||
| $autoUpdateGroups = $xml.Project.ItemGroup | Where-Object { | ||
| $null -ne $_.Label -and $_.Label -match 'AutoUpdate' | ||
| } | ||
|
|
||
| Write-Host "Found $($autoUpdateGroups.Count) ItemGroups with AutoUpdate label" | ||
|
|
||
| foreach ($itemGroup in $autoUpdateGroups) { | ||
| $packageVersions = $itemGroup.PackageVersion | ||
|
|
||
| if (-not $packageVersions) { continue } | ||
|
|
||
| if ($EnableVerboseLogging) { | ||
| Write-Host "[VERBOSE] Processing ItemGroup with $(@($packageVersions).Count) packages" | ||
| } | ||
|
|
||
| foreach ($packageVersion in $packageVersions) { | ||
| $packageId = $packageVersion.Include | ||
| $currentVersion = $packageVersion.Version | ||
| $preserveMajor = $packageVersion.PreserveMajor -eq "true" | ||
|
|
||
| if (-not $packageId -or -not $currentVersion) { | ||
| Write-Host "Skipping invalid PackageVersion: $($packageVersion.OuterXml)" | ||
| continue | ||
| } | ||
|
|
||
| if ($EnableVerboseLogging) { | ||
| Write-Host "[VERBOSE] Processing: $packageId (current: $currentVersion, preserveMajor: $preserveMajor)" | ||
| } | ||
|
|
||
| # Determine if we should include prerelease | ||
| $includePrerelease = Test-PreReleaseVersion -Version $currentVersion | ||
|
|
||
| if ($EnableVerboseLogging) { | ||
| Write-Host " [VERBOSE] Include prerelease: $includePrerelease" | ||
| } | ||
|
|
||
| # Get major version if needed | ||
| $majorVersion = "" | ||
| if ($preserveMajor) { | ||
| $majorVersion = $currentVersion.Split('.')[0] | ||
| if ($EnableVerboseLogging) { | ||
| Write-Host " [VERBOSE] Preserving major version: $majorVersion" | ||
| } | ||
| } | ||
|
|
||
| # Get latest version | ||
| $latestVersion = Get-LatestPackageVersion -PackageId $packageId -IncludePrerelease $includePrerelease -MajorVersion $majorVersion | ||
|
|
||
| if (-not $latestVersion) { | ||
| Write-Host "No update available for '$packageId'" | ||
| continue | ||
| } | ||
|
|
||
| # Compare versions | ||
| $selectedVersion = Get-LatestVersionFromString -First $currentVersion -Second $latestVersion | ||
|
|
||
| if ($EnableVerboseLogging) { | ||
| Write-Host " [VERBOSE] Version comparison: current=$currentVersion, latest=$latestVersion, selected=$selectedVersion" | ||
| } | ||
|
|
||
| if ($selectedVersion -ne $currentVersion) { | ||
| Write-Host "##[section]Updating '$packageId' from '$currentVersion' to '$latestVersion'" | ||
| $packageVersion.Version = $latestVersion | ||
| $updateCount++ | ||
| } else { | ||
| Write-Host "Package '$packageId' already at latest version '$currentVersion'" | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if ($updateCount -gt 0) { | ||
| Write-Host "##[section]Saving $updateCount package updates to $propsFile" | ||
| $xml.Save($propsFile) | ||
| Write-Host "Successfully updated $updateCount packages" | ||
| } else { | ||
| Write-Host "No package updates needed" | ||
| } | ||
|
|
||
| Write-Host "===============================================================================" | ||
| Write-Host "NuGet Package Update Complete" | ||
| Write-Host "===============================================================================" | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pre-release comparison uses a plain string compare on the suffix (
$firstVersionObject.Suffix -lt $secondVersionObject.Suffix). This can select the wrong “latest” version for common SemVer patterns (e.g.,-preview.10vs-preview.9). Use a SemVer-aware comparison for prerelease identifiers (or a NuGet version parser) so updates don’t regress to an older prerelease.