From 41ef772d639194e7e4baec5c65a34d3555afcc74 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 30 Sep 2026 18:52:36 +0000 Subject: [PATCH 1/5] Add Chocolatey version range resolution Co-authored-by: HeyItsGilbert <615265+HeyItsGilbert@users.noreply.github.com> --- PSDepend/PSDependScripts/Chocolatey.ps1 | 65 ++++++++++++++++++++----- Tests/Chocolatey.Type.Tests.ps1 | 52 ++++++++++++++++++++ 2 files changed, 104 insertions(+), 13 deletions(-) diff --git a/PSDepend/PSDependScripts/Chocolatey.ps1 b/PSDepend/PSDependScripts/Chocolatey.ps1 index cc55355..843e7bf 100644 --- a/PSDepend/PSDependScripts/Chocolatey.ps1 +++ b/PSDepend/PSDependScripts/Chocolatey.ps1 @@ -8,7 +8,9 @@ Relevant Dependency metadata: Name: The name of the package - Version: Used to identify existing installs meeting this criteria. Defaults to 'latest' + Version: Used to identify existing installs meeting this criteria. Defaults to 'latest'. + Also accepts a NuGet version range (e.g. '[2.2.3,3.0)', '[2.0,)', '(,3.0)'). + A bare version remains an exact version. Ranges resolve to the highest matching version. Source: Source Uri. Defaults to https://community.chocolatey.org/api/v2/ .PARAMETER Dependency @@ -127,19 +129,24 @@ function Get-ChocoInstalledPackage { Invoke-ExternalCommand @invokeExternalCommandSplat | ConvertFrom-Csv @convertFromCsvSplat } -function Get-ChocoLatestPackage { +function Get-ChocoPackage { [CmdletBinding()] param ( [string]$Name, [string]$Source, - [Management.Automation.PSCredential]$Credential + [Management.Automation.PSCredential]$Credential, + + [switch]$AllVersions ) # 'choco search' queries remote sources on both 1.x and 2.x; 'choco list' stopped # querying remote sources in Chocolatey 2.0 and rejects URL sources (issue #187) $chocoParams = @('search', "$Name", '--limit-output', '--exact') + if ($AllVersions.IsPresent) { + $chocoParams += '--all-versions' + } if ($Source) { $chocoParams += "--source='$Source'" } @@ -230,6 +237,16 @@ if (-not $Dependency.Source -or $Source -eq '') { $Credential = $Dependency.Credential +$versionRange = $null +if ($Version -ne 'latest') { + $versionRange = ConvertFrom-VersionRange -Version $Version + if (-not $versionRange) { + Write-Error "Could not parse version [$Version] for [$Name]; expected an exact version or a valid NuGet range." + return + } +} +$installVersion = $Version + if (-not (Get-Command -Name 'choco.exe' -ErrorAction SilentlyContinue)) { Write-Verbose "Chocolatey is not installed. Installing from [$ChocoInstallScriptUrl]" # download and run the Chocolatey script @@ -251,9 +268,19 @@ if (-not (Get-Command -Name 'choco.exe' -ErrorAction SilentlyContinue)) { # If this is a forced install we don't need to check anything, # just install the package version requested if ($Force.IsPresent -and $PSDependAction -contains 'Install') { + if ($versionRange -and -not $versionRange.IsExact) { + $availableVersions = (Get-ChocoPackage -Name $Name -Source $Source -Credential $Credential -AllVersions).Version + $installVersion = Resolve-VersionInRange -Candidate $availableVersions -Required $Version + if (-not $installVersion) { + Write-Error "No version of [$Name] at source [$Source] satisfies range [$Version]" + return + } + Write-Verbose "Resolved range [$Version] to version [$installVersion] for [$Name]" + } + $params = @{ Name = $Name - Version = $Version + Version = $installVersion Source = $Source Force = $Force.IsPresent } @@ -278,7 +305,7 @@ if ($existingVersion) { } # Specific version requested, and equal to current -if ($Version -ne 'latest' -and (Test-VersionEquality -ReferenceVersion $Version -DifferenceVersion $existingVersion)) { +if ($Version -ne 'latest' -and (Test-VersionInRange -Version $existingVersion -Required $Version)) { Write-Verbose "You have the requested version [$Version] of [$Name]" if ($PSDependAction -contains 'Test') { return $true @@ -287,7 +314,8 @@ if ($Version -ne 'latest' -and (Test-VersionEquality -ReferenceVersion $Version return } -# get the latest version from the source +# Resolve a range to the highest available version. Chocolatey requires an exact +# version for --version. $repoParams = @{ Name = $Name Source = $Source @@ -296,13 +324,24 @@ if ($Credential) { $repoParams.Credential = $Credential } -Write-Verbose "Getting latest package [$Name] version from source [$Source]." -$repositoryVersion = (Get-ChocoLatestPackage @repoParams).Version -if ($repositoryVersion) { - Write-Verbose "Found package [$Name] version [$repositoryVersion] on source [$Source]." +if ($versionRange -and -not $versionRange.IsExact) { + Write-Verbose "Getting available package [$Name] versions from source [$Source]." + $availableVersions = (Get-ChocoPackage @repoParams -AllVersions).Version + $installVersion = Resolve-VersionInRange -Candidate $availableVersions -Required $Version + if (-not $installVersion) { + Write-Error "No version of [$Name] at source [$Source] satisfies range [$Version]" + return + } + Write-Verbose "Resolved range [$Version] to version [$installVersion] for [$Name]" } else { - Write-Verbose "Package [$Name] not found on source [$Source]. Nothing more can be done." - return # cannot continue + Write-Verbose "Getting latest package [$Name] version from source [$Source]." + $repositoryVersion = (Get-ChocoPackage @repoParams).Version + if ($repositoryVersion) { + Write-Verbose "Found package [$Name] version [$repositoryVersion] on source [$Source]." + } else { + Write-Verbose "Package [$Name] not found on source [$Source]. Nothing more can be done." + return # cannot continue + } } # If the version in the remote repository is less than or equal to the version installed, then we have the latest already @@ -338,7 +377,7 @@ Write-Verbose "You do not have the version requested of [$Name]: Requested versi if ($PSDependAction -contains 'Install') { $params = @{ Name = $Name - Version = $Version + Version = $installVersion Source = $Source Force = $Force.IsPresent } diff --git a/Tests/Chocolatey.Type.Tests.ps1 b/Tests/Chocolatey.Type.Tests.ps1 index 5172891..197d879 100644 --- a/Tests/Chocolatey.Type.Tests.ps1 +++ b/Tests/Chocolatey.Type.Tests.ps1 @@ -70,4 +70,56 @@ Describe 'Chocolatey script' -Tag 'WindowsOnly' -Skip:$SkipUnsupported { ($Arguments -join ' ') -match "--username='feeduser'" -and ($Arguments -join ' ') -match "--password='feedpass'" } } + + Context 'NuGet version ranges' { + It 'Installs the highest available version that satisfies the range' { + $dep = New-PSDependFixture -DependencyName 'git' -DependencyType 'Chocolatey' -Version '[2.0.0,3.0.0)' + InModuleScope PSDepend -Parameters @{ Dep = $dep; ScriptPath = $script:ScriptPath } { + Mock Invoke-ExternalCommand { + 'git|1.9.0' + 'git|2.5.0' + 'git|3.0.0' + } -ParameterFilter { $Arguments -contains '--all-versions' } + + & $ScriptPath -Dependency $Dep + } + Should -Invoke -CommandName Invoke-ExternalCommand -ModuleName PSDepend -Times 1 -Exactly -ParameterFilter { + $Arguments -contains 'upgrade' -and $Arguments -contains "--version='2.5.0'" + } + } + + It 'Accepts an installed version that satisfies the range' { + $dep = New-PSDependFixture -DependencyName 'git' -DependencyType 'Chocolatey' -Version '[2.0.0,3.0.0)' + InModuleScope PSDepend -Parameters @{ Dep = $dep; ScriptPath = $script:ScriptPath } { + Mock Invoke-ExternalCommand { 'git|2.4.0' } -ParameterFilter { $Arguments -contains 'list' } + + & $ScriptPath -Dependency $Dep -PSDependAction Test + } | Should -BeTrue + Should -Invoke -CommandName Invoke-ExternalCommand -ModuleName PSDepend -Times 0 -Exactly -ParameterFilter { + $Arguments -contains 'search' + } + } + + It 'Skips installation when no available version satisfies the range' { + $dep = New-PSDependFixture -DependencyName 'git' -DependencyType 'Chocolatey' -Version '[2.0.0,3.0.0)' + InModuleScope PSDepend -Parameters @{ Dep = $dep; ScriptPath = $script:ScriptPath } { + Mock Invoke-ExternalCommand { 'git|1.9.0' } -ParameterFilter { $Arguments -contains '--all-versions' } + + & $ScriptPath -Dependency $Dep -ErrorAction SilentlyContinue + } + Should -Invoke -CommandName Invoke-ExternalCommand -ModuleName PSDepend -Times 0 -Exactly -ParameterFilter { + $Arguments -contains 'upgrade' + } + } + + It 'Skips installation for a malformed range' { + $dep = New-PSDependFixture -DependencyName 'git' -DependencyType 'Chocolatey' -Version '[2.0.0,3.0.0' + InModuleScope PSDepend -Parameters @{ Dep = $dep; ScriptPath = $script:ScriptPath } { + & $ScriptPath -Dependency $Dep -ErrorAction SilentlyContinue + } + Should -Invoke -CommandName Invoke-ExternalCommand -ModuleName PSDepend -Times 0 -Exactly -ParameterFilter { + $Arguments -contains 'upgrade' + } + } + } } From 31e152c7b96faa329b349bf47d27ab947aa5af4c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 30 Sep 2026 18:54:56 +0000 Subject: [PATCH 2/5] Cover forced Chocolatey range installs Co-authored-by: HeyItsGilbert <615265+HeyItsGilbert@users.noreply.github.com> --- Tests/Chocolatey.Type.Tests.ps1 | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Tests/Chocolatey.Type.Tests.ps1 b/Tests/Chocolatey.Type.Tests.ps1 index 197d879..c57e92a 100644 --- a/Tests/Chocolatey.Type.Tests.ps1 +++ b/Tests/Chocolatey.Type.Tests.ps1 @@ -88,6 +88,23 @@ Describe 'Chocolatey script' -Tag 'WindowsOnly' -Skip:$SkipUnsupported { } } + It 'Resolves a range before a forced installation' { + $dep = New-PSDependFixture -DependencyName 'git' -DependencyType 'Chocolatey' -Version '[2.0.0,3.0.0)' + InModuleScope PSDepend -Parameters @{ Dep = $dep; ScriptPath = $script:ScriptPath } { + Mock Invoke-ExternalCommand { + 'git|2.4.0' + 'git|2.8.0' + } -ParameterFilter { $Arguments -contains '--all-versions' } + + & $ScriptPath -Dependency $Dep -Force + } + Should -Invoke -CommandName Invoke-ExternalCommand -ModuleName PSDepend -Times 1 -Exactly -ParameterFilter { + $Arguments -contains 'upgrade' -and + $Arguments -contains "--version='2.8.0'" -and + $Arguments -contains '--force' + } + } + It 'Accepts an installed version that satisfies the range' { $dep = New-PSDependFixture -DependencyName 'git' -DependencyType 'Chocolatey' -Version '[2.0.0,3.0.0)' InModuleScope PSDepend -Parameters @{ Dep = $dep; ScriptPath = $script:ScriptPath } { From 07d0bbaa0121f0bbb46dfbb01236d5a20c61f29d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 30 Sep 2026 18:57:04 +0000 Subject: [PATCH 3/5] Handle exact ranges and failed range tests Co-authored-by: HeyItsGilbert <615265+HeyItsGilbert@users.noreply.github.com> --- PSDepend/PSDependScripts/Chocolatey.ps1 | 15 ++++++++++++++- Tests/Chocolatey.Type.Tests.ps1 | 16 +++++++++++++--- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/PSDepend/PSDependScripts/Chocolatey.ps1 b/PSDepend/PSDependScripts/Chocolatey.ps1 index 843e7bf..bccc40b 100644 --- a/PSDepend/PSDependScripts/Chocolatey.ps1 +++ b/PSDepend/PSDependScripts/Chocolatey.ps1 @@ -242,10 +242,17 @@ if ($Version -ne 'latest') { $versionRange = ConvertFrom-VersionRange -Version $Version if (-not $versionRange) { Write-Error "Could not parse version [$Version] for [$Name]; expected an exact version or a valid NuGet range." + if ($PSDependAction -contains 'Test') { + return $false + } return } } -$installVersion = $Version +$installVersion = if ($versionRange -and $versionRange.IsExact) { + $versionRange.Exact +} else { + $Version +} if (-not (Get-Command -Name 'choco.exe' -ErrorAction SilentlyContinue)) { Write-Verbose "Chocolatey is not installed. Installing from [$ChocoInstallScriptUrl]" @@ -273,6 +280,9 @@ if ($Force.IsPresent -and $PSDependAction -contains 'Install') { $installVersion = Resolve-VersionInRange -Candidate $availableVersions -Required $Version if (-not $installVersion) { Write-Error "No version of [$Name] at source [$Source] satisfies range [$Version]" + if ($PSDependAction -contains 'Test') { + return $false + } return } Write-Verbose "Resolved range [$Version] to version [$installVersion] for [$Name]" @@ -330,6 +340,9 @@ if ($versionRange -and -not $versionRange.IsExact) { $installVersion = Resolve-VersionInRange -Candidate $availableVersions -Required $Version if (-not $installVersion) { Write-Error "No version of [$Name] at source [$Source] satisfies range [$Version]" + if ($PSDependAction -contains 'Test') { + return $false + } return } Write-Verbose "Resolved range [$Version] to version [$installVersion] for [$Name]" diff --git a/Tests/Chocolatey.Type.Tests.ps1 b/Tests/Chocolatey.Type.Tests.ps1 index c57e92a..6805d4e 100644 --- a/Tests/Chocolatey.Type.Tests.ps1 +++ b/Tests/Chocolatey.Type.Tests.ps1 @@ -105,6 +105,16 @@ Describe 'Chocolatey script' -Tag 'WindowsOnly' -Skip:$SkipUnsupported { } } + It 'Passes a bracketed exact range as a concrete version' { + $dep = New-PSDependFixture -DependencyName 'git' -DependencyType 'Chocolatey' -Version '[2.0.0]' + InModuleScope PSDepend -Parameters @{ Dep = $dep; ScriptPath = $script:ScriptPath } { + & $ScriptPath -Dependency $Dep -Force + } + Should -Invoke -CommandName Invoke-ExternalCommand -ModuleName PSDepend -Times 1 -Exactly -ParameterFilter { + $Arguments -contains 'upgrade' -and $Arguments -contains "--version='2.0.0'" + } + } + It 'Accepts an installed version that satisfies the range' { $dep = New-PSDependFixture -DependencyName 'git' -DependencyType 'Chocolatey' -Version '[2.0.0,3.0.0)' InModuleScope PSDepend -Parameters @{ Dep = $dep; ScriptPath = $script:ScriptPath } { @@ -117,13 +127,13 @@ Describe 'Chocolatey script' -Tag 'WindowsOnly' -Skip:$SkipUnsupported { } } - It 'Skips installation when no available version satisfies the range' { + It 'Returns false when no available version satisfies the range during testing' { $dep = New-PSDependFixture -DependencyName 'git' -DependencyType 'Chocolatey' -Version '[2.0.0,3.0.0)' InModuleScope PSDepend -Parameters @{ Dep = $dep; ScriptPath = $script:ScriptPath } { Mock Invoke-ExternalCommand { 'git|1.9.0' } -ParameterFilter { $Arguments -contains '--all-versions' } - & $ScriptPath -Dependency $Dep -ErrorAction SilentlyContinue - } + & $ScriptPath -Dependency $Dep -PSDependAction Test -ErrorAction SilentlyContinue + } | Should -BeFalse Should -Invoke -CommandName Invoke-ExternalCommand -ModuleName PSDepend -Times 0 -Exactly -ParameterFilter { $Arguments -contains 'upgrade' } From ec775064c288425f145614e4de7413b0689e7365 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 30 Sep 2026 19:14:14 +0000 Subject: [PATCH 4/5] Fix pinned Pester build loading Co-authored-by: HeyItsGilbert <615265+HeyItsGilbert@users.noreply.github.com> --- CHANGELOG.md | 13 +++++++++++++ PSDepend/PSDepend.psd1 | 2 +- build.ps1 | 20 +++++++++++++++----- psakeFile.ps1 | 25 +++++++++++++++++++++++++ requirements.psd1 | 8 ++++---- 5 files changed, 58 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7727b8a..7d3ec55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.5.1] - 2026-09-30 + +### Added + +- NuGet version-range support for Chocolatey dependencies. Ranges resolve to + the highest matching package version before installation. + +### Fixed + +- Build initialization now explicitly loads the pinned Pester 5 version before + importing PowerShellBuild and uses a Pester 5-compatible test task, preventing + a newer installed Pester version from being loaded. + ## [0.5.0] - 2026-08-16 ### Added diff --git a/PSDepend/PSDepend.psd1 b/PSDepend/PSDepend.psd1 index adf4c56..0e65876 100644 --- a/PSDepend/PSDepend.psd1 +++ b/PSDepend/PSDepend.psd1 @@ -4,7 +4,7 @@ RootModule = 'PSDepend.psm1' # Version number of this module. - ModuleVersion = '0.5.0' + ModuleVersion = '0.5.1' # ID used to uniquely identify this module GUID = '63ea9e2a-320d-43ff-a11a-4930ca03cce6' diff --git a/build.ps1 b/build.ps1 index 3ec4120..aa06165 100644 --- a/build.ps1 +++ b/build.ps1 @@ -39,7 +39,12 @@ param( ) $ErrorActionPreference = 'Stop' -$psakeFile = './psakeFile.ps1' +$requirementsFile = Join-Path $PSScriptRoot 'requirements.psd1' +$psakeFile = Join-Path $PSScriptRoot 'psakeFile.ps1' + +$requirements = Import-PowerShellDataFile -Path $requirementsFile +$pesterRequirement = $requirements.Pester +$requirements.Remove('Pester') if ($Bootstrap) { if (-not (Get-PackageProvider -Name NuGet -ErrorAction SilentlyContinue)) { @@ -49,13 +54,18 @@ if ($Bootstrap) { if (-not (Get-Module -Name PSDepend -ListAvailable)) { Install-Module -Name PSDepend -Repository PSGallery -Scope CurrentUser -Force -RequiredVersion '0.3.8' } - Import-Module -Name PSDepend -Verbose:$false - Invoke-PSDepend -Path './requirements.psd1' -Install -Import -Force -WarningAction SilentlyContinue } -else { - Invoke-PSDepend -Path './requirements.psd1' -Import -Force -WarningAction SilentlyContinue + +Import-Module -Name PSDepend -Verbose:$false +if ($Bootstrap) { + Invoke-PSDepend -Path $requirementsFile -Install -Force -WarningAction SilentlyContinue } +# PowerShellBuild requires Pester without an upper bound. Load the pinned version +# first, then import the remaining dependencies without re-importing Pester. +Import-Module -Name Pester -RequiredVersion $pesterRequirement.Version +Invoke-PSDepend -InputObject $requirements -Import -Force -WarningAction SilentlyContinue + if ($PSCmdlet.ParameterSetName -eq 'Help') { Get-PSakeScriptTasks -BuildFile $psakeFile | Format-Table -Property Name, Description, Alias, DependsOn diff --git a/psakeFile.ps1 b/psakeFile.ps1 index 52d3f4c..c9a4ebc 100644 --- a/psakeFile.ps1 +++ b/psakeFile.ps1 @@ -30,6 +30,31 @@ Properties { # Skips BuildHelp (GenerateMarkdown) — doc generation is not needed in the test pipeline # and Build-PSBuildMarkdown has a Remove-Module scope bug specific to PSDepend. $PSBBuildDependency = @('StageFiles') +$PSBTestDependency = @('Pester5', 'Analyze') + +Task Pester5 -Depends $PSBBuildDependency { + $moduleManifest = Join-Path $PSBPreference.Build.ModuleOutDir "$($PSBPreference.General.ModuleName).psd1" + + Import-Module -Name $moduleManifest -Force + Push-Location -LiteralPath $PSBPreference.Test.RootDir + try { + $configuration = [PesterConfiguration]::Default + $configuration.Output.Verbosity = 'Detailed' + $configuration.Run.PassThru = $true + $configuration.TestResult.Enabled = $true + $configuration.TestResult.OutputPath = $PSBPreference.Test.OutputFile + $configuration.TestResult.OutputFormat = $PSBPreference.Test.OutputFormat + + $testResult = Invoke-Pester -Configuration $configuration + if ($testResult.Result -eq 'Failed') { + throw 'One or more Pester tests failed.' + } + } + finally { + Pop-Location + Remove-Module -Name $PSBPreference.General.ModuleName -ErrorAction SilentlyContinue + } +} -Description 'Executes Pester 5 tests' # Override with: .\build.ps1 InstallLocal -Properties @{PreReleaseLabel='rc1'} Task InstallLocal -Depends StageFiles { diff --git a/requirements.psd1 b/requirements.psd1 index 266175e..b503902 100644 --- a/requirements.psd1 +++ b/requirements.psd1 @@ -5,15 +5,15 @@ 'psake' = @{ Version = '4.9.1' } - 'PowerShellBuild' = @{ - Version = '0.7.3' - } 'Pester' = @{ - Version = '6.1.0' + Version = '5.9.0' Parameters = @{ SkipPublisherCheck = $true } } + 'PowerShellBuild' = @{ + Version = '0.8.2' + } 'PSScriptAnalyzer' = @{ Version = '1.19.1' } From 97208db5978e3c9d5c5fad11c3ba32f7a1bc01bd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 30 Sep 2026 19:18:40 +0000 Subject: [PATCH 5/5] Bump PSDepend to version 0.6.0 Co-authored-by: HeyItsGilbert <615265+HeyItsGilbert@users.noreply.github.com> --- CHANGELOG.md | 2 +- PSDepend/PSDepend.psd1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d3ec55..95713e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [0.5.1] - 2026-09-30 +## [0.6.0] - 2026-09-30 ### Added diff --git a/PSDepend/PSDepend.psd1 b/PSDepend/PSDepend.psd1 index 0e65876..e97df8a 100644 --- a/PSDepend/PSDepend.psd1 +++ b/PSDepend/PSDepend.psd1 @@ -4,7 +4,7 @@ RootModule = 'PSDepend.psm1' # Version number of this module. - ModuleVersion = '0.5.1' + ModuleVersion = '0.6.0' # ID used to uniquely identify this module GUID = '63ea9e2a-320d-43ff-a11a-4930ca03cce6'