Skip to content

Commit ea1763c

Browse files
committed
#215 feat: enhance Publish-NovaModule with progress reporting and next-step guidance
refactor(Publish-NovaModule): align with terminal-ux-design principles Fixes #225
1 parent 849d3bc commit ea1763c

6 files changed

Lines changed: 266 additions & 20 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
2727
- `Invoke-NovaCli` now treats blank command input as root help, reports unknown commands with clearer recovery guidance, and ships a dedicated PlatyPS help topic for the PowerShell wrapper surface.
2828
- `Invoke-NovaRelease` now shows progress for the main release phases, ends with the publish target plus a next-step hint, and uses a plan summary instead of a success summary when `-WhatIf` previews the release.
2929
- `New-NovaModulePackage` now shows progress for build validation and artifact creation, ends with the package target plus the next suggested deployment step, and uses a package-plan summary in `-WhatIf` mode.
30+
- `Publish-NovaModule` now shows progress for build validation, publish, local import, and CI restore phases, ends with the publish target plus a suggested verification step, and uses a publish-plan summary in `-WhatIf` mode.
3031

3132
### Deprecated
3233

RELEASE_NOTE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ This file summarizes the release notes for NovaModuleTools. **UNRELEASED** chang
2323
- `Invoke-NovaCli` now treats blank command input as root help, reports unknown commands with clearer recovery guidance, and ships a dedicated help topic for the PowerShell wrapper surface.
2424
- `Invoke-NovaRelease` now shows progress for the main release phases, ends with the publish target plus a next-step hint, and uses a plan summary instead of a success summary when `-WhatIf` previews the release.
2525
- `New-NovaModulePackage` now shows progress for build validation and artifact creation, ends with the package target plus the next suggested deployment step, and uses a package-plan summary in `-WhatIf` mode.
26+
- `Publish-NovaModule` now shows progress for build validation, publish, local import, and CI restore phases, ends with the publish target plus a suggested verification step, and uses a publish-plan summary in `-WhatIf` mode.
2627

2728
### Deprecated
2829

docs/NovaModuleTools/en-US/Publish-NovaModule.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,12 @@ Use `-ContinuousIntegration` when the same CI/self-hosting session should switch
4545
Use `-OverrideWarning` only when you intentionally want the nested build to continue even though a file under
4646
`src/public` contains zero or multiple top-level functions.
4747

48-
This command supports `-WhatIf` and `-Confirm` through PowerShell `SupportsShouldProcess`. Use `-WhatIf` to preview the resolved publish target and workflow without building, testing, or publishing.
48+
This command supports `-WhatIf` and `-Confirm` through PowerShell `SupportsShouldProcess`. Use `-WhatIf` when you want the normal build-validation flow to run but the nested publish step to stay in preview mode.
4949

5050
Use `-Confirm` when you want PowerShell to prompt before the publish workflow starts.
5151

52+
During a publish run, Nova shows progress for the build-validation phase, the publish phase, the local import phase when applicable, and the CI restore phase when `-ContinuousIntegration` is used. When the run completes, Nova prints the publish target together with the next suggested verification step. In `-WhatIf` mode, Nova ends with a publish-plan summary instead of a publish-completed summary.
53+
5254
## EXAMPLES
5355

5456
### EXAMPLE 1
@@ -89,7 +91,7 @@ Prompts before the repository publish workflow starts.
8991
PS> Publish-NovaModule -Local -WhatIf
9092
```
9193

92-
Previews the local publish workflow and target directory without making changes. No module copy or import happens when `-WhatIf` is used.
94+
Runs the normal build-validation flow, then previews the local publish target directory without copying or importing the module.
9395

9496
### EXAMPLE 6
9597

@@ -300,6 +302,8 @@ When `-ContinuousIntegration` is used, Nova restores the built `dist/` module af
300302
`Publish-NovaModule` uses `SupportsShouldProcess`, so `Get-Help Publish-NovaModule -Full` should surface native
301303
`-WhatIf` and `-Confirm` support.
302304

305+
Use `Ctrl+C` if you need to stop a running publish workflow before the publish step finishes.
306+
303307
## RELATED LINKS
304308

305309
- [Invoke-NovaBuild](./Invoke-NovaBuild.md)

src/private/release/InvokeNovaPublishWorkflow.ps1

Lines changed: 152 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@ function Test-NovaPublishWorkflowShouldImportLocalModule {
88
return $ShouldRun -and $null -ne $WorkflowContext.LocalPublishActivation
99
}
1010

11+
function Test-NovaPublishWorkflowWhatIfEnabled {
12+
[CmdletBinding()]
13+
param(
14+
[Parameter(Mandatory)][pscustomobject]$WorkflowContext
15+
)
16+
17+
return $WorkflowContext.WorkflowParams.ContainsKey('WhatIf') -and $WorkflowContext.WorkflowParams.WhatIf
18+
}
19+
1120
function Invoke-NovaPublishWorkflowCiRestore {
1221
[CmdletBinding()]
1322
param(
@@ -22,6 +31,19 @@ function Invoke-NovaPublishWorkflowCiRestore {
2231
}
2332
}
2433

34+
function Invoke-NovaPublishWorkflowStep {
35+
[CmdletBinding()]
36+
param(
37+
[Parameter(Mandatory)][string]$Activity,
38+
[Parameter(Mandatory)][string]$Status,
39+
[Parameter(Mandatory)][int]$PercentComplete,
40+
[Parameter(Mandatory)][scriptblock]$Action
41+
)
42+
43+
Write-Progress -Activity $Activity -Status $Status -PercentComplete $PercentComplete
44+
& $Action
45+
}
46+
2547
function Invoke-NovaPublishWorkflow {
2648
[CmdletBinding()]
2749
param(
@@ -32,20 +54,140 @@ function Invoke-NovaPublishWorkflow {
3254
$publishParams = $WorkflowContext.PublishParams
3355
$continuousIntegrationRequested = ($WorkflowContext.PSObject.Properties.Name -contains 'ContinuousIntegrationRequested') -and $WorkflowContext.ContinuousIntegrationRequested
3456
$shouldImportPublishedLocalModule = Test-NovaPublishWorkflowShouldImportLocalModule -WorkflowContext $WorkflowContext -ShouldRun:$ShouldRun
35-
$ciRestoreAction = ${function:Invoke-NovaPublishWorkflowCiRestore}
36-
$importBuiltModuleForCiAction = ${function:Import-NovaBuiltModuleForCi}
57+
$whatIfEnabled = Test-NovaPublishWorkflowWhatIfEnabled -WorkflowContext $WorkflowContext
58+
$progressActivity = 'Running Nova publish workflow'
59+
$importedLocalModule = $false
60+
$restoredBuiltModule = $false
61+
62+
try {
63+
Invoke-NovaPublishWorkflowStep -Activity $progressActivity -Status (Get-NovaPublishWorkflowValidationStatus -WorkflowContext $WorkflowContext) -PercentComplete 35 -Action {
64+
Invoke-NovaBuildValidation -WorkflowContext $WorkflowContext
65+
}
66+
67+
Invoke-NovaPublishWorkflowStep -Activity $progressActivity -Status (Get-NovaPublishWorkflowPublishStatus -WorkflowContext $WorkflowContext -WhatIfEnabled:$whatIfEnabled) -PercentComplete 75 -Action {
68+
& $WorkflowContext.PublishInvocation.Action @publishParams
69+
}
70+
71+
if ($shouldImportPublishedLocalModule) {
72+
Invoke-NovaPublishWorkflowStep -Activity $progressActivity -Status 'Importing the published local module' -PercentComplete 90 -Action {
73+
$null = & $WorkflowContext.LocalPublishActivation.ImportAction -ProjectName $WorkflowContext.PublishInvocation.Parameters.ProjectInfo.ProjectName -ManifestPath $WorkflowContext.LocalPublishActivation.ManifestPath
74+
}
75+
$importedLocalModule = $true
76+
Write-Verbose "Module copy to local path complete and imported from $( $WorkflowContext.LocalPublishActivation.ManifestPath )"
77+
}
78+
79+
if ($ShouldRun -and $continuousIntegrationRequested) {
80+
Invoke-NovaPublishWorkflowStep -Activity $progressActivity -Status 'Refreshing the current session with the built module' -PercentComplete 98 -Action {
81+
$null = Import-NovaBuiltModuleForCi -ProjectInfo $WorkflowContext.ProjectInfo
82+
}
83+
$restoredBuiltModule = $true
84+
}
85+
} finally {
86+
Write-Progress -Activity $progressActivity -Completed
87+
}
3788

38-
Invoke-NovaBuildValidation -WorkflowContext $WorkflowContext
89+
Write-NovaPublishWorkflowResult -WorkflowContext $WorkflowContext -WhatIfEnabled:$whatIfEnabled -ImportedLocalModule:$importedLocalModule -RestoredBuiltModule:$restoredBuiltModule
90+
}
3991

40-
& $WorkflowContext.PublishInvocation.Action @publishParams
92+
function Get-NovaPublishWorkflowValidationStatus {
93+
[CmdletBinding()]
94+
param(
95+
[Parameter(Mandatory)][pscustomobject]$WorkflowContext
96+
)
4197

42-
if (-not $shouldImportPublishedLocalModule) {
43-
& $ciRestoreAction -WorkflowContext $WorkflowContext -ShouldRun:$ShouldRun -ContinuousIntegrationRequested:$continuousIntegrationRequested -ImportBuiltModuleForCiAction $importBuiltModuleForCiAction
44-
return
98+
if ($WorkflowContext.SkipTestsRequested) {
99+
return 'Building publish output with tests skipped'
45100
}
46101

47-
$null = & $WorkflowContext.LocalPublishActivation.ImportAction -ProjectName $WorkflowContext.PublishInvocation.Parameters.ProjectInfo.ProjectName -ManifestPath $WorkflowContext.LocalPublishActivation.ManifestPath
48-
Write-Verbose "Module copy to local path complete and imported from $( $WorkflowContext.LocalPublishActivation.ManifestPath )"
102+
return 'Building and testing publish output'
103+
}
49104

50-
& $ciRestoreAction -WorkflowContext $WorkflowContext -ShouldRun:$ShouldRun -ContinuousIntegrationRequested:$continuousIntegrationRequested -ImportBuiltModuleForCiAction $importBuiltModuleForCiAction
105+
function Get-NovaPublishWorkflowPublishStatus {
106+
[CmdletBinding()]
107+
param(
108+
[Parameter(Mandatory)][pscustomobject]$WorkflowContext,
109+
[switch]$WhatIfEnabled
110+
)
111+
112+
$targetDescription = if ($WorkflowContext.PublishInvocation.IsLocal) {
113+
'the local module path'
114+
} else {
115+
"repository $( $WorkflowContext.PublishInvocation.Target )"
116+
}
117+
118+
if ($WhatIfEnabled) {
119+
return "Previewing publish to $targetDescription"
120+
}
121+
122+
return "Publishing to $targetDescription"
123+
}
124+
125+
function Write-NovaPublishWorkflowResult {
126+
[CmdletBinding()]
127+
param(
128+
[Parameter(Mandatory)][pscustomobject]$WorkflowContext,
129+
[switch]$WhatIfEnabled,
130+
[switch]$ImportedLocalModule,
131+
[switch]$RestoredBuiltModule
132+
)
133+
134+
Write-Message (Get-NovaPublishWorkflowStatusMessage -WorkflowContext $WorkflowContext -WhatIfEnabled:$WhatIfEnabled) -color Green
135+
Write-Message "Publish target: $( $WorkflowContext.PublishInvocation.Target )"
136+
137+
if ($WorkflowContext.SkipTestsRequested) {
138+
Write-Message 'Pre-publish tests were skipped for this run.'
139+
}
140+
141+
if ($ImportedLocalModule) {
142+
Write-Message "The published local module is loaded from $( $WorkflowContext.LocalPublishActivation.ManifestPath )."
143+
}
144+
145+
if ($RestoredBuiltModule) {
146+
Write-Message 'The freshly built dist module is loaded again for later commands in this session.'
147+
}
148+
149+
foreach ($line in (Get-NovaPublishWorkflowNextStepLine -WorkflowContext $WorkflowContext -WhatIfEnabled:$WhatIfEnabled)) {
150+
Write-Message $line
151+
}
152+
}
153+
154+
function Get-NovaPublishWorkflowStatusMessage {
155+
[CmdletBinding()]
156+
param(
157+
[Parameter(Mandatory)][pscustomobject]$WorkflowContext,
158+
[switch]$WhatIfEnabled
159+
)
160+
161+
if ($WhatIfEnabled) {
162+
return "Publish plan ready for $( $WorkflowContext.ProjectInfo.ProjectName )"
163+
}
164+
165+
return "Published Nova module: $( $WorkflowContext.ProjectInfo.ProjectName )"
166+
}
167+
168+
function Get-NovaPublishWorkflowNextStepLine {
169+
[CmdletBinding()]
170+
param(
171+
[Parameter(Mandatory)][pscustomobject]$WorkflowContext,
172+
[switch]$WhatIfEnabled
173+
)
174+
175+
if ($WhatIfEnabled) {
176+
return @(
177+
'Next step:'
178+
'Run Publish-NovaModule without -WhatIf when you are ready to publish the module.'
179+
)
180+
}
181+
182+
if ($WorkflowContext.PublishInvocation.IsLocal) {
183+
return @(
184+
'Next step:'
185+
'Get-NovaProjectInfo -Installed'
186+
)
187+
}
188+
189+
return @(
190+
'Next step:'
191+
"Find-Module $( $WorkflowContext.ProjectInfo.ProjectName ) -Repository $( $WorkflowContext.PublishInvocation.Target )"
192+
)
51193
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
function Import-NovaBuiltModuleForCi {
2+
param($ProjectInfo)
3+
4+
$script:ciImportCalls += 1
5+
}
6+
7+
function Invoke-NovaBuildValidation {
8+
param($WorkflowContext)
9+
10+
$script:validationCalls += 1
11+
}
12+
13+
function Write-Message {
14+
param(
15+
[string]$Text,
16+
[string]$color
17+
)
18+
}
19+
20+
function Write-Progress {
21+
param(
22+
[string]$Activity,
23+
[string]$Status,
24+
[int]$PercentComplete,
25+
[switch]$Completed
26+
)
27+
}

tests/private/release/InvokeNovaPublishWorkflow.Tests.ps1

Lines changed: 79 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
BeforeAll {
22
$projectRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $PSScriptRoot))
33
. (Join-Path $projectRoot 'src/private/release/InvokeNovaPublishWorkflow.ps1')
4-
function Import-NovaBuiltModuleForCi {param($ProjectInfo) $script:ciImportCalls += 1}
5-
function Invoke-NovaBuildValidation {param($WorkflowContext) $script:validationCalls += 1}
4+
. (Join-Path $PSScriptRoot 'InvokeNovaPublishWorkflow.TestSupport.ps1')
65
}
76

87
Describe 'Test-NovaPublishWorkflowShouldImportLocalModule' {
@@ -20,6 +19,18 @@ Describe 'Test-NovaPublishWorkflowShouldImportLocalModule' {
2019
}
2120
}
2221

22+
Describe 'Test-NovaPublishWorkflowWhatIfEnabled' {
23+
It 'returns true when WorkflowParams.WhatIf is enabled' {
24+
$ctx = [pscustomobject]@{WorkflowParams = @{WhatIf = $true}}
25+
Test-NovaPublishWorkflowWhatIfEnabled -WorkflowContext $ctx | Should -BeTrue
26+
}
27+
28+
It 'returns false when WorkflowParams.WhatIf is not enabled' {
29+
$ctx = [pscustomobject]@{WorkflowParams = @{}}
30+
Test-NovaPublishWorkflowWhatIfEnabled -WorkflowContext $ctx | Should -BeFalse
31+
}
32+
}
33+
2334
Describe 'Invoke-NovaPublishWorkflowCiRestore' {
2435
It 'invokes the importer when ShouldRun and CI requested' {
2536
$script:calls = 0
@@ -41,39 +52,99 @@ Describe 'Invoke-NovaPublishWorkflow' {
4152
$script:publishCalls = 0
4253
$script:localImportCalls = 0
4354
$script:ciImportCalls = 0
55+
Mock Write-Message {}
56+
Mock Write-Progress {}
4457
}
4558

46-
It 'runs validation and publish; skips local import when activation is null' {
59+
It 'runs validation and publish, reports progress, and suggests how to verify repository publish' {
4760
Mock Invoke-NovaBuildValidation {$script:validationCalls += 1}
4861
$ctx = [pscustomobject]@{
4962
PublishParams = @{}
50-
PublishInvocation = [pscustomobject]@{Action = {param() $script:publishCalls += 1}}
63+
PublishInvocation = [pscustomobject]@{Action = {param() $script:publishCalls += 1}; Target = 'PSGallery'; IsLocal = $false}
5164
LocalPublishActivation = $null
52-
ProjectInfo = [pscustomobject]@{}
65+
ProjectInfo = [pscustomobject]@{ProjectName = 'NovaModuleTools'}
66+
WorkflowParams = @{}
67+
SkipTestsRequested = $false
5368
ContinuousIntegrationRequested = $false
5469
}
5570
Invoke-NovaPublishWorkflow -WorkflowContext $ctx -ShouldRun
5671
$script:validationCalls | Should -Be 1
5772
$script:publishCalls | Should -Be 1
5873
$script:localImportCalls | Should -Be 0
74+
Assert-MockCalled Write-Progress -Times 3
75+
Assert-MockCalled Write-Message -Times 4
76+
Assert-MockCalled Write-Message -Times 1 -ParameterFilter {
77+
$Text -eq 'Published Nova module: NovaModuleTools' -and $color -eq 'Green'
78+
}
79+
Assert-MockCalled Write-Message -Times 1 -ParameterFilter {
80+
$Text -eq 'Find-Module NovaModuleTools -Repository PSGallery'
81+
}
5982
}
6083

61-
It 'imports local published module when LocalPublishActivation is set' {
84+
It 'imports the local published module, restores the built module in CI, and reports the result' {
6285
Mock Invoke-NovaBuildValidation {$script:validationCalls += 1}
6386
$ctx = [pscustomobject]@{
6487
PublishParams = @{}
6588
PublishInvocation = [pscustomobject]@{
6689
Action = {param() $script:publishCalls += 1}
90+
IsLocal = $true
91+
Target = '/modules'
6792
Parameters = [pscustomobject]@{ProjectInfo = [pscustomobject]@{ProjectName='Mod'}}
6893
}
6994
LocalPublishActivation = [pscustomobject]@{
7095
ManifestPath='/m/Mod.psd1'
7196
ImportAction = {param($ProjectName,$ManifestPath) $script:localImportCalls += 1}
7297
}
73-
ProjectInfo = [pscustomobject]@{}
74-
ContinuousIntegrationRequested = $false
98+
ProjectInfo = [pscustomobject]@{ProjectName = 'Mod'}
99+
WorkflowParams = @{}
100+
SkipTestsRequested = $true
101+
ContinuousIntegrationRequested = $true
75102
}
76103
Invoke-NovaPublishWorkflow -WorkflowContext $ctx -ShouldRun
77104
$script:localImportCalls | Should -Be 1
105+
$script:ciImportCalls | Should -Be 1
106+
Assert-MockCalled Write-Progress -Times 5
107+
Assert-MockCalled Write-Message -Times 6
108+
Assert-MockCalled Write-Message -Times 1 -ParameterFilter {
109+
$Text -eq 'Pre-publish tests were skipped for this run.'
110+
}
111+
Assert-MockCalled Write-Message -Times 1 -ParameterFilter {
112+
$Text -eq 'The published local module is loaded from /m/Mod.psd1.'
113+
}
114+
Assert-MockCalled Write-Message -Times 1 -ParameterFilter {
115+
$Text -eq 'The freshly built dist module is loaded again for later commands in this session.'
116+
}
117+
Assert-MockCalled Write-Message -Times 1 -ParameterFilter {
118+
$Text -eq 'Get-NovaProjectInfo -Installed'
119+
}
120+
}
121+
122+
It 'writes a publish plan summary in WhatIf mode without importing or restoring modules' {
123+
Mock Invoke-NovaBuildValidation {$script:validationCalls += 1}
124+
$ctx = [pscustomobject]@{
125+
PublishParams = @{WhatIf = $true}
126+
PublishInvocation = [pscustomobject]@{Action = {param() $script:publishCalls += 1}; Target = 'PSGallery'; IsLocal = $false}
127+
LocalPublishActivation = [pscustomobject]@{
128+
ManifestPath='/m/Mod.psd1'
129+
ImportAction = {param($ProjectName,$ManifestPath) $script:localImportCalls += 1}
130+
}
131+
ProjectInfo = [pscustomobject]@{ProjectName = 'NovaModuleTools'}
132+
WorkflowParams = @{WhatIf = $true}
133+
SkipTestsRequested = $false
134+
ContinuousIntegrationRequested = $true
135+
}
136+
137+
Invoke-NovaPublishWorkflow -WorkflowContext $ctx
138+
139+
$script:publishCalls | Should -Be 1
140+
$script:localImportCalls | Should -Be 0
141+
$script:ciImportCalls | Should -Be 0
142+
Assert-MockCalled Write-Message -Times 4
143+
Assert-MockCalled Write-Message -Times 1 -ParameterFilter {
144+
$Text -eq 'Publish plan ready for NovaModuleTools' -and $color -eq 'Green'
145+
}
146+
Assert-MockCalled Write-Message -Times 1 -ParameterFilter {
147+
$Text -eq 'Run Publish-NovaModule without -WhatIf when you are ready to publish the module.'
148+
}
78149
}
79150
}

0 commit comments

Comments
 (0)