From 99ae6bd649d3427573645693737578391d1e3d25 Mon Sep 17 00:00:00 2001 From: Stiwi Gabriel Courage Date: Tue, 28 Apr 2026 12:03:16 +0200 Subject: [PATCH] feat(#104): implement package upload file selection utilities - Add Get-NovaPackageUploadFileInfo function for package file info retrieval - Introduce Get-NovaPackageUploadOutputDirectoryFileList for file listing in output directory - Create Get-NovaPackageUploadRequestedTypeList to filter requested package types - Update ResolveNovaPackageUploadExplicitFile and ResolveNovaPackageUploadOutputFileList to utilize new functions --- .../package/GetNovaPackageUploadFileInfo.ps1 | 19 +++ ...vaPackageUploadOutputDirectoryFileList.ps1 | 21 +++ .../GetNovaPackageUploadRequestedTypeList.ps1 | 15 +++ .../ResolveNovaPackageUploadExplicitFile.ps1 | 7 +- ...solveNovaPackageUploadExplicitFileList.ps1 | 6 +- ...ResolveNovaPackageUploadOutputFileList.ps1 | 3 +- .../ResolveNovaPackageUploadOutputFileSet.ps1 | 16 +-- .../NovaCommandModel.PackageUpload.Tests.ps1 | 126 ++++++++++++++++++ 8 files changed, 187 insertions(+), 26 deletions(-) create mode 100644 src/private/package/GetNovaPackageUploadFileInfo.ps1 create mode 100644 src/private/package/GetNovaPackageUploadOutputDirectoryFileList.ps1 create mode 100644 src/private/package/GetNovaPackageUploadRequestedTypeList.ps1 diff --git a/src/private/package/GetNovaPackageUploadFileInfo.ps1 b/src/private/package/GetNovaPackageUploadFileInfo.ps1 new file mode 100644 index 00000000..0f318411 --- /dev/null +++ b/src/private/package/GetNovaPackageUploadFileInfo.ps1 @@ -0,0 +1,19 @@ +function Get-NovaPackageUploadFileInfo { + [CmdletBinding()] + param( + [Parameter(Mandatory)][string]$PackageType, + [Parameter(Mandatory)][string]$PackagePath, + [string]$PackageFileName + ) + + $resolvedPackageFileName = $PackageFileName + if ( [string]::IsNullOrWhiteSpace($resolvedPackageFileName)) { + $resolvedPackageFileName = [System.IO.Path]::GetFileName($PackagePath) + } + + return [pscustomobject]@{ + Type = $PackageType + PackagePath = $PackagePath + PackageFileName = $resolvedPackageFileName + } +} diff --git a/src/private/package/GetNovaPackageUploadOutputDirectoryFileList.ps1 b/src/private/package/GetNovaPackageUploadOutputDirectoryFileList.ps1 new file mode 100644 index 00000000..58eaa1d9 --- /dev/null +++ b/src/private/package/GetNovaPackageUploadOutputDirectoryFileList.ps1 @@ -0,0 +1,21 @@ +function Get-NovaPackageUploadOutputDirectoryFileList { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Justification = 'Package upload output directory file list is the domain term represented by this helper.')] + [CmdletBinding()] + param( + [Parameter(Mandatory)][string]$OutputDirectory, + [Parameter(Mandatory)][string]$SearchPattern, + [Parameter(Mandatory)][string]$PackageType + ) + + $matchingFileList = @( + Get-ChildItem -LiteralPath $OutputDirectory -File -ErrorAction Stop | + Where-Object {$_.Name -like $SearchPattern} | + Sort-Object Name + ) + + if ($matchingFileList.Count -eq 0) { + Stop-NovaOperation -Message "Package file not found for package type '$PackageType' in '$OutputDirectory'. Expected pattern: $SearchPattern. Run New-NovaModulePackage first or provide -PackagePath." -ErrorId 'Nova.Workflow.PackageOutputArtifactNotFound' -Category InvalidOperation -TargetObject $PackageType + } + + return $matchingFileList +} diff --git a/src/private/package/GetNovaPackageUploadRequestedTypeList.ps1 b/src/private/package/GetNovaPackageUploadRequestedTypeList.ps1 new file mode 100644 index 00000000..f097c7b7 --- /dev/null +++ b/src/private/package/GetNovaPackageUploadRequestedTypeList.ps1 @@ -0,0 +1,15 @@ +function Get-NovaPackageUploadRequestedTypeList { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Justification = 'Package upload requested type list is the domain term represented by this helper.')] + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$ProjectInfo, + [string[]]$PackageType + ) + + $requestedTypeList = @($PackageType | Where-Object {-not [string]::IsNullOrWhiteSpace("$_")}) + if ($requestedTypeList.Count -eq 0) { + return @() + } + + return @(Resolve-NovaPackageUploadTypeList -ProjectInfo $ProjectInfo -PackageType $requestedTypeList) +} diff --git a/src/private/package/ResolveNovaPackageUploadExplicitFile.ps1 b/src/private/package/ResolveNovaPackageUploadExplicitFile.ps1 index 4bc71dc0..eb640bab 100644 --- a/src/private/package/ResolveNovaPackageUploadExplicitFile.ps1 +++ b/src/private/package/ResolveNovaPackageUploadExplicitFile.ps1 @@ -15,10 +15,5 @@ function Resolve-NovaPackageUploadExplicitFile { Stop-NovaOperation -Message "Package selection is ambiguous. Explicit PackagePath '$resolvedPackagePath' resolves to type '$resolvedPackageType', but requested PackageType values are: $( $RequestedPackageTypeList -join ', ' )." -ErrorId 'Nova.Validation.PackageUploadSelectionAmbiguous' -Category InvalidArgument -TargetObject $resolvedPackagePath } - return [pscustomobject]@{ - Type = $resolvedPackageType - PackagePath = $resolvedPackagePath - PackageFileName = [System.IO.Path]::GetFileName($resolvedPackagePath) - } + return Get-NovaPackageUploadFileInfo -PackageType $resolvedPackageType -PackagePath $resolvedPackagePath } - diff --git a/src/private/package/ResolveNovaPackageUploadExplicitFileList.ps1 b/src/private/package/ResolveNovaPackageUploadExplicitFileList.ps1 index deff1f25..4301c9af 100644 --- a/src/private/package/ResolveNovaPackageUploadExplicitFileList.ps1 +++ b/src/private/package/ResolveNovaPackageUploadExplicitFileList.ps1 @@ -7,10 +7,7 @@ function Resolve-NovaPackageUploadExplicitFileList { [string[]]$PackageType ) - $requestedTypeList = @() - if (@($PackageType).Count -gt 0) { - $requestedTypeList = @(Resolve-NovaPackageUploadTypeList -ProjectInfo $ProjectInfo -PackageType $PackageType) - } + $requestedTypeList = @(Get-NovaPackageUploadRequestedTypeList -ProjectInfo $ProjectInfo -PackageType $PackageType) $resolvedFileList = @( $PackagePath | @@ -20,4 +17,3 @@ function Resolve-NovaPackageUploadExplicitFileList { return $resolvedFileList } - diff --git a/src/private/package/ResolveNovaPackageUploadOutputFileList.ps1 b/src/private/package/ResolveNovaPackageUploadOutputFileList.ps1 index d3175a2a..dda9080e 100644 --- a/src/private/package/ResolveNovaPackageUploadOutputFileList.ps1 +++ b/src/private/package/ResolveNovaPackageUploadOutputFileList.ps1 @@ -6,13 +6,14 @@ function Resolve-NovaPackageUploadOutputFileList { [string[]]$PackageType ) + $resolvedTypeList = @(Resolve-NovaPackageUploadTypeList -ProjectInfo $ProjectInfo -PackageType $PackageType) $outputDirectory = Get-NovaPackageOutputDirectory -ProjectInfo $ProjectInfo if (-not (Test-Path -LiteralPath $outputDirectory -PathType Container)) { Stop-NovaOperation -Message "Package output directory not found: $outputDirectory. Run New-NovaModulePackage first or provide -PackagePath." -ErrorId 'Nova.Environment.PackageOutputDirectoryNotFound' -Category ObjectNotFound -TargetObject $outputDirectory } return @( - @(Resolve-NovaPackageUploadTypeList -ProjectInfo $ProjectInfo -PackageType $PackageType) | + $resolvedTypeList | ForEach-Object { Resolve-NovaPackageUploadOutputFileSet -OutputDirectory $outputDirectory -ProjectInfo $ProjectInfo -PackageType $_ } diff --git a/src/private/package/ResolveNovaPackageUploadOutputFileSet.ps1 b/src/private/package/ResolveNovaPackageUploadOutputFileSet.ps1 index 7254027d..d2312ab8 100644 --- a/src/private/package/ResolveNovaPackageUploadOutputFileSet.ps1 +++ b/src/private/package/ResolveNovaPackageUploadOutputFileSet.ps1 @@ -8,23 +8,11 @@ function Resolve-NovaPackageUploadOutputFileSet { ) $searchPattern = Get-NovaPackageArtifactSearchPattern -ProjectInfo $ProjectInfo -PackageType $PackageType - $matchingFileList = @( - Get-ChildItem -LiteralPath $OutputDirectory -File -ErrorAction Stop | - Where-Object {$_.Name -like $searchPattern} | - Sort-Object Name - ) - - if ($matchingFileList.Count -eq 0) { - Stop-NovaOperation -Message "Package file not found for package type '$PackageType' in '$OutputDirectory'. Expected pattern: $searchPattern. Run New-NovaModulePackage first or provide -PackagePath." -ErrorId 'Nova.Workflow.PackageOutputArtifactNotFound' -Category InvalidOperation -TargetObject $PackageType - } + $matchingFileList = @(Get-NovaPackageUploadOutputDirectoryFileList -OutputDirectory $OutputDirectory -SearchPattern $searchPattern -PackageType $PackageType) return @( $matchingFileList | ForEach-Object { - [pscustomobject]@{ - Type = $PackageType - PackagePath = $_.FullName - PackageFileName = $_.Name - } + Get-NovaPackageUploadFileInfo -PackageType $PackageType -PackagePath $_.FullName -PackageFileName $_.Name } ) } diff --git a/tests/NovaCommandModel.PackageUpload.Tests.ps1 b/tests/NovaCommandModel.PackageUpload.Tests.ps1 index e887f5f1..b1a5b6c9 100644 --- a/tests/NovaCommandModel.PackageUpload.Tests.ps1 +++ b/tests/NovaCommandModel.PackageUpload.Tests.ps1 @@ -22,6 +22,7 @@ foreach ($functionName in $global:novaCommandModelTestSupportFunctionNameList) { $scriptBlock = (Get-Command -Name $functionName -CommandType Function -ErrorAction Stop).ScriptBlock Set-Item -Path "function:global:$functionName" -Value $scriptBlock } + foreach ($functionName in $global:novaCommandModelPackageUploadTestSupportFunctionNameList) { $scriptBlock = (Get-Command -Name $functionName -CommandType Function -ErrorAction Stop).ScriptBlock Set-Item -Path "function:global:$functionName" -Value $scriptBlock @@ -240,6 +241,131 @@ Describe 'Nova command model - package upload behavior' { } } + It 'Get-NovaPackageUploadFileList prefers explicit package paths over output discovery' { + InModuleScope $script:moduleName { + $projectInfo = [pscustomobject]@{ProjectName = 'PackageProject'} + $explicitFileList = @( + [pscustomobject]@{Type = 'Zip'; PackagePath = '/tmp/project/artifacts/packages/PackageProject.2.3.4.zip'; PackageFileName = 'PackageProject.2.3.4.zip'} + ) + + Mock Resolve-NovaPackageUploadExplicitFileList {$explicitFileList} + Mock Resolve-NovaPackageUploadOutputFileList {throw 'explicit package selection should not fall back to output discovery'} + + $result = @(Get-NovaPackageUploadFileList -ProjectInfo $projectInfo -PackagePath @(' ', '/tmp/project/artifacts/packages/PackageProject.2.3.4.zip', '') -PackageType @('Zip')) + + $result.PackagePath | Should -Be @('/tmp/project/artifacts/packages/PackageProject.2.3.4.zip') + Assert-MockCalled Resolve-NovaPackageUploadExplicitFileList -Times 1 -ParameterFilter { + $ProjectInfo.ProjectName -eq 'PackageProject' -and + $PackagePath.Count -eq 1 -and + $PackagePath[0] -eq '/tmp/project/artifacts/packages/PackageProject.2.3.4.zip' -and + $PackageType.Count -eq 1 -and + $PackageType[0] -eq 'Zip' + } + Assert-MockCalled Resolve-NovaPackageUploadOutputFileList -Times 0 + } + } + + It 'Get-NovaPackageUploadFileList falls back to output discovery when explicit package paths are not provided' { + InModuleScope $script:moduleName { + $projectInfo = [pscustomobject]@{ProjectName = 'PackageProject'} + $outputFileList = @( + [pscustomobject]@{Type = 'Zip'; PackagePath = '/tmp/project/artifacts/packages/PackageProject.2.3.4.zip'; PackageFileName = 'PackageProject.2.3.4.zip'} + ) + + Mock Resolve-NovaPackageUploadExplicitFileList {throw 'output discovery should be used when PackagePath is empty'} + Mock Resolve-NovaPackageUploadOutputFileList {$outputFileList} + + $result = @(Get-NovaPackageUploadFileList -ProjectInfo $projectInfo -PackagePath @('', ' ') -PackageType @('Zip')) + + $result.PackagePath | Should -Be @('/tmp/project/artifacts/packages/PackageProject.2.3.4.zip') + Assert-MockCalled Resolve-NovaPackageUploadExplicitFileList -Times 0 + Assert-MockCalled Resolve-NovaPackageUploadOutputFileList -Times 1 -ParameterFilter { + $ProjectInfo.ProjectName -eq 'PackageProject' -and + $PackageType.Count -eq 1 -and + $PackageType[0] -eq 'Zip' + } + } + } + + It 'Get-NovaPackageUploadRequestedTypeList returns an empty list when PackageType is omitted or whitespace' { + InModuleScope $script:moduleName { + $projectInfo = [pscustomobject]@{ProjectName = 'PackageProject'} + + Mock Resolve-NovaPackageUploadTypeList {throw 'type resolution should be skipped when no package types were requested'} + + $result = @(Get-NovaPackageUploadRequestedTypeList -ProjectInfo $projectInfo -PackageType @('', ' ')) + + $result.Count | Should -Be 0 + Assert-MockCalled Resolve-NovaPackageUploadTypeList -Times 0 + } + } + + It 'Resolve-NovaPackageUploadExplicitFileList resolves requested types once and deduplicates by package path' { + InModuleScope $script:moduleName { + $projectInfo = [pscustomobject]@{ProjectName = 'PackageProject'} + + Mock Get-NovaPackageUploadRequestedTypeList {@('Zip')} + Mock Resolve-NovaPackageUploadExplicitFile { + [pscustomobject]@{ + Type = 'Zip' + PackagePath = '/tmp/project/artifacts/packages/PackageProject.2.3.4.zip' + PackageFileName = 'PackageProject.2.3.4.zip' + } + } + + $result = @(Resolve-NovaPackageUploadExplicitFileList -ProjectInfo $projectInfo -PackagePath @('/tmp/project/artifacts/packages/PackageProject.2.3.4.zip', './artifacts/packages/PackageProject.2.3.4.zip') -PackageType @('Zip')) + + $result.Count | Should -Be 1 + $result[0].PackageFileName | Should -Be 'PackageProject.2.3.4.zip' + Assert-MockCalled Get-NovaPackageUploadRequestedTypeList -Times 1 -ParameterFilter { + $ProjectInfo.ProjectName -eq 'PackageProject' -and + $PackageType.Count -eq 1 -and + $PackageType[0] -eq 'Zip' + } + Assert-MockCalled Resolve-NovaPackageUploadExplicitFile -Times 2 -ParameterFilter { + $RequestedPackageTypeList.Count -eq 1 -and + $RequestedPackageTypeList[0] -eq 'Zip' + } + } + } + + It 'Resolve-NovaPackageUploadOutputFileList resolves types before discovering artifacts in the output directory' { + InModuleScope $script:moduleName { + $projectInfo = [pscustomobject]@{ProjectName = 'PackageProject'} + + Mock Resolve-NovaPackageUploadTypeList {@('NuGet', 'Zip')} + Mock Get-NovaPackageOutputDirectory {'/tmp/project/artifacts/packages'} + Mock Test-Path {$true} -ParameterFilter {$LiteralPath -eq '/tmp/project/artifacts/packages' -and $PathType -eq 'Container'} + Mock Resolve-NovaPackageUploadOutputFileSet { + [pscustomobject]@{ + Type = $PackageType + PackagePath = "/tmp/project/artifacts/packages/PackageProject.2.3.4.$($PackageType.ToLower() )" + PackageFileName = "PackageProject.2.3.4.$($PackageType.ToLower() )" + } + } + + $result = @(Resolve-NovaPackageUploadOutputFileList -ProjectInfo $projectInfo -PackageType @('NuGet', 'Zip')) + + $result.Type | Should -Be @('NuGet', 'Zip') + Assert-MockCalled Resolve-NovaPackageUploadTypeList -Times 1 -ParameterFilter { + $ProjectInfo.ProjectName -eq 'PackageProject' -and + $PackageType.Count -eq 2 + } + Assert-MockCalled Resolve-NovaPackageUploadOutputFileSet -Times 1 -ParameterFilter {$PackageType -eq 'NuGet' -and $OutputDirectory -eq '/tmp/project/artifacts/packages'} + Assert-MockCalled Resolve-NovaPackageUploadOutputFileSet -Times 1 -ParameterFilter {$PackageType -eq 'Zip' -and $OutputDirectory -eq '/tmp/project/artifacts/packages'} + } + } + + It 'Get-NovaPackageUploadFileInfo falls back to the package path file name when none is provided' { + InModuleScope $script:moduleName { + $result = Get-NovaPackageUploadFileInfo -PackageType 'Zip' -PackagePath '/tmp/project/artifacts/packages/PackageProject.2.3.4.zip' + + $result.Type | Should -Be 'Zip' + $result.PackagePath | Should -Be '/tmp/project/artifacts/packages/PackageProject.2.3.4.zip' + $result.PackageFileName | Should -Be 'PackageProject.2.3.4.zip' + } + } + It 'Resolve-NovaPackageUploadTarget resolves target precedence correctly when ' -ForEach (Get-TestNovaPackageUploadTargetResolutionCases) { $testCase = $_ $layout = Initialize-TestNovaPackageUploadLayout -ProjectRoot (Join-Path $TestDrive $testCase.ProjectRootName)