From 10a0d3493c114884594c823ea15ad558d1feccf1 Mon Sep 17 00:00:00 2001 From: Justin Grote Date: Fri, 15 Dec 2023 17:32:06 -0800 Subject: [PATCH 1/5] Initial Fixes --- ModuleFast.psm1 | 89 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 82 insertions(+), 7 deletions(-) diff --git a/ModuleFast.psm1 b/ModuleFast.psm1 index dce1071..057535e 100644 --- a/ModuleFast.psm1 +++ b/ModuleFast.psm1 @@ -566,7 +566,8 @@ function Install-ModuleFastHelper { [Dictionary[Task, hashtable]]$taskMap = @{} [List[Task[Stream]]]$streamTasks = foreach ($module in $ModuleToInstall) { - $installPath = Join-Path $Destination $module.Name $module.ModuleVersion.ToString().Split('+-')[0] + + $installPath = Join-Path $Destination $module.Name (ConvertTo-FolderVersion $module.ModuleVersion) #TODO: Do a get-localmodule check here if (Test-Path $installPath) { @@ -580,7 +581,7 @@ function Install-ModuleFastHelper { } } - Write-Verbose "${module}: Downloading to $($module.Location)" + Write-Verbose "${module}: Downloading from $($module.Location)" if (-not $module.Location) { throw "$module`: No Download Link found. This is a bug" } @@ -604,8 +605,10 @@ function Install-ModuleFastHelper { $context.fetchStream = $stream $streamTasks.RemoveAt($thisTaskIndex) + #We are going to extract these straight out of memory, so we don't need to write the nupkg to disk Write-Verbose "$($context.Module): Extracting to $($context.installPath)" + # This is a sync process and we want to do it in parallel, hence the threadjob $installJob = Start-ThreadJob -ThrottleLimit 8 { param( @@ -614,11 +617,16 @@ function Install-ModuleFastHelper { ) $installPath = $context.InstallPath #TODO: Add a ".incomplete" marker file to the folder and remove it when done. This will allow us to detect failed installations + $zip = [IO.Compression.ZipArchive]::new($stream, 'Read') [IO.Compression.ZipFileExtensions]::ExtractToDirectory($zip, $installPath) - Write-Verbose "Cleanup Nuget Files in $installPath" + #FIXME: Output inside a threadjob is not surfaced to the user. + Write-Debug "Cleanup Nuget Files in $installPath" if (-not $installPath) { throw 'ModuleDestination was not set. This is a bug, report it' } - Remove-Item -Path $installPath -Include '_rels', 'package', '*.nuspec' -Recurse -Force + Get-ChildItem -Path $installPath | Where-Object { + $_.Name -in '_rels', 'package', '[Content_Types].xml' -or + $_.Name.EndsWith('.nuspec') + } | Remove-Item -Force -Recurse ($zip).Dispose() ($stream).Dispose() return $context @@ -1168,10 +1176,14 @@ function Find-LocalModule { $manifestName = "$($ModuleSpec.Name).psd1" #We can attempt a fast-search for modules if the ModuleSpec is for a specific version - if ($ModuleSpec.Required) { - #TODO: Split this off into Find-RequiredLocalModule + $required = $ModuleSpec.Required + if ($required) { + + #If there is a prerelease, we will fetch the folder where the prerelease might live, and verify the manifest later. + [Version]$moduleVersion = $ModuleSpec.Prerelease ? + (ConvertTo-FolderVersion $required) : + $required.OriginalVersion - $moduleVersion = $ModuleSpec.Required.OriginalVersion $moduleFolder = Join-Path $moduleBaseDir $moduleVersion $manifestPath = Join-Path $moduleFolder $manifestName @@ -1293,6 +1305,69 @@ function Get-StringHash ([string]$String, [string]$Algorithm = 'SHA256') { (Get-FileHash -InputStream ([MemoryStream]::new([Encoding]::UTF8.GetBytes($String))) -Algorithm $algorithm).Hash } +#Imports a powershell data file or json file for the required spec configuration. +filter ConvertFrom-RequiredSpec { + [CmdletBinding(DefaultParameterSetName = 'File')] + [OutputType([ModuleFastSpec[]])] + param( + [Parameter(Mandatory, ParameterSetName = 'File')][string]$RequiredSpecPath, + [Parameter(Mandatory, ParameterSetName = 'Object')][object]$RequiredSpec + ) + $ErrorActionPreference = 'Stop' + + if ($RequiredSpecPath) { + $uri = $RequiredSpecPath -as [Uri] + + $RequiredData = if ($uri.scheme -in 'http', 'https') { + [string]$content = (Invoke-WebRequest -Uri $uri).Content + if ($content.StartsWith('@{')) { + $tempFile = [io.path]::GetTempFileName() + $content > $tempFile + Import-PowerShellDataFile -Path $tempFile + } else { + ConvertFrom-Json $content -Depth 5 + } + } else { + #Assume this is a local if a URL above didn't match + $resolvedPath = Resolve-Path $RequiredSpecPath + $extension = [Path]::GetExtension($resolvedPath) + if ($extension -eq '.psd1') { + Import-PowerShellDataFile -Path $resolvedPath + } elseif ($extension -in '.json', '.jsonc') { + Get-Content -Path $resolvedPath -Raw | ConvertFrom-Json -Depth 5 + } else { + throw [NotSupportedException]'Only .psd1 and .json files are supported to import to this command' + } + } + } + + + + if ($RequiredData -is [IDictionary]) { + foreach ($kv in $RequiredData.GetEnumerator()) { + if ($kv.Value -is [IDictionary]) { + throw [NotImplementedException]'TODO: PSResourceGet/PSDepend full syntax' + } + if ($kv.Value -isnot [string]) { + throw [NotSupportedException]'Only strings and hashtables are supported on the right hand side of the = operator.' + } + if ($kv.Value -eq 'latest') { + [ModuleFastSpec]"$($kv.Name)" + continue + } + if ($kv.Value -as [NuGetVersion]) { + [ModuleFastSpec]"$($kv.Name)@$($kv.Value)" + continue + } + + #All other potential options (<=, @, :, etc.) are a direct merge + [ModuleFastSpec]"$($kv.Name)$($kv.Value)" + } + } else { + throw [NotImplementedException]'TODO: Support simple array based json strings' + } +} + #endregion Helpers From 1f0a6e713be404fed51f1bdb1d7ebf1fc245c6c9 Mon Sep 17 00:00:00 2001 From: Justin Grote Date: Sat, 16 Dec 2023 09:10:13 -0800 Subject: [PATCH 2/5] Switch to IWR for raw.githubusercontent compatibility --- ModuleFast.ps1 | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ModuleFast.ps1 b/ModuleFast.ps1 index 838710d..b2b45ba 100644 --- a/ModuleFast.ps1 +++ b/ModuleFast.ps1 @@ -65,9 +65,7 @@ if (Get-Module $ModuleName) { Write-Debug "Fetching $ModuleName from $Uri" $ProgressPreference = 'SilentlyContinue' try { - $httpClient = [HttpClient]::new() - $httpClient.DefaultRequestHeaders.AcceptEncoding.Add('gzip') - $response = $httpClient.GetStringAsync($Uri).GetAwaiter().GetResult() + $response = (Invoke-WebRequest $uri).Content } catch { $PSItem.ErrorDetails = "Failed to fetch $ModuleName from $Uri`: $PSItem" $PSCmdlet.ThrowTerminatingError($PSItem) From d651ddb69829b272c8085a916b920cb2701f978f Mon Sep 17 00:00:00 2001 From: Justin Grote Date: Sat, 16 Dec 2023 09:44:33 -0800 Subject: [PATCH 3/5] Fixup prelease detection logic --- ModuleFast.psm1 | 67 ++++++++++++++++++++++++++++---------------- ModuleFast.tests.ps1 | 20 +++++++------ 2 files changed, 55 insertions(+), 32 deletions(-) diff --git a/ModuleFast.psm1 b/ModuleFast.psm1 index 057535e..1a0b666 100644 --- a/ModuleFast.psm1 +++ b/ModuleFast.psm1 @@ -411,11 +411,15 @@ function Get-ModuleFastPlan { #TODO: Dedupe as a function with above if ($entries) { - [SortedSet[NuGetVersion]]$inlinedVersions = $entries.version + [SortedSet[NuGetVersion]]$pageVersions = $entries.version - foreach ($candidate in $inlinedVersions.Reverse()) { + foreach ($candidate in $pageVersions.Reverse()) { #Skip Prereleases unless explicitly requested - if (($candidate.IsPrerelease -or $candidate.HasMetadata) -and -not $Prerelease) { continue } + if (($candidate.IsPrerelease -or $candidate.HasMetadata) -and -not ($currentModuleSpec.PreRelease -or $Prerelease)) { + Write-Debug "Skipping candidate $candidate because it is a prerelease and prerelease was not specified either with the -Prerelease parameter or with a ! on the module name." + continue + } + if ($currentModuleSpec.SatisfiedBy($candidate)) { Write-Debug "$currentModuleSpec`: Found satisfying version $candidate in the additional pages." $matchingEntry = $entries | Where-Object version -EQ $candidate @@ -567,17 +571,34 @@ function Install-ModuleFastHelper { [List[Task[Stream]]]$streamTasks = foreach ($module in $ModuleToInstall) { - $installPath = Join-Path $Destination $module.Name (ConvertTo-FolderVersion $module.ModuleVersion) + $installPath = Join-Path $Destination $module.Name (Resolve-FolderVersion $module.ModuleVersion) #TODO: Do a get-localmodule check here if (Test-Path $installPath) { - #TODO: Check for a corrupted module - #TODO: Prerelease checking - if (-not $Update) { - throw "${module}: Module already exists at $installPath and -Update wasn't specified. This is a bug" + $existingManifestPath = try { + Resolve-Path (Join-Path $installPath "$($module.Name).psd1") -ErrorAction Stop + } catch [ActionPreferenceStopException] { + throw "$module`: Existing module folder found at $installPath but the manifest could not be found. This is likely a corrupted or missing module and should be fixed manually." + } + + #TODO: Dedupe all import-powershelldatafile operations to a function ideally + $existingModuleMetadata = Import-PowerShellDataFile $existingManifestPath + $existingVersion = [NugetVersion]::new( + $existingModuleMetadata.ModuleVersion, + $existingModuleMetadata.privatedata.psdata.prerelease + ) + + if (-not $existingVersion.IsPrerelease) { + throw [InvalidOperationException]"${module}: A non-prerelease module with version $existingVersion already exists at $installPath. This is a bug because the local module resolution should have already excluded this." + } + + #Do a prerelease evaluation + if ($module.ModuleVersion -lt $existingVersion) { + #TODO: Add force to override + throw [NotSupportedException]"$module`: Existing module folder found at $installPath but the module is a prerelease and the existing version $existingVersion is newer than the requested version $($module.ModuleVersion). If you wish to continue, please remove the existing module folder or modify your specification and try again." } else { - Write-Verbose "${module}: Module already exists at $installPath but -Update was specified. This can happen because we did in fact have the latest version. Skipping." - continue + Write-Warning "$module`: Existing prerelease version $existingVersion is older than our planned version $($module.ModuleVersion) so we are replacing the existing module with this version." + Remove-Item $installPath -Force -Recurse } } @@ -586,15 +607,17 @@ function Install-ModuleFastHelper { throw "$module`: No Download Link found. This is a bug" } - $fetchTask = $httpClient.GetStreamAsync($module.Location, $CancellationToken) + $streamTask = $httpClient.GetStreamAsync($module.Location, $CancellationToken) $context = @{ Module = $module InstallPath = $installPath } - $taskMap.Add($fetchTask, $context) - $fetchTask + $taskMap.Add($streamTask, $context) + $streamTask } + #We are going to extract these straight out of memory, so we don't need to write the nupkg to disk + Write-Verbose "$($context.Module): Extracting to $($context.installPath)" [List[Job2]]$installJobs = while ($streamTasks.count -gt 0) { $noTasksYetCompleted = -1 [int]$thisTaskIndex = [Task]::WaitAny($streamTasks, 500) @@ -605,10 +628,6 @@ function Install-ModuleFastHelper { $context.fetchStream = $stream $streamTasks.RemoveAt($thisTaskIndex) - - #We are going to extract these straight out of memory, so we don't need to write the nupkg to disk - Write-Verbose "$($context.Module): Extracting to $($context.installPath)" - # This is a sync process and we want to do it in parallel, hence the threadjob $installJob = Start-ThreadJob -ThrottleLimit 8 { param( @@ -1134,8 +1153,6 @@ function Add-DestinationToPSModulePath { } } - - function Find-LocalModule { [OutputType([ModuleFastInfo])] <# @@ -1180,9 +1197,7 @@ function Find-LocalModule { if ($required) { #If there is a prerelease, we will fetch the folder where the prerelease might live, and verify the manifest later. - [Version]$moduleVersion = $ModuleSpec.Prerelease ? - (ConvertTo-FolderVersion $required) : - $required.OriginalVersion + [Version]$moduleVersion = Resolve-FolderVersion $required $moduleFolder = Join-Path $moduleBaseDir $moduleVersion $manifestPath = Join-Path $moduleFolder $manifestName @@ -1341,8 +1356,6 @@ filter ConvertFrom-RequiredSpec { } } - - if ($RequiredData -is [IDictionary]) { foreach ($kv in $RequiredData.GetEnumerator()) { if ($kv.Value -is [IDictionary]) { @@ -1368,6 +1381,12 @@ filter ConvertFrom-RequiredSpec { } } +filter Resolve-FolderVersion([NuGetVersion]$version) { + if ($version.IsLegacyVersion) { + return $version.version + } + [Version]::new($version.Major, $version.Minor, $version.Patch) +} #endregion Helpers diff --git a/ModuleFast.tests.ps1 b/ModuleFast.tests.ps1 index 118099c..2c1d453 100644 --- a/ModuleFast.tests.ps1 +++ b/ModuleFast.tests.ps1 @@ -229,7 +229,7 @@ Describe 'Get-ModuleFastPlan' -Tag 'E2E' { Spec = 'PrereleaseTest!' Check = { $actual.Name | Should -Be 'PrereleaseTest' - $actual.ModuleVersion | Should -Be '0.0.2-newerversion' + $actual.ModuleVersion | Should -Be '0.0.2-prerelease' } ModuleName = 'PrereleaseTest' }, @@ -237,7 +237,7 @@ Describe 'Get-ModuleFastPlan' -Tag 'E2E' { Spec = '!PrereleaseTest' Check = { $actual.Name | Should -Be 'PrereleaseTest' - $actual.ModuleVersion | Should -Be '0.0.2-newerversion' + $actual.ModuleVersion | Should -Be '0.0.2-prerelease' } ModuleName = 'PrereleaseTest' }, @@ -245,7 +245,7 @@ Describe 'Get-ModuleFastPlan' -Tag 'E2E' { Spec = 'PrereleaseTest!<0.0.1' Check = { $actual.Name | Should -Be 'PrereleaseTest' - $actual.ModuleVersion | Should -Be '0.0.1-newerversion' + $actual.ModuleVersion | Should -Be '0.0.1-prerelease' } ModuleName = 'PrereleaseTest' } @@ -316,7 +316,7 @@ Describe 'Get-ModuleFastPlan' -Tag 'E2E' { $actual = 'Az.Accounts!', 'PrereleaseTest' | Get-ModuleFastPlan -PreRelease $actual | Should -HaveCount 2 $actual | Where-Object Name -EQ 'PrereleaseTest' | ForEach-Object { - $PSItem.ModuleVersion | Should -Be '0.0.2-newerversion' + $PSItem.ModuleVersion | Should -Be '0.0.2-prerelease' } } } @@ -353,11 +353,11 @@ Describe 'Get-ModuleFastPlan' -Tag 'E2E' { } It 'Shows Prerelease Modules if Prerelease is specified' { $actual = Get-ModuleFastPlan 'PrereleaseTest' -PreRelease - $actual.ModuleVersion | Should -Be '0.0.2-newerversion' + $actual.ModuleVersion | Should -Be '0.0.2-prerelease' } It 'Detects Prerelease even if Prerelease not specified' { - $actual = Get-ModuleFastPlan 'PrereleaseTest@0.0.2-newerversion' - $actual.ModuleVersion | Should -Be '0.0.2-newerversion' + $actual = Get-ModuleFastPlan 'PrereleaseTest@0.0.2-prerelease' + $actual.ModuleVersion | Should -Be '0.0.2-prerelease' } } @@ -404,7 +404,7 @@ Describe 'Install-ModuleFast' -Tag 'E2E' { Get-Item $installTempPath\Az.Accounts\*\Az.Accounts.psd1 | Should -Not -BeNullOrEmpty } It '4 section version numbers (VMware.PowerCLI)' { - Install-ModuleFast @imfParams 'VMware.VimAutomation.Common' + Install-ModuleFast @imfParams 'VMware.VimAutomation.Common@13.2.0.22643733' Get-Item $installTempPath\VMware*\*\*.psd1 | ForEach-Object { $moduleFolderVersion = $_ | Split-Path | Split-Path -Leaf Import-PowerShellDataFile -Path $_.FullName | Select-Object -ExpandProperty ModuleVersion | Should -Be $moduleFolderVersion @@ -481,4 +481,8 @@ Describe 'Install-ModuleFast' -Tag 'E2E' { | Select-Object -First 1 | Should -BeGreaterThan ([version]'5.0.0') } + It 'Errors trying to install prerelease over regular module' { + Install-ModuleFast @imfParams 'PrereleaseTest@0.0.1' + Install-ModuleFast @imfParams 'PrereleaseTest@0.0.1-prerelease' + } } From e43cd38ab270549c00dda6364a49643b837ab899 Mon Sep 17 00:00:00 2001 From: Justin Grote Date: Sat, 16 Dec 2023 10:11:02 -0800 Subject: [PATCH 4/5] Fixup Installation Logic for Prereleases (failed case still present) --- ModuleFast.psm1 | 16 ++++++++++------ ModuleFast.tests.ps1 | 6 ++++++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/ModuleFast.psm1 b/ModuleFast.psm1 index 1a0b666..4a8c6dc 100644 --- a/ModuleFast.psm1 +++ b/ModuleFast.psm1 @@ -588,16 +588,20 @@ function Install-ModuleFastHelper { $existingModuleMetadata.privatedata.psdata.prerelease ) - if (-not $existingVersion.IsPrerelease) { - throw [InvalidOperationException]"${module}: A non-prerelease module with version $existingVersion already exists at $installPath. This is a bug because the local module resolution should have already excluded this." - } - #Do a prerelease evaluation + if ($module.ModuleVersion -eq $existingVersion) { + if ($Update) { + Write-Verbose "${module}: Existing module found at $installPath and its version $existingVersion is the same as the requested version. -Update was specified so we are assuming that the discovered online version is the same as the local version and skipping this module." + continue + } else { + throw [System.NotImplementedException]"${module}: Existing module found at $installPath and its version $existingVersion is the same as the requested version. This is probably a bug because it should have been detected by localmodule detection but we should probably allow this if -Update or -Force is specified..." + } + } if ($module.ModuleVersion -lt $existingVersion) { #TODO: Add force to override - throw [NotSupportedException]"$module`: Existing module folder found at $installPath but the module is a prerelease and the existing version $existingVersion is newer than the requested version $($module.ModuleVersion). If you wish to continue, please remove the existing module folder or modify your specification and try again." + throw [NotSupportedException]"${module}: Existing module found at $installPath and its version $existingVersion is newer than the requested prerelease version $($module.ModuleVersion). If you wish to continue, please remove the existing module folder or modify your specification and try again." } else { - Write-Warning "$module`: Existing prerelease version $existingVersion is older than our planned version $($module.ModuleVersion) so we are replacing the existing module with this version." + Write-Warning "${module}: Existing prerelease version $existingVersion is older than our planned version $($module.ModuleVersion) so we are replacing the existing module with this version." Remove-Item $installPath -Force -Recurse } } diff --git a/ModuleFast.tests.ps1 b/ModuleFast.tests.ps1 index 2c1d453..30e5bc7 100644 --- a/ModuleFast.tests.ps1 +++ b/ModuleFast.tests.ps1 @@ -483,6 +483,12 @@ Describe 'Install-ModuleFast' -Tag 'E2E' { } It 'Errors trying to install prerelease over regular module' { Install-ModuleFast @imfParams 'PrereleaseTest@0.0.1' + { Install-ModuleFast @imfParams 'PrereleaseTest@0.0.1-prerelease' } + | Should -Throw '*is newer than the requested prerelease version*' + } + It 'Succeeds installing regular module over prerelease module' { Install-ModuleFast @imfParams 'PrereleaseTest@0.0.1-prerelease' + Install-ModuleFast @imfParams 'PrereleaseTest@0.0.1' -WarningVariable actual *>&1 | Out-Null + $actual | Should -Not -BeNullOrEmpty -Because 'it should warn about installing regular module over prerelease module but proceed anyways' } } From a7deecc5d966f30df93a8072a449f4fe5ae75a40 Mon Sep 17 00:00:00 2001 From: Justin Grote Date: Sat, 16 Dec 2023 10:26:51 -0800 Subject: [PATCH 5/5] Fix bad prerelease reference and add more tests --- ModuleFast.psm1 | 13 +++++++------ ModuleFast.tests.ps1 | 14 ++++++++++++-- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/ModuleFast.psm1 b/ModuleFast.psm1 index 4a8c6dc..463220f 100644 --- a/ModuleFast.psm1 +++ b/ModuleFast.psm1 @@ -594,14 +594,14 @@ function Install-ModuleFastHelper { Write-Verbose "${module}: Existing module found at $installPath and its version $existingVersion is the same as the requested version. -Update was specified so we are assuming that the discovered online version is the same as the local version and skipping this module." continue } else { - throw [System.NotImplementedException]"${module}: Existing module found at $installPath and its version $existingVersion is the same as the requested version. This is probably a bug because it should have been detected by localmodule detection but we should probably allow this if -Update or -Force is specified..." + throw [System.NotImplementedException]"${module}: Existing module found at $installPath and its version $existingVersion is the same as the requested version. This is probably a bug because it should have been detected by localmodule detection. Use -Update to override..." } } if ($module.ModuleVersion -lt $existingVersion) { #TODO: Add force to override throw [NotSupportedException]"${module}: Existing module found at $installPath and its version $existingVersion is newer than the requested prerelease version $($module.ModuleVersion). If you wish to continue, please remove the existing module folder or modify your specification and try again." } else { - Write-Warning "${module}: Existing prerelease version $existingVersion is older than our planned version $($module.ModuleVersion) so we are replacing the existing module with this version." + Write-Warning "${module}: Planned version $($module.ModuleVersion) is newer than existing prerelease version $existingVersion so we will overwrite." Remove-Item $installPath -Force -Recurse } } @@ -1285,13 +1285,14 @@ function Find-LocalModule { continue } - [string]$prereleaseData = $manifestData.PreRelease - - [NuGetVersion]$manifestVersion = [NuGetVersion]::new($manifestVersionData, $prereleaseData, $null) + [NuGetVersion]$manifestVersion = [NuGetVersion]::new( + $manifestVersionData, + $manifestData.PrivateData.PSData.Prerelease + ) #Re-Test against the manifest loaded version to be sure if (-not $ModuleSpec.SatisfiedBy($manifestVersion)) { - Write-Debug "$(ModuleSpec.Name): Found a module $($moduleInfo.Item2) that initially matched the name and version folder but after reading the manifest, the version label not satisfy the version spec $($ModuleSpec). This is an edge case and should only occur if you specified a prerelease upper bound that is less than the PreRelease label in the manifest. Skipping..." + Write-Debug "$($ModuleSpec.Name): Found a module $($moduleInfo.Item2) that initially matched the name and version folder but after reading the manifest, the version label not satisfy the version spec $($ModuleSpec). This is an edge case and should only occur if you specified a prerelease upper bound that is less than the PreRelease label in the manifest. Skipping..." continue } diff --git a/ModuleFast.tests.ps1 b/ModuleFast.tests.ps1 index 30e5bc7..6d28ae6 100644 --- a/ModuleFast.tests.ps1 +++ b/ModuleFast.tests.ps1 @@ -486,9 +486,19 @@ Describe 'Install-ModuleFast' -Tag 'E2E' { { Install-ModuleFast @imfParams 'PrereleaseTest@0.0.1-prerelease' } | Should -Throw '*is newer than the requested prerelease version*' } - It 'Succeeds installing regular module over prerelease module' { + It 'Errors trying to install older prerelease over regular module' { + Install-ModuleFast @imfParams 'PrereleaseTest@0.0.1' + { Install-ModuleFast @imfParams 'PrereleaseTest@0.0.1-prerelease' } + | Should -Throw '*is newer than the requested prerelease version*' + } + It 'Installs regular module over prerelease module with warning' { Install-ModuleFast @imfParams 'PrereleaseTest@0.0.1-prerelease' Install-ModuleFast @imfParams 'PrereleaseTest@0.0.1' -WarningVariable actual *>&1 | Out-Null - $actual | Should -Not -BeNullOrEmpty -Because 'it should warn about installing regular module over prerelease module but proceed anyways' + $actual | Should -BeLike '*is newer than existing prerelease version*' + } + It 'Installs newer prerelease with warning' { + Install-ModuleFast @imfParams 'PrereleaseTest@0.0.1-aprerelease' + Install-ModuleFast @imfParams 'PrereleaseTest@0.0.1-bprerelease' -WarningVariable actual *>&1 | Out-Null + $actual | Should -BeLike '*is newer than existing prerelease version*' } }