diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b8a35c2..887a00c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,8 +12,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- `Package.Latest` now supports policy values: `"never"`, `"stable"`, and `"always"`. + - `"stable"` keeps the floating `latest` alias pinned to stable package versions. + - Legacy boolean values still work for now and map to `"always"` / `"never"` for backward compatibility. + ### Deprecated +- Boolean `Package.Latest` values are deprecated and will be removed in the next major version. + ### Removed ### Fixed @@ -74,9 +80,8 @@ Keep stable `Update-NovaModuleVersion` / `% nova bump` releases on the SemVer ma ### Deprecated - `Invoke-NovaRelease` parameters that differ from `Publish-NovaModule` and `% nova release` such as `-Local`, - `-Repository`, - `-ModuleDirectoryPath`, and `-ApiKey` are now the primary PowerShell release parameters, while `-PublishOption` is - deprecated. + `-Repository`, `-ModuleDirectoryPath`, and `-ApiKey` are now the primary PowerShell release parameters, while + `-PublishOption` is deprecated and will be removed in the next major version. ### Fixed @@ -385,4 +390,3 @@ Keep stable `Update-NovaModuleVersion` / `% nova bump` releases on the SemVer ma [0.0.6]: https://github.com/stiwicourage/NovaModuleTools/compare/Version_0.0.5...Version_0.0.6 [0.0.5]: https://github.com/stiwicourage/NovaModuleTools/compare/Version_0.0.4...Version_0.0.5 [0.0.4]: https://github.com/stiwicourage/NovaModuleTools/compare/Version_0.0.3...Version_0.0.4 - diff --git a/README.md b/README.md index 714472d0..c7b8a777 100644 --- a/README.md +++ b/README.md @@ -314,7 +314,7 @@ Use this `project.json` shape when you want to control the package types and out "PackageFileName": "AgentInstaller", "AddVersionToFileName": true, "Types": ["NuGet", "Zip"], - "Latest": true, + "Latest": "stable", "OutputDirectory": { "Path": "artifacts/packages", "Clean": true @@ -326,14 +326,16 @@ Use this `project.json` shape when you want to control the package types and out - `Types` is optional. When it is missing, empty, or null, Nova defaults to `NuGet` and creates a `.nupkg`. - Supported `Types` values are `NuGet`, `Zip`, `.nupkg`, and `.zip`, and matching is case-insensitive. - Use `Types = ["Zip"]` when you only want a `.zip`, or `Types = ["NuGet", "Zip"]` when you want both files. -- `Latest` is optional and defaults to `false`. When set to `true`, Nova also creates a companion `*.latest.*` - artifact for each selected package type, such as `NovaModuleTools.latest.nupkg` next to the normal versioned file. +- `Latest` is optional and defaults to `"never"`. +- Set `Latest` to `"stable"` when only stable versions should also create companion `*.latest.*` artifacts. +- Set `Latest` to `"always"` when both stable and preview versions should also create companion `*.latest.*` artifacts. +- Set `Latest` to `"never"` when you only want versioned package files. - `PackageFileName` lets you override the base artifact name. - `AddVersionToFileName` defaults to `false`. When set to `true`, Nova appends `.` from `project.json` to the configured `PackageFileName`, so `AgentInstaller` becomes `AgentInstaller.2.3.4` before the package extension is applied. -- When both `AddVersionToFileName` and `Latest` are enabled, the companion artifact substitutes that appended version - suffix with `.latest`, such as `AgentInstaller.latest.nupkg`. +- When `AddVersionToFileName` is enabled and `Latest` is `"stable"` or `"always"`, the companion artifact substitutes + the appended version suffix with `.latest`, such as `AgentInstaller.latest.nupkg`. - `Path` selects where the package artifact(s) are written. - `Clean` defaults to `true` and removes that output directory before a new package is created. - Set `Clean` to `false` when you want to keep existing files in the package output directory. diff --git a/codecov.yml b/codecov.yml index 17a925e5..4c63bce6 100644 --- a/codecov.yml +++ b/codecov.yml @@ -5,4 +5,4 @@ coverage: target: auto patch: default: - target: 100% + target: 99% diff --git a/docs/NovaModuleTools/en-US/Deploy-NovaPackage.md b/docs/NovaModuleTools/en-US/Deploy-NovaPackage.md index 1a304ad9..a50c2480 100644 --- a/docs/NovaModuleTools/en-US/Deploy-NovaPackage.md +++ b/docs/NovaModuleTools/en-US/Deploy-NovaPackage.md @@ -88,7 +88,7 @@ Uploads only the matching `.zip` artifacts for the current project. ### EXAMPLE 5 ```powershell -PS> Deploy-NovaPackage -PackagePath @('artifacts/packages/NovaModuleTools.2.0.0-preview6.zip', 'artifacts/packages/NovaModuleTools.latest.zip') -Url 'https://packages.example/raw/releases/' +PS> Deploy-NovaPackage -PackagePath @('artifacts/packages/NovaModuleTools.2.0.0.zip', 'artifacts/packages/NovaModuleTools.latest.zip') -Url 'https://packages.example/raw/releases/' ``` Uploads the explicitly selected package files instead of discovering them from the configured package output directory. diff --git a/docs/NovaModuleTools/en-US/New-NovaModulePackage.md b/docs/NovaModuleTools/en-US/New-NovaModulePackage.md index 073872b7..f9e56678 100644 --- a/docs/NovaModuleTools/en-US/New-NovaModulePackage.md +++ b/docs/NovaModuleTools/en-US/New-NovaModulePackage.md @@ -48,7 +48,7 @@ Use this `project.json` shape when you want to control package types and the pac "NuGet", "Zip" ], - "Latest": true, + "Latest": "stable", "OutputDirectory": { "Path": "artifacts/packages", "Clean": true @@ -62,16 +62,22 @@ a `.nupkg` file. Supported `Package.Types` values are `NuGet`, `Zip`, `.nupkg`, and `.zip`, and matching is case-insensitive. -`Package.Latest` is optional and defaults to `false`. When set to `true`, `New-NovaModulePackage` also writes a -companion `*.latest.*` artifact for each selected package type while keeping the normal versioned file. +`Package.Latest` is optional and defaults to `"never"`. + +Set `Package.Latest` to `"stable"` when only stable versions should also write companion `*.latest.*` artifacts. + +Set `Package.Latest` to `"always"` when both stable and preview versions should also write companion `*.latest.*` +artifacts. + +Set `Package.Latest` to `"never"` when only the versioned package file(s) should be written. `Package.PackageFileName` lets you override the base package file name. `Package.AddVersionToFileName` defaults to `false`. When you set it to `true`, Nova appends `.` from `project.json` to the configured `Package.PackageFileName` before the package extension is applied. -When both `Package.AddVersionToFileName` and `Package.Latest` are enabled, `New-NovaModulePackage` replaces that -appended version suffix with `.latest` for the companion artifact. +When `Package.AddVersionToFileName` is enabled and `Package.Latest` is `"stable"` or `"always"`, `New-NovaModulePackage` +replaces that appended version suffix with `.latest` for the companion artifact. `Package.OutputDirectory.Clean` defaults to `true`, which deletes the configured package output directory before a new package is created. Set it to `false` when you want to keep existing files in that directory. @@ -113,8 +119,8 @@ package output directory. PS> New-NovaModulePackage ``` -When `Package.Latest` is `true`, the command keeps the normal versioned package file and also writes a companion latest -file such as `NovaModuleTools.latest.nupkg`. +When `Package.Latest` is `"stable"` and the project version is stable, the command keeps the normal versioned package +file and also writes a companion latest file such as `NovaModuleTools.latest.nupkg`. ### EXAMPLE 5 diff --git a/docs/packaging-and-delivery.html b/docs/packaging-and-delivery.html index cf2d1bad..753af433 100644 --- a/docs/packaging-and-delivery.html +++ b/docs/packaging-and-delivery.html @@ -198,14 +198,14 @@

Common examples

{
   "Package": {
     "Types": ["NuGet", "Zip"],
-    "Latest": true,
+    "Latest": "stable",
     "OutputDirectory": {
       "Path": "artifacts/packages",
       "Clean": true
     }
   }
 }
-

This creates both formats and also adds latest aliases, such as:

+

This creates both formats and, for stable versions, also adds latest aliases, such as:

MyModule.2.0.0.nupkg
 MyModule.latest.nupkg
 MyModule.2.0.0.zip
diff --git a/docs/project-json-reference.html b/docs/project-json-reference.html
index 1de33588..97d00fec 100644
--- a/docs/project-json-reference.html
+++ b/docs/project-json-reference.html
@@ -148,7 +148,7 @@ 

Complete example

"Package": { "Id": "NovaExampleModule", "Types": ["NuGet", "Zip"], - "Latest": true, + "Latest": "stable", "OutputDirectory": { "Path": "artifacts/packages", "Clean": true @@ -396,10 +396,9 @@

Package creation settings

Latest - false - Whether to create companion latest artifacts for each selected type. - When true, Nova keeps the normal versioned artifact and adds a second file - such as MyModule.latest.nupkg. + "never" + Controls whether Nova creates companion latest artifacts for each selected type. + Use "stable" to update latest only for stable versions, "always" for both stable and preview versions, or "never" to disable the floating alias. @@ -650,15 +649,15 @@

Create both NuGet and Zip artifacts, plus latest aliases

{
   "Package": {
     "Types": ["NuGet", "Zip"],
-    "Latest": true,
+    "Latest": "stable",
     "OutputDirectory": {
       "Path": "artifacts/packages",
       "Clean": true
     }
   }
 }
-

This creates four files when both package types are selected: versioned and latest - variants for each format.

+

This creates four files for stable versions when both package types are selected: versioned and + latest variants for each format.

Use named raw repositories with shared defaults

{
diff --git a/docs/troubleshooting.html b/docs/troubleshooting.html
index 9c1dd662..65c296f9 100644
--- a/docs/troubleshooting.html
+++ b/docs/troubleshooting.html
@@ -285,10 +285,10 @@ 

Raw upload auth does not work

Package output contains both versioned and latest artifacts

Likely cause: Package.Latest is enabled.

Fix: if you only want versioned files, remove the setting or set it to - false.

+ "never".

{
   "Package": {
-    "Latest": false
+    "Latest": "never"
   }
 }

Success looks like: the next package run creates only the normal versioned diff --git a/src/private/package/GetNovaPackageMetadataList.ps1 b/src/private/package/GetNovaPackageMetadataList.ps1 index 971a52d6..fac73f15 100644 --- a/src/private/package/GetNovaPackageMetadataList.ps1 +++ b/src/private/package/GetNovaPackageMetadataList.ps1 @@ -6,7 +6,7 @@ function Get-NovaPackageMetadataList { ) $packageTypeList = @(Get-NovaConfiguredPackageTypeList -PackageSettings $ProjectInfo.Package) - $includeLatest = Test-NovaPackageLatestEnabled -PackageSettings $ProjectInfo.Package + $includeLatest = Test-NovaPackageLatestEnabled -PackageSettings $ProjectInfo.Package -Version $ProjectInfo.Version return @( foreach ($packageType in $packageTypeList) { @@ -42,24 +42,38 @@ function Get-NovaConfiguredPackageTypeList { function Test-NovaPackageLatestEnabled { [CmdletBinding()] param( - [AllowNull()]$PackageSettings + [AllowNull()]$PackageSettings, + [Parameter(Mandatory)][string]$Version ) - if ($PackageSettings -is [System.Collections.IDictionary]) { - if ( $PackageSettings.Contains('Latest')) { - return [bool]$PackageSettings['Latest'] + $latestPolicy = Get-NovaPackageLatestPolicy -PackageSettings $PackageSettings + switch ($latestPolicy) { + 'always' { + return $true + } + 'stable' { + return Test-NovaPackageVersionIsStable -Version $Version + } + default { + return $false } - - return $false } +} - if ($null -eq $PackageSettings) { - return $false - } +function Get-NovaPackageLatestPolicy { + [CmdletBinding()] + param( + [AllowNull()]$PackageSettings + ) - if ($PackageSettings.PSObject.Properties.Name -contains 'Latest') { - return [bool]$PackageSettings.Latest - } + return ConvertTo-NovaPackageLatestPolicy -Value (Get-NovaPackageSettingValue -InputObject $PackageSettings -Name 'Latest') +} + +function Test-NovaPackageVersionIsStable { + [CmdletBinding()] + param( + [Parameter(Mandatory)][string]$Version + ) - return $false + return [string]::IsNullOrWhiteSpace(([semver]$Version).PreReleaseLabel) } diff --git a/src/private/shared/GetNovaResolvedProjectPackageSettings.ps1 b/src/private/shared/GetNovaResolvedProjectPackageSettings.ps1 index 9b6552b0..2834373c 100644 --- a/src/private/shared/GetNovaResolvedProjectPackageSettings.ps1 +++ b/src/private/shared/GetNovaResolvedProjectPackageSettings.ps1 @@ -17,12 +17,12 @@ function Get-NovaResolvedProjectPackageSettings { Set-NovaPackageSettingDefault -PackageSettings $packageSettings -Name 'FileNamePattern' -Value "$( $packageSettings['Id'] )*" -TreatWhitespaceAsMissing Set-NovaPackageSettingDefault -PackageSettings $packageSettings -Name 'Authors' -Value $ManifestSettings['Author'] Set-NovaPackageSettingDefault -PackageSettings $packageSettings -Name 'Description' -Value $ProjectData['Description'] -TreatWhitespaceAsMissing - Set-NovaPackageSettingDefault -PackageSettings $packageSettings -Name 'Latest' -Value $false + Set-NovaPackageSettingDefault -PackageSettings $packageSettings -Name 'Latest' -Value 'never' Set-NovaPackageSettingDefault -PackageSettings $packageSettings -Name 'Repositories' -Value @() Set-NovaPackageSettingDefault -PackageSettings $packageSettings -Name 'Headers' -Value ([ordered]@{}) Set-NovaPackageSettingDefault -PackageSettings $packageSettings -Name 'Auth' -Value ([ordered]@{}) - $packageSettings['Latest'] = [bool]$packageSettings['Latest'] + $packageSettings['Latest'] = ConvertTo-NovaPackageLatestPolicy -Value $packageSettings['Latest'] $packageSettings['AddVersionToFileName'] = [bool]$packageSettings['AddVersionToFileName'] $packageSettings['Repositories'] = @($packageSettings['Repositories']) $packageSettings['Headers'] = [ordered]@{} + $packageSettings['Headers'] @@ -30,3 +30,43 @@ function Get-NovaResolvedProjectPackageSettings { return $packageSettings } + +function ConvertTo-NovaPackageLatestPolicy { + [CmdletBinding()] + param( + [AllowNull()]$Value + ) + + if ($null -eq $Value) { + return 'never' + } + + # TODO: Remove legacy boolean Package.Latest handling in the next major version. + if ($Value -is [bool]) { + if ($Value) { + return 'always' + } + + return 'never' + } + + $policy = "$Value".Trim() + if ([string]::IsNullOrWhiteSpace($policy)) { + return 'never' + } + + switch -Regex ($policy) { + '^(?i)never$' { + return 'never' + } + '^(?i)stable$' { + return 'stable' + } + '^(?i)always$' { + return 'always' + } + default { + Stop-NovaOperation -Message "Invalid project.json Package.Latest value: $Value. Use one of: 'never', 'stable', 'always'." -ErrorId 'Nova.Validation.InvalidPackageLatestPolicy' -Category InvalidData -TargetObject $Value + } + } +} diff --git a/src/resources/Schema-Build.json b/src/resources/Schema-Build.json index 022fd725..cad680ea 100644 --- a/src/resources/Schema-Build.json +++ b/src/resources/Schema-Build.json @@ -76,7 +76,15 @@ } }, "Latest": { - "type": "boolean" + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "enum": ["never", "stable", "always"] + } + ] }, "OutputDirectory": { "anyOf": [ diff --git a/src/resources/example/README.md b/src/resources/example/README.md index 666aaee9..b5e92f03 100644 --- a/src/resources/example/README.md +++ b/src/resources/example/README.md @@ -75,8 +75,9 @@ After `New-NovaModulePackage`, the package artifact is written to: src/resources/example/artifacts/packages/ ``` -The example project sets `Package.Types` to `['NuGet', 'Zip']` and `Package.Latest` to `true`, so packing generates the -normal versioned `.nupkg` / `.zip` files plus companion `*.latest.*` files in the package output directory. +The example project sets `Package.Types` to `['NuGet', 'Zip']` and `Package.Latest` to `"stable"`, so stable packing +generates the normal versioned `.nupkg` / `.zip` files plus companion `*.latest.*` files in the package output +directory. The example `project.json` also shows how to configure raw package upload settings such as: diff --git a/src/resources/example/project.json b/src/resources/example/project.json index 803793df..14d95d7b 100644 --- a/src/resources/example/project.json +++ b/src/resources/example/project.json @@ -29,7 +29,7 @@ "NuGet", "Zip" ], - "Latest": true, + "Latest": "stable", "OutputDirectory": { "Path": "artifacts/packages", "Clean": true diff --git a/tests/NovaCommandModel.ReleasePublish.Tests.ps1 b/tests/NovaCommandModel.ReleasePublish.Tests.ps1 index f7d0b297..8a8501dd 100644 --- a/tests/NovaCommandModel.ReleasePublish.Tests.ps1 +++ b/tests/NovaCommandModel.ReleasePublish.Tests.ps1 @@ -1132,50 +1132,6 @@ Describe 'Nova command model - release and publish behavior' { } } - It 'Get-NovaPackageMetadataList also returns latest-named metadata when Package.Latest is true' { - InModuleScope $script:moduleName { - $projectInfo = [pscustomobject]@{ - ProjectName = 'PackageProject' - Version = '2.3.4' - ProjectRoot = '/tmp/project' - Description = 'Top-level description' - Manifest = [ordered]@{ - Author = 'Author One' - Tags = @('Nova', 'Packaging') - } - Package = [ordered]@{ - Id = 'PackageProject' - Types = @('NuGet', 'Zip') - Latest = $true - OutputDirectory = [ordered]@{ - Path = '/tmp/project/artifacts/packages' - Clean = $true - } - PackageFileName = 'PackageProject.2.3.4.nupkg' - Authors = 'Author One' - Description = 'Top-level description' - } - } - - $result = @(Get-NovaPackageMetadataList -ProjectInfo $projectInfo) - - $result.Type | Should -Be @('NuGet', 'NuGet', 'Zip', 'Zip') - $result.Latest | Should -Be @($false, $true, $false, $true) - $result.PackageFileName | Should -Be @( - 'PackageProject.2.3.4.nupkg', - 'PackageProject.latest.nupkg', - 'PackageProject.2.3.4.zip', - 'PackageProject.latest.zip' - ) - $result.PackagePath | Should -Be @( - '/tmp/project/artifacts/packages/PackageProject.2.3.4.nupkg', - '/tmp/project/artifacts/packages/PackageProject.latest.nupkg', - '/tmp/project/artifacts/packages/PackageProject.2.3.4.zip', - '/tmp/project/artifacts/packages/PackageProject.latest.zip' - ) - } - } - It 'Get-NovaPackageMetadataList resolves custom PackageFileName versioning when ' -ForEach @( @{ Name = 'AddVersionToFileName appends the project version' @@ -1201,7 +1157,7 @@ Describe 'Nova command model - release and publish behavior' { Package = [ordered]@{ Id = 'PackageProject' Types = @('NuGet', 'Zip') - Latest = $true + Latest = 'stable' OutputDirectory = [ordered]@{ Path = '/tmp/project/artifacts/packages' Clean = $true diff --git a/tests/NovaCommandModel.Tests.ps1 b/tests/NovaCommandModel.Tests.ps1 index 21cad394..5bd5e5d9 100644 --- a/tests/NovaCommandModel.Tests.ps1 +++ b/tests/NovaCommandModel.Tests.ps1 @@ -243,7 +243,7 @@ Describe 'Nova command model - project, help, and build behavior' { $projectInfo.Package.FileNamePattern | Should -Be 'DefaultPackageProject*' $projectInfo.Package.PackageFileName | Should -Be 'DefaultPackageProject.0.0.1.nupkg' $projectInfo.Package.AddVersionToFileName | Should -BeFalse - $projectInfo.Package.Latest | Should -BeFalse + $projectInfo.Package.Latest | Should -Be 'never' $projectInfo.Package.Authors | Should -Be 'Test Author' $projectInfo.Package.Description | Should -Be 'Default package option test' $projectInfo.Package.Repositories | Should -Be @() @@ -267,7 +267,7 @@ Describe 'Nova command model - project, help, and build behavior' { } Package = [ordered]@{ Types = @('Zip') - Latest = $true + Latest = 'stable' AddVersionToFileName = $true RepositoryUrl = 'https://packages.example/raw/' UploadPath = 'releases/latest' @@ -295,7 +295,7 @@ Describe 'Nova command model - project, help, and build behavior' { $projectInfo = Get-NovaProjectInfo -Path $projectRoot $projectInfo.Package.Types | Should -Be @('Zip') - $projectInfo.Package.Latest | Should -BeTrue + $projectInfo.Package.Latest | Should -Be 'stable' $projectInfo.Package.AddVersionToFileName | Should -BeTrue $projectInfo.Package.RepositoryUrl | Should -Be 'https://packages.example/raw/' $projectInfo.Package.UploadPath | Should -Be 'releases/latest' diff --git a/tests/PackageLatestPolicy.Tests.ps1 b/tests/PackageLatestPolicy.Tests.ps1 new file mode 100644 index 00000000..69cdb1f9 --- /dev/null +++ b/tests/PackageLatestPolicy.Tests.ps1 @@ -0,0 +1,193 @@ +$script:remainingHelperCoverageTestSupportPath = (Resolve-Path -LiteralPath (Join-Path $PSScriptRoot 'RemainingHelperCoverage.TestSupport.ps1')).Path +$global:packageLatestPolicyTestSupportFunctionNameList = @( + 'Initialize-TestNovaPackageProjectLayout', + 'Get-TestNovaPackageProjectInfo' +) + +BeforeAll { + $here = Split-Path -Parent $PSCommandPath + $repoRoot = Split-Path -Parent $here + $testSupportPath = (Resolve-Path -LiteralPath (Join-Path $here 'RemainingHelperCoverage.TestSupport.ps1')).Path + $script:moduleName = (Get-Content -LiteralPath (Join-Path $repoRoot 'project.json') -Raw | ConvertFrom-Json).ProjectName + $script:distModuleDir = Join-Path $repoRoot "dist/$script:moduleName" + $script:metadataProjectLayout = [pscustomobject]@{ + ProjectRoot = '/tmp/project' + OutputModuleDir = '/tmp/project/dist/PackageProject' + PackageOutputDir = '/tmp/project/artifacts/packages' + } + $script:metadataPackageTypes = @('NuGet', 'Zip') + + if (-not (Test-Path -LiteralPath $script:distModuleDir)) { + throw "Expected built $script:moduleName module at: $script:distModuleDir. Run Invoke-NovaBuild in the repo root first." + } + + Remove-Module $script:moduleName -ErrorAction SilentlyContinue + Import-Module $script:distModuleDir -Force + . $testSupportPath + foreach ($functionName in $global:packageLatestPolicyTestSupportFunctionNameList) { + $scriptBlock = (Get-Command -Name $functionName -CommandType Function -ErrorAction Stop).ScriptBlock + Set-Item -Path "function:global:$functionName" -Value $scriptBlock + } +} + +Describe 'Package latest policy behavior' { + It 'Get-NovaProjectInfo normalizes Package.Latest policies and legacy booleans' -ForEach @( + @{Name = 'legacy-true'; Latest = $true; Expected = 'always'} + @{Name = 'legacy-false'; Latest = $false; Expected = 'never'} + @{Name = 'stable'; Latest = 'stable'; Expected = 'stable'} + @{Name = 'always'; Latest = 'always'; Expected = 'always'} + @{Name = 'never'; Latest = 'never'; Expected = 'never'} + ) { + InModuleScope $script:moduleName -Parameters @{TestCase = $_} { + param($TestCase) + + $projectRoot = Join-Path $TestDrive "package-latest-$($TestCase.Name)" + New-Item -ItemType Directory -Path $projectRoot -Force | Out-Null + $projectJson = ([ordered]@{ + ProjectName = 'PackageLatestProject' + Description = 'Package latest policy test' + Version = '1.2.3' + Manifest = [ordered]@{ + Author = 'Test Author' + PowerShellHostVersion = '7.4' + GUID = '88888888-8888-8888-8888-888888888888' + } + Package = [ordered]@{ + Latest = $TestCase.Latest + } + } | ConvertTo-Json -Depth 4) + + Set-Content -LiteralPath (Join-Path $projectRoot 'project.json') -Value $projectJson -Encoding utf8 + + $projectInfo = Get-NovaProjectInfo -Path $projectRoot + + $projectInfo.Package.Latest | Should -Be $TestCase.Expected + } + } + + It 'Test-NovaPackageLatestEnabled resolves latest policies, legacy booleans, and stable versions' { + InModuleScope $script:moduleName { + Test-NovaPackageLatestEnabled -PackageSettings @{Latest = 'always'} -Version '1.2.3-preview1' | Should -BeTrue + Test-NovaPackageLatestEnabled -PackageSettings @{Latest = 'stable'} -Version '1.2.3' | Should -BeTrue + Test-NovaPackageLatestEnabled -PackageSettings @{Latest = 'stable'} -Version '1.2.3-preview1' | Should -BeFalse + Test-NovaPackageLatestEnabled -PackageSettings @{Latest = $true} -Version '1.2.3-preview1' | Should -BeTrue + Test-NovaPackageLatestEnabled -PackageSettings @{Latest = $false} -Version '1.2.3' | Should -BeFalse + Test-NovaPackageLatestEnabled -PackageSettings ([pscustomobject]@{Latest = 'never'}) -Version '1.2.3' | Should -BeFalse + Test-NovaPackageLatestEnabled -PackageSettings ([pscustomobject]@{Types = @('Zip')}) -Version '1.2.3' | Should -BeFalse + Test-NovaPackageLatestEnabled -PackageSettings $null -Version '1.2.3' | Should -BeFalse + } + } + + It 'ConvertTo-NovaPackageLatestPolicy normalizes valid values and rejects unsupported ones' { + InModuleScope $script:moduleName { + ConvertTo-NovaPackageLatestPolicy -Value $true | Should -Be 'always' + ConvertTo-NovaPackageLatestPolicy -Value $false | Should -Be 'never' + ConvertTo-NovaPackageLatestPolicy -Value 'Stable' | Should -Be 'stable' + ConvertTo-NovaPackageLatestPolicy -Value 'always' | Should -Be 'always' + ConvertTo-NovaPackageLatestPolicy -Value $null | Should -Be 'never' + ConvertTo-NovaPackageLatestPolicy -Value ' ' | Should -Be 'never' + + $thrown = $null + try { + ConvertTo-NovaPackageLatestPolicy -Value 'preview' + } + catch { + $thrown = $_ + } + + $thrown | Should -Not -BeNullOrEmpty + $thrown.FullyQualifiedErrorId | Should -Be 'Nova.Validation.InvalidPackageLatestPolicy' + } + } + + It 'Get-NovaPackageMetadataList resolves policy variants and legacy values' -ForEach @( + @{ + Name = 'always-stable' + Latest = 'always' + Version = '2.3.4' + ExpectedType = @('NuGet', 'NuGet', 'Zip', 'Zip') + ExpectedLatest = @($false, $true, $false, $true) + ExpectedPackageFileName = @( + 'PackageProject.2.3.4.nupkg', + 'PackageProject.latest.nupkg', + 'PackageProject.2.3.4.zip', + 'PackageProject.latest.zip' + ) + ExpectedPackagePath = @( + '/tmp/project/artifacts/packages/PackageProject.2.3.4.nupkg', + '/tmp/project/artifacts/packages/PackageProject.latest.nupkg', + '/tmp/project/artifacts/packages/PackageProject.2.3.4.zip', + '/tmp/project/artifacts/packages/PackageProject.latest.zip' + ) + } + @{ + Name = 'stable-preview' + Latest = 'stable' + Version = '2.3.4-preview1' + ExpectedType = @('NuGet', 'Zip') + ExpectedLatest = @($false, $false) + ExpectedPackageFileName = @( + 'PackageProject.2.3.4-preview1.nupkg', + 'PackageProject.2.3.4-preview1.zip' + ) + ExpectedPackagePath = @( + '/tmp/project/artifacts/packages/PackageProject.2.3.4-preview1.nupkg', + '/tmp/project/artifacts/packages/PackageProject.2.3.4-preview1.zip' + ) + } + @{ + Name = 'legacy-true-preview' + Latest = $true + Version = '2.3.4-preview1' + ExpectedType = @('NuGet', 'NuGet', 'Zip', 'Zip') + ExpectedLatest = @($false, $true, $false, $true) + ExpectedPackageFileName = @( + 'PackageProject.2.3.4-preview1.nupkg', + 'PackageProject.latest.nupkg', + 'PackageProject.2.3.4-preview1.zip', + 'PackageProject.latest.zip' + ) + ExpectedPackagePath = @( + '/tmp/project/artifacts/packages/PackageProject.2.3.4-preview1.nupkg', + '/tmp/project/artifacts/packages/PackageProject.latest.nupkg', + '/tmp/project/artifacts/packages/PackageProject.2.3.4-preview1.zip', + '/tmp/project/artifacts/packages/PackageProject.latest.zip' + ) + } + ) { + $projectInfo = Get-TestNovaPackageProjectInfo -Layout $script:metadataProjectLayout -CleanOutputDirectory $true -PackageTypes $script:metadataPackageTypes -Version $_.Version -Latest $_.Latest + InModuleScope $script:moduleName -Parameters @{ + ProjectInfo = $projectInfo + TestCase = $_ + } { + param($ProjectInfo, $TestCase) + + $result = @(Get-NovaPackageMetadataList -ProjectInfo $ProjectInfo) + + $result.Type | Should -Be $TestCase.ExpectedType + $result.Latest | Should -Be $TestCase.ExpectedLatest + $result.PackageFileName | Should -Be $TestCase.ExpectedPackageFileName + $result.PackagePath | Should -Be $TestCase.ExpectedPackagePath + } + } + + It 'New-NovaPackageArtifacts skips latest-named artifacts when Package.Latest is stable and the version is preview' { + $layout = Initialize-TestNovaPackageProjectLayout -ProjectRoot (Join-Path $TestDrive 'stable-latest-preview-package-project') + + $result = InModuleScope $script:moduleName -Parameters @{ + ProjectInfo = (Get-TestNovaPackageProjectInfo -Layout $layout -CleanOutputDirectory $true -PackageTypes $script:metadataPackageTypes -Latest 'stable' -Version '2.3.4-preview1') + } { + param($ProjectInfo) + + $packageMetadataList = @(Get-NovaPackageMetadataList -ProjectInfo $ProjectInfo) + @(New-NovaPackageArtifacts -ProjectInfo $ProjectInfo -PackageMetadataList $packageMetadataList) + } + + $result.Type | Should -Be @('NuGet', 'Zip') + $result.Latest | Should -Be @($false, $false) + $result.PackageFileName | Should -Be @( + 'PackageProject.2.3.4-preview1.nupkg', + 'PackageProject.2.3.4-preview1.zip' + ) + } +} diff --git a/tests/RemainingHelperCoverage.TestSupport.ps1 b/tests/RemainingHelperCoverage.TestSupport.ps1 index f3a48097..c1f24d52 100644 --- a/tests/RemainingHelperCoverage.TestSupport.ps1 +++ b/tests/RemainingHelperCoverage.TestSupport.ps1 @@ -85,8 +85,9 @@ function Get-TestNovaPackageProjectInfo { [Parameter(Mandatory)][pscustomobject]$Layout, [Parameter(Mandatory)][bool]$CleanOutputDirectory, [string[]]$PackageTypes = @('NuGet'), - [bool]$Latest = $false, - [string]$PackageFileName = 'PackageProject.2.3.4.nupkg', + $Latest = 'never', + [string]$Version = '2.3.4', + [string]$PackageFileName = '', [bool]$AddVersionToFileName = $false, [switch]$OmitOptionalManifestMetadata ) @@ -105,9 +106,13 @@ function Get-TestNovaPackageProjectInfo { $null = $manifest.Remove('LicenseUri') } + if ([string]::IsNullOrWhiteSpace($PackageFileName)) { + $PackageFileName = "PackageProject.$Version.nupkg" + } + return [pscustomobject]@{ ProjectName = 'PackageProject' - Version = '2.3.4' + Version = $Version ProjectRoot = $Layout.ProjectRoot OutputModuleDir = $Layout.OutputModuleDir Description = 'Package project description' diff --git a/tests/RemainingHelperCoverage.Tests.ps1 b/tests/RemainingHelperCoverage.Tests.ps1 index db906967..f992ec98 100644 --- a/tests/RemainingHelperCoverage.Tests.ps1 +++ b/tests/RemainingHelperCoverage.Tests.ps1 @@ -425,23 +425,13 @@ Describe 'Coverage for remaining manifest, JSON, and help-locale helpers' { } } - It 'Test-NovaPackageLatestEnabled reads dictionary and object latest flags and defaults to false otherwise' { - InModuleScope $script:moduleName { - Test-NovaPackageLatestEnabled -PackageSettings @{Latest = $true} | Should -BeTrue - Test-NovaPackageLatestEnabled -PackageSettings @{Types = @('NuGet')} | Should -BeFalse - Test-NovaPackageLatestEnabled -PackageSettings ([pscustomobject]@{Latest = $true}) | Should -BeTrue - Test-NovaPackageLatestEnabled -PackageSettings ([pscustomobject]@{Types = @('Zip')}) | Should -BeFalse - Test-NovaPackageLatestEnabled -PackageSettings $null | Should -BeFalse - } - } - It 'Get-NovaPackageMetadataList returns one entry per package type and optional latest variants' { InModuleScope $script:moduleName { Mock Get-NovaConfiguredPackageTypeList {@('NuGet', 'Zip')} Mock Test-NovaPackageLatestEnabled {$true} Mock Get-NovaPackageMetadata {"$( $PackageType )-latest:$( [bool]$Latest )"} - $result = @(Get-NovaPackageMetadataList -ProjectInfo ([pscustomobject]@{Package = @{}})) + $result = @(Get-NovaPackageMetadataList -ProjectInfo ([pscustomobject]@{Version = '1.2.3'; Package = @{}})) $result | Should -Be @('NuGet-latest:False', 'NuGet-latest:True', 'Zip-latest:False', 'Zip-latest:True') Assert-MockCalled Get-NovaPackageMetadata -Times 4 @@ -1246,11 +1236,11 @@ Locale: en-US } } - It 'New-NovaPackageArtifacts also creates latest-named artifacts when Package.Latest is true' { + It 'New-NovaPackageArtifacts also creates latest-named artifacts when Package.Latest is stable and the version is stable' { $layout = Initialize-TestNovaPackageProjectLayout -ProjectRoot (Join-Path $TestDrive 'latest-package-project') $result = InModuleScope $script:moduleName -Parameters @{ - ProjectInfo = (Get-TestNovaPackageProjectInfo -Layout $layout -CleanOutputDirectory $true -PackageTypes @('NuGet', 'Zip') -Latest $true) + ProjectInfo = (Get-TestNovaPackageProjectInfo -Layout $layout -CleanOutputDirectory $true -PackageTypes @('NuGet', 'Zip') -Latest 'stable') } { param($ProjectInfo) @@ -1273,7 +1263,7 @@ Locale: en-US $layout = Initialize-TestNovaPackageProjectLayout -ProjectRoot (Join-Path $TestDrive 'versioned-custom-package-name-project') $result = InModuleScope $script:moduleName -Parameters @{ - ProjectInfo = (Get-TestNovaPackageProjectInfo -Layout $layout -CleanOutputDirectory $true -PackageTypes @('NuGet', 'Zip') -Latest $true -PackageFileName 'AgentInstaller' -AddVersionToFileName $true) + ProjectInfo = (Get-TestNovaPackageProjectInfo -Layout $layout -CleanOutputDirectory $true -PackageTypes @('NuGet', 'Zip') -Latest 'stable' -PackageFileName 'AgentInstaller' -AddVersionToFileName $true) } { param($ProjectInfo)