diff --git a/CHANGELOG.md b/CHANGELOG.md index 7955eb6..7510148 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ### Changed +- `Deploy-NovaPackage` now shows a concise resolved-upload summary before execution, reports progress while multiple artifacts are uploading, and prints a short completion summary with a suggested verification step after successful raw uploads. + ### Deprecated ### Removed diff --git a/RELEASE_NOTE.md b/RELEASE_NOTE.md index d318a84..4e20f22 100644 --- a/RELEASE_NOTE.md +++ b/RELEASE_NOTE.md @@ -13,6 +13,8 @@ This file summarizes the release notes for NovaModuleTools. **UNRELEASED** chang ### Changed +- `Deploy-NovaPackage` now shows clearer terminal feedback during raw package uploads, including a concise pre-flight summary, progress across multiple artifacts, and a short verification hint after success. + ### Deprecated ### Removed diff --git a/docs/NovaModuleTools/en-US/Deploy-NovaPackage.md b/docs/NovaModuleTools/en-US/Deploy-NovaPackage.md index 4a6fc55..d86b97e 100644 --- a/docs/NovaModuleTools/en-US/Deploy-NovaPackage.md +++ b/docs/NovaModuleTools/en-US/Deploy-NovaPackage.md @@ -1,10 +1,10 @@ ---- +--- document type: cmdlet external help file: NovaModuleTools-Help.xml -HelpUri: 'https://www.novamoduletools.com/packaging-and-delivery.html#upload' +HelpUri: https://www.novamoduletools.com/packaging-and-delivery.html#upload Locale: en-US Module Name: NovaModuleTools -ms.date: 04/25/2026 +ms.date: 05.24.2026 PlatyPS schema version: 2024-05-01 title: Deploy-NovaPackage --- @@ -19,10 +19,15 @@ Uploads one or more generated package artifacts to a raw HTTP endpoint. ### __AllParameterSets -```powershell -PS> Deploy-NovaPackage [[-PackagePath] ] [[-PackageType] ] [[-Url] ] [[-Repository] ] [[-UploadPath] ] [[-Headers] ] [[-Token] ] [[-TokenEnvironmentVariable] ] [[-AuthenticationScheme] ] [-WhatIf] [-Confirm] [] +``` +Deploy-NovaPackage [[-PackagePath] ] [[-PackageType] ] [[-Url] ] + [[-Repository] ] [-WhatIf] [-Confirm] [-UploadPath ] [-Headers ] + [-Token ] [-TokenEnvironmentVariable ] [-AuthenticationScheme ] + [] ``` +## ALIASES + ## DESCRIPTION `Deploy-NovaPackage` uploads existing package artifacts that were generated by `New-NovaModulePackage`. @@ -42,6 +47,8 @@ This command is intentionally separate from `Publish-NovaModule`. `Deploy-NovaPa When you run `Deploy-NovaPackage -Confirm`, PowerShell uses its native confirmation prompt against the full resolved upload set instead of prompting once per artifact. +Before uploads start, `Deploy-NovaPackage` prints a concise summary of the resolved upload set. During multi-artifact uploads it shows progress, and after success it prints a short completion summary plus a verification hint while still returning result objects for automation. + ## EXAMPLES ### EXAMPLE 1 @@ -87,10 +94,10 @@ Uploads the explicitly selected package files instead of discovering them from t ### EXAMPLE 6 ```powershell -PS> Deploy-NovaPackage -Repository LocalNexus -WhatIf +PS> Deploy-NovaPackage -PackageType @('NuGet', 'Zip') -Repository LocalNexus ``` -Previews which package artifacts would be uploaded and which destination URLs would be used. +Uploads both matching `.nupkg` and `.zip` artifacts, shows progress as each artifact is sent, and prints a short completion summary with a suggested verification step. ### EXAMPLE 7 @@ -102,130 +109,138 @@ Uses PowerShell's native confirmation prompt for the full resolved upload set be ## PARAMETERS -### -PackagePath +### -AuthenticationScheme -Optional explicit package file path list. When omitted, the command resolves matching artifacts from the configured package output directory. +Optional authentication scheme prefix used when formatting the resolved authentication header value. ```yaml -Type: System.String[] +Type: System.String DefaultValue: '' SupportsWildcards: false -Aliases: [ ] +Aliases: [] ParameterSets: - - Name: (All) - Position: Named - IsRequired: false - ValueFromPipeline: false - ValueFromPipelineByPropertyName: false - ValueFromRemainingArguments: false +- Name: (All) + Position: Named + IsRequired: false + ValueFromPipeline: false + ValueFromPipelineByPropertyName: false + ValueFromRemainingArguments: false DontShow: false -AcceptedValues: [ ] +AcceptedValues: [] HelpMessage: '' ``` -### -PackageType +### -Confirm -Optional package type filter. Supported values follow the same normalization as `Package.Types`, including `NuGet`, -`Zip`, `.nupkg`, and `.zip`. +Prompts for confirmation before uploading package artifacts. ```yaml -Type: System.String[] -DefaultValue: '' +Type: System.Management.Automation.SwitchParameter +DefaultValue: False SupportsWildcards: false -Aliases: [ ] +Aliases: +- cf ParameterSets: - - Name: (All) - Position: Named - IsRequired: false - ValueFromPipeline: false - ValueFromPipelineByPropertyName: false - ValueFromRemainingArguments: false +- Name: (All) + Position: Named + IsRequired: false + ValueFromPipeline: false + ValueFromPipelineByPropertyName: false + ValueFromRemainingArguments: false DontShow: false -AcceptedValues: [ ] +AcceptedValues: [] HelpMessage: '' ``` -### -Url +### -Headers -Explicit raw upload base URL. This takes precedence over repository or package configuration. +Optional additional HTTP headers. These are merged with `Package.Headers` and repository-specific headers. +Optional additional HTTP headers. +These are merged with `Package.Headers` and repository-specific headers. ```yaml -Type: System.String +Type: System.Collections.Hashtable DefaultValue: '' SupportsWildcards: false -Aliases: [ ] +Aliases: [] ParameterSets: - - Name: (All) - Position: Named - IsRequired: false - ValueFromPipeline: false - ValueFromPipelineByPropertyName: false - ValueFromRemainingArguments: false +- Name: (All) + Position: Named + IsRequired: false + ValueFromPipeline: false + ValueFromPipelineByPropertyName: false + ValueFromRemainingArguments: false DontShow: false -AcceptedValues: [ ] +AcceptedValues: [] HelpMessage: '' ``` -### -Repository +### -PackagePath -Repository name to resolve from `Package.Repositories`. +Optional explicit package file path list. When omitted, the command resolves matching artifacts from the configured package output directory. +Optional explicit package file path list. +When omitted, the command resolves matching artifacts from the configured package output directory. ```yaml -Type: System.String +Type: System.String[] DefaultValue: '' SupportsWildcards: false -Aliases: [ ] +Aliases: [] ParameterSets: - - Name: (All) - Position: Named - IsRequired: false - ValueFromPipeline: false - ValueFromPipelineByPropertyName: false - ValueFromRemainingArguments: false +- Name: (All) + Position: 0 + IsRequired: false + ValueFromPipeline: false + ValueFromPipelineByPropertyName: false + ValueFromRemainingArguments: false DontShow: false -AcceptedValues: [ ] +AcceptedValues: [] HelpMessage: '' ``` -### -UploadPath +### -PackageType -Optional extra path segment appended between the resolved base URL and the uploaded package file name. +Optional package type filter. Supported values follow the same normalization as `Package.Types`, including `NuGet`, +`Zip`, `.nupkg`, and `.zip`. +Optional package type filter. +Supported values follow the same normalization as `Package.Types`, including `NuGet`, +`Zip`, `.nupkg`, and `.zip`. ```yaml -Type: System.String +Type: System.String[] DefaultValue: '' SupportsWildcards: false -Aliases: [ ] +Aliases: [] ParameterSets: - - Name: (All) - Position: Named - IsRequired: false - ValueFromPipeline: false - ValueFromPipelineByPropertyName: false - ValueFromRemainingArguments: false +- Name: (All) + Position: 1 + IsRequired: false + ValueFromPipeline: false + ValueFromPipelineByPropertyName: false + ValueFromRemainingArguments: false DontShow: false -AcceptedValues: [ ] +AcceptedValues: [] HelpMessage: '' ``` -### -Headers +### -Repository -Optional additional HTTP headers. These are merged with `Package.Headers` and repository-specific headers. +Repository name to resolve from `Package.Repositories`. ```yaml -Type: System.Collections.Hashtable +Type: System.String DefaultValue: '' SupportsWildcards: false -Aliases: [ ] +Aliases: [] ParameterSets: - - Name: (All) - Position: Named - IsRequired: false - ValueFromPipeline: false - ValueFromPipelineByPropertyName: false - ValueFromRemainingArguments: false +- Name: (All) + Position: 3 + IsRequired: false + ValueFromPipeline: false + ValueFromPipelineByPropertyName: false + ValueFromRemainingArguments: false DontShow: false -AcceptedValues: [ ] +AcceptedValues: [] HelpMessage: '' ``` @@ -237,16 +252,16 @@ Optional explicit token value used to populate the resolved authentication heade Type: System.String DefaultValue: '' SupportsWildcards: false -Aliases: [ ] +Aliases: [] ParameterSets: - - Name: (All) - Position: Named - IsRequired: false - ValueFromPipeline: false - ValueFromPipelineByPropertyName: false - ValueFromRemainingArguments: false +- Name: (All) + Position: Named + IsRequired: false + ValueFromPipeline: false + ValueFromPipelineByPropertyName: false + ValueFromRemainingArguments: false DontShow: false -AcceptedValues: [ ] +AcceptedValues: [] HelpMessage: '' ``` @@ -258,89 +273,93 @@ Optional environment variable name that holds the upload token. Type: System.String DefaultValue: '' SupportsWildcards: false -Aliases: [ ] +Aliases: [] ParameterSets: - - Name: (All) - Position: Named - IsRequired: false - ValueFromPipeline: false - ValueFromPipelineByPropertyName: false - ValueFromRemainingArguments: false +- Name: (All) + Position: Named + IsRequired: false + ValueFromPipeline: false + ValueFromPipelineByPropertyName: false + ValueFromRemainingArguments: false DontShow: false -AcceptedValues: [ ] +AcceptedValues: [] HelpMessage: '' ``` -### -AuthenticationScheme +### -UploadPath -Optional authentication scheme prefix used when formatting the resolved authentication header value. +Optional extra path segment appended between the resolved base URL and the uploaded package file name. ```yaml Type: System.String DefaultValue: '' SupportsWildcards: false -Aliases: [ ] +Aliases: [] ParameterSets: - - Name: (All) - Position: Named - IsRequired: false - ValueFromPipeline: false - ValueFromPipelineByPropertyName: false - ValueFromRemainingArguments: false +- Name: (All) + Position: Named + IsRequired: false + ValueFromPipeline: false + ValueFromPipelineByPropertyName: false + ValueFromRemainingArguments: false DontShow: false -AcceptedValues: [ ] +AcceptedValues: [] HelpMessage: '' ``` -### -WhatIf +### -Url -Shows what would happen if the cmdlet runs. The cmdlet is not run. +Explicit raw upload base URL. This takes precedence over repository or package configuration. +Explicit raw upload base URL. +This takes precedence over repository or package configuration. ```yaml -Type: System.Management.Automation.SwitchParameter -DefaultValue: False +Type: System.String +DefaultValue: '' SupportsWildcards: false -Aliases: - - wi +Aliases: [] ParameterSets: - - Name: (All) - Position: Named - IsRequired: false - ValueFromPipeline: false - ValueFromPipelineByPropertyName: false - ValueFromRemainingArguments: false +- Name: (All) + Position: 2 + IsRequired: false + ValueFromPipeline: false + ValueFromPipelineByPropertyName: false + ValueFromRemainingArguments: false DontShow: false -AcceptedValues: [ ] +AcceptedValues: [] HelpMessage: '' ``` -### -Confirm +### -WhatIf -Prompts for confirmation before uploading package artifacts. +Shows what would happen if the cmdlet runs. The cmdlet is not run. +Shows what would happen if the cmdlet runs. +The cmdlet is not run. ```yaml Type: System.Management.Automation.SwitchParameter DefaultValue: False SupportsWildcards: false Aliases: - - cf +- wi ParameterSets: - - Name: (All) - Position: Named - IsRequired: false - ValueFromPipeline: false - ValueFromPipelineByPropertyName: false - ValueFromRemainingArguments: false +- Name: (All) + Position: Named + IsRequired: false + ValueFromPipeline: false + ValueFromPipelineByPropertyName: false + ValueFromRemainingArguments: false DontShow: false -AcceptedValues: [ ] +AcceptedValues: [] HelpMessage: '' ``` ### CommonParameters -This cmdlet supports the common parameters: `-Debug`, `-ErrorAction`, `-ErrorVariable`, `-InformationAction`, -`-InformationVariable`, `-OutBuffer`, `-OutVariable`, `-PipelineVariable`, `-ProgressAction`, `-Verbose`, -`-WarningAction`, `-WarningVariable`, `-WhatIf`, and `-Confirm`. +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, +-InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, +-ProgressAction, -Verbose, -WarningAction, and -WarningVariable. For more information, see +[about_CommonParameters](https://go.microsoft.com/fwlink/?LinkID=113216). ## INPUTS diff --git a/src/private/package/InvokeNovaPackageArtifactUpload.ps1 b/src/private/package/InvokeNovaPackageArtifactUpload.ps1 index 09a0f20..c11a3c1 100644 --- a/src/private/package/InvokeNovaPackageArtifactUpload.ps1 +++ b/src/private/package/InvokeNovaPackageArtifactUpload.ps1 @@ -6,13 +6,13 @@ function Invoke-NovaPackageArtifactUpload { ) if (-not (Test-Path -LiteralPath $UploadArtifact.PackagePath -PathType Leaf)) { - Stop-NovaOperation -Message "Package file not found: $( $UploadArtifact.PackagePath )" -ErrorId 'Nova.Environment.PackageUploadFileNotFound' -Category ObjectNotFound -TargetObject $UploadArtifact.PackagePath + Stop-NovaOperation -Message "Package file not found: $( $UploadArtifact.PackagePath ). Run New-NovaModulePackage first or provide a valid -PackagePath." -ErrorId 'Nova.Environment.PackageUploadFileNotFound' -Category ObjectNotFound -TargetObject $UploadArtifact.PackagePath } try { $response = Invoke-NovaPackageUploadRequest -UploadArtifact $UploadArtifact } catch { - Stop-NovaOperation -Message "Package upload failed for $( $UploadArtifact.PackagePath ) -> $( $UploadArtifact.UploadUrl ). $( $_.Exception.Message )" -ErrorId 'Nova.Dependency.PackageUploadRequestFailed' -Category ConnectionError -TargetObject $UploadArtifact.UploadUrl + Stop-NovaOperation -Message "Package upload failed for $( $UploadArtifact.PackagePath ) -> $( $UploadArtifact.UploadUrl ). Check the upload URL, authentication token, and network access, then try again. Details: $( $_.Exception.Message )" -ErrorId 'Nova.Dependency.PackageUploadRequestFailed' -Category ConnectionError -TargetObject $UploadArtifact.UploadUrl } return [pscustomobject]@{ diff --git a/src/private/package/InvokeNovaPackageUploadWorkflow.ps1 b/src/private/package/InvokeNovaPackageUploadWorkflow.ps1 index 079d8a4..4e3ef26 100644 --- a/src/private/package/InvokeNovaPackageUploadWorkflow.ps1 +++ b/src/private/package/InvokeNovaPackageUploadWorkflow.ps1 @@ -5,14 +5,60 @@ function Invoke-NovaPackageUploadWorkflow { [object[]]$UploadArtifactList = @() ) - $resolvedUploadArtifactList = @($UploadArtifactList) - if ($resolvedUploadArtifactList.Count -eq 0 -and @($WorkflowContext.UploadArtifactList).Count -eq 0) { + $resolvedUploadArtifactList = @(Get-NovaResolvedPackageUploadArtifactList -WorkflowContext $WorkflowContext -UploadArtifactList $UploadArtifactList) + if ($resolvedUploadArtifactList.Count -eq 0) { return @() } - return @( - $resolvedUploadArtifactList | ForEach-Object { - Invoke-NovaPackageArtifactUpload -UploadArtifact $_ + $uploadResult = @() + $artifactCount = $resolvedUploadArtifactList.Count + try { + for ($index = 0; $index -lt $artifactCount; $index++) { + $uploadArtifact = $resolvedUploadArtifactList[$index] + Write-NovaPackageUploadProgress -UploadArtifact $uploadArtifact -CurrentIndex ($index + 1) -TotalCount $artifactCount + $uploadResult += Invoke-NovaPackageArtifactUpload -UploadArtifact $uploadArtifact + } + } finally { + Complete-NovaPackageUploadProgress } + + return $uploadResult +} + +function Get-NovaResolvedPackageUploadArtifactList { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext, + [object[]]$UploadArtifactList = @() + ) + + if (@($UploadArtifactList).Count -gt 0) { + return @($UploadArtifactList) + } + + return @($WorkflowContext.UploadArtifactList) +} + +function Write-NovaPackageUploadProgress { + [CmdletBinding()] + param( + [Parameter(Mandatory)][object]$UploadArtifact, + [Parameter(Mandatory)][int]$CurrentIndex, + [Parameter(Mandatory)][int]$TotalCount ) + + $percentComplete = [int](($CurrentIndex - 1) * 100 / $TotalCount) + $status = "Uploading $( $UploadArtifact.PackageFileName ) ($CurrentIndex of $TotalCount)" + if ([string]::IsNullOrWhiteSpace([string]$UploadArtifact.PackageFileName)) { + $status = "Uploading artifact $CurrentIndex of $TotalCount" + } + + Write-Progress -Activity 'Uploading package artifacts' -Status $status -PercentComplete $percentComplete +} + +function Complete-NovaPackageUploadProgress { + [CmdletBinding()] + param() + + Write-Progress -Activity 'Uploading package artifacts' -Completed } diff --git a/src/private/package/WriteNovaPackageUploadResultOutput.ps1 b/src/private/package/WriteNovaPackageUploadResultOutput.ps1 new file mode 100644 index 0000000..1a422b9 --- /dev/null +++ b/src/private/package/WriteNovaPackageUploadResultOutput.ps1 @@ -0,0 +1,50 @@ +function Write-NovaPackageUploadResultOutput { + [CmdletBinding()] + param( + [Parameter(Mandatory)][object[]]$Result + ) + + $resolvedResult = @($Result) + if ($resolvedResult.Count -eq 0) { + return + } + + Write-Host (Get-NovaPackageUploadResultSummaryMessage -Result $resolvedResult) + + $nextStepMessage = Get-NovaPackageUploadResultNextStepMessage -Result $resolvedResult + if ($null -ne $nextStepMessage) { + Write-Host $nextStepMessage + } +} + +function Get-NovaPackageUploadResultSummaryMessage { + [CmdletBinding()] + param( + [Parameter(Mandatory)][object[]]$Result + ) + + $artifactCount = @($Result).Count + if ($artifactCount -eq 1) { + return 'Uploaded 1 package artifact.' + } + + return "Uploaded $artifactCount package artifacts." +} + +function Get-NovaPackageUploadResultNextStepMessage { + [CmdletBinding()] + param( + [Parameter(Mandatory)][AllowEmptyCollection()][object[]]$Result + ) + + $resolvedResult = @($Result) + if ($resolvedResult.Count -eq 0) { + return $null + } + + if ($resolvedResult.Count -eq 1 -and -not [string]::IsNullOrWhiteSpace($resolvedResult[0].UploadUrl)) { + return "Next step: verify the uploaded artifact at $( $resolvedResult[0].UploadUrl )." + } + + return 'Next step: verify the uploaded artifacts at the target repository.' +} diff --git a/src/private/package/WriteNovaPackageUploadWorkflowContext.ps1 b/src/private/package/WriteNovaPackageUploadWorkflowContext.ps1 new file mode 100644 index 0000000..e441f8b --- /dev/null +++ b/src/private/package/WriteNovaPackageUploadWorkflowContext.ps1 @@ -0,0 +1,33 @@ +function Write-NovaPackageUploadWorkflowContext { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext + ) + + $message = Get-NovaPackageUploadWorkflowContextMessage -WorkflowContext $WorkflowContext + if ($null -ne $message) { + Write-Host $message + } + + if (-not [string]::IsNullOrWhiteSpace($WorkflowContext.Target)) { + Write-Verbose "Target: $( $WorkflowContext.Target )" + } +} + +function Get-NovaPackageUploadWorkflowContextMessage { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext + ) + + $artifactCount = @($WorkflowContext.UploadArtifactList).Count + if ($artifactCount -eq 0) { + return $null + } + + if ($artifactCount -eq 1) { + return "$( $WorkflowContext.Operation )." + } + + return "Ready to upload $artifactCount package artifacts." +} diff --git a/src/public/DeployNovaPackage.ps1 b/src/public/DeployNovaPackage.ps1 index f81e677..10af1ec 100644 --- a/src/public/DeployNovaPackage.ps1 +++ b/src/public/DeployNovaPackage.ps1 @@ -16,12 +16,17 @@ function Deploy-NovaPackage { $uploadOption = New-NovaPackageUploadOption -BoundParameters $PSBoundParameters $workflowContext = Get-NovaPackageUploadWorkflowContext -BoundParameters $PSBoundParameters -ProjectInfo $projectInfo -UploadOption $uploadOption + Write-NovaPackageUploadWorkflowContext -WorkflowContext $workflowContext + $shouldRun = $PSCmdlet.ShouldProcess($workflowContext.Target, $workflowContext.Operation) if (-not $shouldRun) { return @() } - return @(Invoke-NovaPackageUploadWorkflow -WorkflowContext $workflowContext -UploadArtifactList $workflowContext.UploadArtifactList) + $result = @(Invoke-NovaPackageUploadWorkflow -WorkflowContext $workflowContext -UploadArtifactList $workflowContext.UploadArtifactList) + Write-NovaPackageUploadResultOutput -Result $result + + return $result } } diff --git a/tests/ArchitectureGuardrails.Tests.ps1 b/tests/ArchitectureGuardrails.Tests.ps1 index a8b3eec..dc07e3d 100644 --- a/tests/ArchitectureGuardrails.Tests.ps1 +++ b/tests/ArchitectureGuardrails.Tests.ps1 @@ -82,7 +82,7 @@ Describe 'Architecture guardrails' { It 'public command files use only their approved Nova helper surface' { $testCases = @( - [pscustomobject]@{Path = 'src/public/DeployNovaPackage.ps1'; ExpectedHelpers = @('Get-NovaPackageUploadWorkflowContext', 'Get-NovaProjectInfo', 'Invoke-NovaPackageUploadWorkflow', 'New-NovaPackageUploadDynamicParameterDictionary', 'New-NovaPackageUploadOption')} + [pscustomobject]@{Path = 'src/public/DeployNovaPackage.ps1'; ExpectedHelpers = @('Get-NovaPackageUploadWorkflowContext', 'Get-NovaProjectInfo', 'Invoke-NovaPackageUploadWorkflow', 'New-NovaPackageUploadDynamicParameterDictionary', 'New-NovaPackageUploadOption', 'Write-NovaPackageUploadResultOutput', 'Write-NovaPackageUploadWorkflowContext')} [pscustomobject]@{Path = 'src/public/GetNovaProjectInfo.ps1'; ExpectedHelpers = @('Format-NovaCliVersionString', 'Get-NovaCliInstalledVersion', 'Get-NovaProjectInfoContext', 'Get-NovaProjectInfoResult')} [pscustomobject]@{Path = 'src/public/GetNovaUpdateNotificationPreference.ps1'; ExpectedHelpers = @('Get-NovaUpdateNotificationPreferenceStatus')} [pscustomobject]@{Path = 'src/public/InitializeNovaModule.ps1'; ExpectedHelpers = @('Get-NovaModuleInitializationWorkflowContext', 'Invoke-NovaModuleInitializationWorkflow')} diff --git a/tests/private/package/InvokeNovaPackageArtifactUpload.Tests.ps1 b/tests/private/package/InvokeNovaPackageArtifactUpload.Tests.ps1 index 9a72b05..7f4974c 100644 --- a/tests/private/package/InvokeNovaPackageArtifactUpload.Tests.ps1 +++ b/tests/private/package/InvokeNovaPackageArtifactUpload.Tests.ps1 @@ -17,7 +17,7 @@ Describe 'Invoke-NovaPackageArtifactUpload' { It 'throws when the package file does not exist' { Remove-Item -LiteralPath $script:file -Force - {Invoke-NovaPackageArtifactUpload -UploadArtifact $script:artifact} | Should -Throw '*Package file not found*' + {Invoke-NovaPackageArtifactUpload -UploadArtifact $script:artifact} | Should -Throw '*Run New-NovaModulePackage first or provide a valid -PackagePath*' } It 'returns a result object including the status code on success' { @@ -28,6 +28,6 @@ Describe 'Invoke-NovaPackageArtifactUpload' { It 'wraps upload errors into Stop-NovaOperation' { Mock Invoke-NovaPackageUploadRequest {throw 'boom'} - {Invoke-NovaPackageArtifactUpload -UploadArtifact $script:artifact} | Should -Throw '*Package upload failed*' + {Invoke-NovaPackageArtifactUpload -UploadArtifact $script:artifact} | Should -Throw '*Check the upload URL, authentication token, and network access, then try again*' } } diff --git a/tests/private/package/InvokeNovaPackageUploadWorkflow.Tests.ps1 b/tests/private/package/InvokeNovaPackageUploadWorkflow.Tests.ps1 index 916efa5..e15e66a 100644 --- a/tests/private/package/InvokeNovaPackageUploadWorkflow.Tests.ps1 +++ b/tests/private/package/InvokeNovaPackageUploadWorkflow.Tests.ps1 @@ -5,17 +5,45 @@ BeforeAll { } Describe 'Invoke-NovaPackageUploadWorkflow' { + BeforeEach { + Mock Write-Progress {} + } + It 'returns empty when neither supplied list nor context list has artifacts' { $ctx = [pscustomobject]@{UploadArtifactList=@()} $r = Invoke-NovaPackageUploadWorkflow -WorkflowContext $ctx @($r).Count | Should -Be 0 + Should -Invoke Write-Progress -Times 0 } - It 'uploads each supplied artifact and returns the responses' { + It 'uploads each supplied artifact, reports progress, and returns the responses' { Mock Invoke-NovaPackageArtifactUpload {[pscustomobject]@{StatusCode=200; Artifact=$UploadArtifact}} - $ctx = [pscustomobject]@{UploadArtifactList=@([pscustomobject]@{Name='a'})} - $r = @(Invoke-NovaPackageUploadWorkflow -WorkflowContext $ctx -UploadArtifactList @([pscustomobject]@{Name='a'},[pscustomobject]@{Name='b'})) + $ctx = [pscustomobject]@{UploadArtifactList=@([pscustomobject]@{PackageFileName='a.nupkg'})} + $r = @(Invoke-NovaPackageUploadWorkflow -WorkflowContext $ctx -UploadArtifactList @([pscustomobject]@{PackageFileName='a.nupkg'},[pscustomobject]@{PackageFileName='b.nupkg'})) $r.Count | Should -Be 2 Should -Invoke Invoke-NovaPackageArtifactUpload -Times 2 + Should -Invoke Write-Progress -Times 2 -ParameterFilter {-not $Completed} + Should -Invoke Write-Progress -Times 1 -ParameterFilter {$Completed} + } + + It 'uses a generic progress status when the artifact file name is blank' { + Mock Invoke-NovaPackageArtifactUpload {[pscustomobject]@{StatusCode=200; Artifact=$UploadArtifact}} + $ctx = [pscustomobject]@{UploadArtifactList=@()} + + $null = Invoke-NovaPackageUploadWorkflow -WorkflowContext $ctx -UploadArtifactList @([pscustomobject]@{PackageFileName=''}) + + Should -Invoke Write-Progress -Times 1 -ParameterFilter { + (-not $Completed) -and + $Status -eq 'Uploading artifact 1 of 1' + } + Should -Invoke Write-Progress -Times 1 -ParameterFilter {$Completed} + } + + It 'falls back to the workflow context artifact list when no explicit upload list is supplied' { + Mock Invoke-NovaPackageArtifactUpload {[pscustomobject]@{StatusCode=200; Artifact=$UploadArtifact}} + $ctx = [pscustomobject]@{UploadArtifactList=@([pscustomobject]@{PackageFileName='a.nupkg'})} + $r = @(Invoke-NovaPackageUploadWorkflow -WorkflowContext $ctx) + $r.Count | Should -Be 1 + Should -Invoke Invoke-NovaPackageArtifactUpload -Times 1 } } diff --git a/tests/private/package/WriteNovaPackageUploadResultOutput.Tests.ps1 b/tests/private/package/WriteNovaPackageUploadResultOutput.Tests.ps1 new file mode 100644 index 0000000..671831c --- /dev/null +++ b/tests/private/package/WriteNovaPackageUploadResultOutput.Tests.ps1 @@ -0,0 +1,37 @@ +BeforeAll { + $projectRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $PSScriptRoot)) + . (Join-Path $projectRoot 'src/private/package/WriteNovaPackageUploadResultOutput.ps1') +} + +Describe 'Write-NovaPackageUploadResultOutput' { + BeforeEach { + Mock Write-Host {} + } + + It 'returns no repository next-step message for an empty result list' { + $message = Get-NovaPackageUploadResultNextStepMessage -Result @() + + $message | Should -BeNullOrEmpty + } + + It 'writes a single-artifact summary and URL-specific next step' { + $result = @([pscustomobject]@{UploadUrl = 'https://packages.example/raw/NovaModuleTools.1.0.0.nupkg'}) + + Write-NovaPackageUploadResultOutput -Result $result + + Should -Invoke Write-Host -Times 1 -ParameterFilter {$Object -eq 'Uploaded 1 package artifact.'} + Should -Invoke Write-Host -Times 1 -ParameterFilter {$Object -eq 'Next step: verify the uploaded artifact at https://packages.example/raw/NovaModuleTools.1.0.0.nupkg.'} + } + + It 'writes a multi-artifact summary and repository-level next step' { + $result = @( + [pscustomobject]@{UploadUrl = 'https://packages.example/raw/a.nupkg'} + [pscustomobject]@{UploadUrl = 'https://packages.example/raw/b.nupkg'} + ) + + Write-NovaPackageUploadResultOutput -Result $result + + Should -Invoke Write-Host -Times 1 -ParameterFilter {$Object -eq 'Uploaded 2 package artifacts.'} + Should -Invoke Write-Host -Times 1 -ParameterFilter {$Object -eq 'Next step: verify the uploaded artifacts at the target repository.'} + } +} diff --git a/tests/private/package/WriteNovaPackageUploadWorkflowContext.Tests.ps1 b/tests/private/package/WriteNovaPackageUploadWorkflowContext.Tests.ps1 new file mode 100644 index 0000000..9e3f51f --- /dev/null +++ b/tests/private/package/WriteNovaPackageUploadWorkflowContext.Tests.ps1 @@ -0,0 +1,50 @@ +BeforeAll { + $projectRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $PSScriptRoot)) + . (Join-Path $projectRoot 'src/private/package/WriteNovaPackageUploadWorkflowContext.ps1') +} + +Describe 'Write-NovaPackageUploadWorkflowContext' { + BeforeEach { + Mock Write-Host {} + Mock Write-Verbose {} + } + + It 'writes nothing when no artifacts are resolved' { + $workflowContext = [pscustomobject]@{ + UploadArtifactList = @() + Target = '' + Operation = 'Upload package artifacts' + } + + Write-NovaPackageUploadWorkflowContext -WorkflowContext $workflowContext + + Should -Invoke Write-Host -Times 0 + Should -Invoke Write-Verbose -Times 0 + } + + It 'writes a ready message and verbose target for multiple artifacts' { + $workflowContext = [pscustomobject]@{ + UploadArtifactList = @([pscustomobject]@{}, [pscustomobject]@{}) + Target = 'https://packages.example/raw/' + Operation = 'Upload 2 package artifacts' + } + + Write-NovaPackageUploadWorkflowContext -WorkflowContext $workflowContext + + Should -Invoke Write-Host -Times 1 -ParameterFilter {$Object -eq 'Ready to upload 2 package artifacts.'} + Should -Invoke Write-Verbose -Times 1 -ParameterFilter {$Message -eq 'Target: https://packages.example/raw/'} + } + + It 'writes the operation message for a single artifact' { + $workflowContext = [pscustomobject]@{ + UploadArtifactList = @([pscustomobject]@{}) + Target = '' + Operation = 'Upload NuGet package artifact NovaModuleTools.1.0.0.nupkg' + } + + Write-NovaPackageUploadWorkflowContext -WorkflowContext $workflowContext + + Should -Invoke Write-Host -Times 1 -ParameterFilter {$Object -eq 'Upload NuGet package artifact NovaModuleTools.1.0.0.nupkg.'} + Should -Invoke Write-Verbose -Times 0 + } +} diff --git a/tests/public/DeployNovaPackage.TestSupport.ps1 b/tests/public/DeployNovaPackage.TestSupport.ps1 index 66a2370..b9da4bd 100644 --- a/tests/public/DeployNovaPackage.TestSupport.ps1 +++ b/tests/public/DeployNovaPackage.TestSupport.ps1 @@ -4,7 +4,9 @@ function New-NovaPackageUploadOption {param($BoundParameters) return [pscustomob function Get-NovaPackageUploadWorkflowContext {param($BoundParameters, $ProjectInfo, $UploadOption) return [pscustomobject]@{Target='https://x/repo'; Operation='Upload'; UploadArtifactList=@([pscustomobject]@{Name='a.nupkg'})} } +function Write-NovaPackageUploadWorkflowContext {param($WorkflowContext) $script:wroteContext = $true} function Invoke-NovaPackageUploadWorkflow {param($WorkflowContext, $UploadArtifactList) $script:invoked = $true return @([pscustomobject]@{StatusCode=201}) } +function Write-NovaPackageUploadResultOutput {param($Result) $script:wroteResult = $true} diff --git a/tests/public/DeployNovaPackage.Tests.ps1 b/tests/public/DeployNovaPackage.Tests.ps1 index 3b03e43..9ab9bcc 100644 --- a/tests/public/DeployNovaPackage.Tests.ps1 +++ b/tests/public/DeployNovaPackage.Tests.ps1 @@ -6,18 +6,26 @@ BeforeAll { } Describe 'Deploy-NovaPackage' { - BeforeEach {$script:invoked = $false} + BeforeEach { + $script:invoked = $false + $script:wroteContext = $false + $script:wroteResult = $false + } - It 'invokes the upload workflow and returns its results' { + It 'writes context, invokes the upload workflow, and returns its results' { $result = Deploy-NovaPackage -PackagePath '/o/a.nupkg' -Url 'https://x' -Repository 'Nexus' + $script:wroteContext | Should -BeTrue $script:invoked | Should -BeTrue + $script:wroteResult | Should -BeTrue @($result).Count | Should -Be 1 $result[0].StatusCode | Should -Be 201 } - It 'returns an empty array and does not invoke the workflow when -WhatIf is set' { + It 'writes context and returns an empty array without invoking the workflow when -WhatIf is set' { $result = Deploy-NovaPackage -PackagePath '/o/a.nupkg' -WhatIf + $script:wroteContext | Should -BeTrue $script:invoked | Should -BeFalse + $script:wroteResult | Should -BeFalse @($result).Count | Should -Be 0 } }