Skip to content

Commit b5066da

Browse files
committed
#215 feat: enhance Update-NovaModuleTool with improved workflow and WhatIf support
refactor(Update-NovaModuleTools): align with terminal-ux-design principles Fixes #228
1 parent 0903c7c commit b5066da

12 files changed

Lines changed: 364 additions & 45 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
3030
- `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.
3131
- `Set-NovaUpdateNotificationPreference` now ends with a clear success or preview summary, prints the settings file path, suggests `Get-NovaUpdateNotificationPreference` as the next verification step, and gives a more actionable validation error when no enable/disable switch is supplied.
3232
- `Test-NovaBuild` now shows progress across the main test phases, ends with the result file path plus a coverage summary and next-step hint when tests pass, uses a test-plan summary in `-WhatIf` mode, and fails with more actionable guidance when Pester or coverage checks fail.
33+
- `Update-NovaModuleTool` now ends with a visible up-to-date, preview, cancelled, or updated summary, skips prerelease confirmation during `-WhatIf`, shows progress for the actual self-update step, and suggests `Get-NovaProjectInfo -Installed` after a successful update.
3334

3435
### Deprecated
3536

RELEASE_NOTE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ This file summarizes the release notes for NovaModuleTools. **UNRELEASED** chang
2626
- `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.
2727
- `Set-NovaUpdateNotificationPreference` now ends with a clear success or preview summary, prints the settings file path, suggests `Get-NovaUpdateNotificationPreference` as the next verification step, and gives a more actionable validation error when no enable/disable switch is supplied.
2828
- `Test-NovaBuild` now shows progress across the main test phases, ends with the result file path plus a coverage summary and next-step hint when tests pass, uses a test-plan summary in `-WhatIf` mode, and fails with more actionable guidance when Pester or coverage checks fail.
29+
- `Update-NovaModuleTool` now ends with a visible up-to-date, preview, cancelled, or updated summary, skips prerelease confirmation during `-WhatIf`, shows progress for the actual self-update step, and suggests `Get-NovaProjectInfo -Installed` after a successful update.
2930

3031
### Deprecated
3132

docs/NovaModuleTools/en-US/Update-NovaModuleTools.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ When prerelease notifications are enabled, `Update-NovaModuleTool` may target a
3838

3939
Stable updates do not require prerelease confirmation.
4040

41-
After a successful update, `Update-NovaModuleTool` prints the release notes link from the installed module manifest.
41+
When a newer version is available, `Update-NovaModuleTool` shows progress while it installs the update and reads the release-notes link from the updated module. Every command path ends with a visible summary: up-to-date, preview, cancelled, or updated.
42+
43+
After a successful update, `Update-NovaModuleTool` prints the release notes link from the installed module manifest and suggests `Get-NovaProjectInfo -Installed` as the next verification step.
4244

4345
## EXAMPLES
4446

@@ -48,7 +50,7 @@ After a successful update, `Update-NovaModuleTool` prints the release notes link
4850
PS> Update-NovaModuleTool
4951
```
5052

51-
Updates the installed `NovaModuleTools` module by using the stored prerelease preference to resolve the update candidate.
53+
Updates the installed `NovaModuleTools` module by using the stored prerelease preference to resolve the update candidate, then prints the current version, target version, and the next suggested verification step.
5254

5355
### EXAMPLE 2
5456

@@ -76,13 +78,13 @@ Successful updates print the release notes link from the installed module manife
7678
PS> Update-NovaModuleTool -WhatIf
7779
```
7880

79-
Previews the resolved update action without running `Update-Module`.
81+
Previews the resolved update action without prompting for prerelease confirmation or running `Update-Module`.
8082

8183
## PARAMETERS
8284

8385
### -WhatIf
8486

85-
Shows what would happen if the cmdlet runs. `Update-NovaModuleTool` resolves the target version first, then previews the selected stable or prerelease update action without changing the installed module.
87+
Shows what would happen if the cmdlet runs. `Update-NovaModuleTool` resolves the target version first, then previews the selected stable or prerelease update action without prompting for prerelease confirmation or changing the installed module.
8688

8789
```yaml
8890
Type: System.Management.Automation.SwitchParameter
@@ -147,8 +149,11 @@ If the PowerShell Gallery cannot be reached well enough to resolve an update can
147149

148150
Use `Get-NovaUpdateNotificationPreference` and `Set-NovaUpdateNotificationPreference` to inspect or change the stored prerelease setting.
149151

152+
Use `Ctrl+C` if you need to stop an active self-update before `Update-Module` finishes.
153+
150154
## RELATED LINKS
151155

152156
- [Get-NovaUpdateNotificationPreference](./Get-NovaUpdateNotificationPreference.md)
153157
- [Set-NovaUpdateNotificationPreference](./Set-NovaUpdateNotificationPreference.md)
158+
- [Get-NovaProjectInfo](./Get-NovaProjectInfo.md)
154159
- [Invoke-NovaBuild](./Invoke-NovaBuild.md)

src/private/update/GetNovaModuleSelfUpdateWorkflowContext.ps1

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ function Get-NovaModuleSelfUpdateWorkflowContext {
44
[pscustomobject]$Preference,
55
[pscustomobject]$InstalledModule,
66
[pscustomobject]$LookupResult,
7-
[int]$TimeoutMilliseconds = 10000
7+
[hashtable]$WorkflowParams = @{}
88
)
99

1010
$resolvedPreference = if ($null -ne $Preference) {
@@ -20,7 +20,7 @@ function Get-NovaModuleSelfUpdateWorkflowContext {
2020
$resolvedLookupResult = if ($null -ne $LookupResult) {
2121
$LookupResult
2222
} else {
23-
Invoke-NovaModuleUpdateLookup -AllowPrereleaseNotifications:$resolvedPreference.PrereleaseNotificationsEnabled -TimeoutMilliseconds $TimeoutMilliseconds
23+
Invoke-NovaModuleUpdateLookup -AllowPrereleaseNotifications:$resolvedPreference.PrereleaseNotificationsEnabled -TimeoutMilliseconds 10000
2424
}
2525

2626
if ($null -eq $resolvedLookupResult) {
@@ -38,6 +38,7 @@ function Get-NovaModuleSelfUpdateWorkflowContext {
3838
Preference = $resolvedPreference
3939
InstalledModule = $resolvedInstalledModule
4040
LookupResult = $resolvedLookupResult
41+
WorkflowParams = $WorkflowParams
4142
Plan = $plan
4243
Action = $action
4344
}

src/private/update/InvokeNovaModuleSelfUpdateWorkflow.ps1

Lines changed: 183 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ function Invoke-NovaModuleSelfUpdateOrStop {
77
try {
88
$null = Invoke-NovaModuleSelfUpdate -ModuleName $Plan.ModuleName -AllowPrerelease:$Plan.UsedAllowPrerelease
99
} catch {
10-
Stop-NovaOperation -Message $_.Exception.Message -ErrorId 'Nova.Dependency.ModuleSelfUpdateFailed' -Category InvalidOperation -TargetObject $Plan.ModuleName
10+
$message = "NovaModuleTools self-update failed: $( $_.Exception.Message ) Confirm that the PowerShell Gallery is reachable and that this session can update installed modules, then rerun Update-NovaModuleTool."
11+
Stop-NovaOperation -Message $message -ErrorId 'Nova.Dependency.ModuleSelfUpdateFailed' -Category InvalidOperation -TargetObject $Plan.ModuleName
1112
}
1213
}
1314

@@ -30,16 +31,190 @@ function Complete-NovaModuleSelfUpdateResult {
3031
function Invoke-NovaModuleSelfUpdateWorkflow {
3132
[CmdletBinding()]
3233
param(
34+
[Parameter(Mandatory)][pscustomobject]$WorkflowContext,
35+
[switch]$ShouldRun
36+
)
37+
38+
$progressActivity = 'Updating NovaModuleTools'
39+
$result = $null
40+
41+
if (Test-NovaModuleSelfUpdateWorkflowShouldSkipExecution -WorkflowContext $WorkflowContext -ShouldRun:$ShouldRun) {
42+
$result = Complete-NovaModuleSelfUpdateResult -Plan $WorkflowContext.Plan -ReleaseNotesUri $null
43+
Write-NovaModuleSelfUpdateWorkflowResult -Result $result -WorkflowContext $WorkflowContext
44+
return $result
45+
}
46+
47+
try {
48+
Invoke-NovaModuleSelfUpdateWorkflowStep -Activity $progressActivity -Status (Get-NovaModuleSelfUpdateWorkflowUpdateStatus -WorkflowContext $WorkflowContext) -PercentComplete 80 -Action {
49+
Invoke-NovaModuleSelfUpdateOrStop -Plan $WorkflowContext.Plan
50+
}
51+
52+
$WorkflowContext.Plan.Updated = $true
53+
$releaseNotesUri = Invoke-NovaModuleSelfUpdateWorkflowStep -Activity $progressActivity -Status 'Reading release notes from the updated module' -PercentComplete 95 -Action {
54+
Get-NovaModuleReleaseNotesUri
55+
}
56+
57+
$result = Complete-NovaModuleSelfUpdateResult -Plan $WorkflowContext.Plan -ReleaseNotesUri $releaseNotesUri
58+
} finally {
59+
Write-Progress -Activity $progressActivity -Completed
60+
}
61+
62+
Write-NovaModuleSelfUpdateWorkflowResult -Result $result -WorkflowContext $WorkflowContext
63+
return $result
64+
}
65+
66+
function Test-NovaModuleSelfUpdateWorkflowShouldSkipExecution {
67+
[CmdletBinding()]
68+
param(
69+
[Parameter(Mandatory)][pscustomobject]$WorkflowContext,
70+
[switch]$ShouldRun
71+
)
72+
73+
return (-not $WorkflowContext.Plan.UpdateAvailable) -or (-not $ShouldRun)
74+
}
75+
76+
function Invoke-NovaModuleSelfUpdateWorkflowStep {
77+
[CmdletBinding()]
78+
param(
79+
[Parameter(Mandatory)][string]$Activity,
80+
[Parameter(Mandatory)][string]$Status,
81+
[Parameter(Mandatory)][int]$PercentComplete,
82+
[Parameter(Mandatory)][scriptblock]$Action
83+
)
84+
85+
Write-Progress -Activity $Activity -Status $Status -PercentComplete $PercentComplete
86+
& $Action
87+
}
88+
89+
function Write-NovaModuleSelfUpdateWorkflowResult {
90+
[CmdletBinding()]
91+
param(
92+
[Parameter(Mandatory)][pscustomobject]$Result,
3393
[Parameter(Mandatory)][pscustomobject]$WorkflowContext
3494
)
3595

36-
$plan = $WorkflowContext.Plan
37-
if (-not $plan.UpdateAvailable) {
38-
return Complete-NovaModuleSelfUpdateResult -Plan $plan -ReleaseNotesUri $null
96+
$whatIfEnabled = Test-NovaModuleSelfUpdateWorkflowWhatIfEnabled -WorkflowContext $WorkflowContext
97+
Write-Message (Get-NovaModuleSelfUpdateWorkflowStatusMessage -Result $Result -WhatIfEnabled:$whatIfEnabled) -color (Get-NovaModuleSelfUpdateWorkflowStatusColor -Result $Result)
98+
Write-Message "Current version: $( $Result.CurrentVersion )"
99+
100+
$targetVersionLine = Get-NovaModuleSelfUpdateWorkflowTargetVersionLine -Result $Result
101+
if (-not [string]::IsNullOrWhiteSpace($targetVersionLine)) {
102+
Write-Message $targetVersionLine
103+
}
104+
105+
$repositoryLine = Get-NovaModuleSelfUpdateWorkflowRepositoryLine -Result $Result
106+
if (-not [string]::IsNullOrWhiteSpace($repositoryLine)) {
107+
Write-Message $repositoryLine
108+
}
109+
110+
foreach ($line in (Get-NovaModuleSelfUpdateWorkflowNextStepLine -Result $Result -WhatIfEnabled:$whatIfEnabled)) {
111+
Write-Message $line
39112
}
113+
}
40114

41-
Invoke-NovaModuleSelfUpdateOrStop -Plan $plan
42-
$plan.Updated = $true
43-
$releaseNotesUri = Get-NovaModuleReleaseNotesUri
44-
return Complete-NovaModuleSelfUpdateResult -Plan $plan -ReleaseNotesUri $releaseNotesUri
115+
function Test-NovaModuleSelfUpdateWorkflowWhatIfEnabled {
116+
[CmdletBinding()]
117+
param(
118+
[Parameter(Mandatory)][pscustomobject]$WorkflowContext
119+
)
120+
121+
return $WorkflowContext.WorkflowParams.ContainsKey('WhatIf') -and $WorkflowContext.WorkflowParams.WhatIf
122+
}
123+
124+
function Get-NovaModuleSelfUpdateWorkflowUpdateStatus {
125+
[CmdletBinding()]
126+
param(
127+
[Parameter(Mandatory)][pscustomobject]$WorkflowContext
128+
)
129+
130+
if ($WorkflowContext.Plan.IsPrereleaseTarget) {
131+
return "Installing prerelease version $( $WorkflowContext.Plan.TargetVersion )"
132+
}
133+
134+
return "Installing version $( $WorkflowContext.Plan.TargetVersion )"
135+
}
136+
137+
function Get-NovaModuleSelfUpdateWorkflowStatusMessage {
138+
[CmdletBinding()]
139+
param(
140+
[Parameter(Mandatory)][pscustomobject]$Result,
141+
[switch]$WhatIfEnabled
142+
)
143+
144+
if (-not $Result.UpdateAvailable) {
145+
return 'NovaModuleTools is already up to date.'
146+
}
147+
148+
if ($WhatIfEnabled) {
149+
return 'Self-update plan ready for NovaModuleTools'
150+
}
151+
152+
if ($Result.Cancelled) {
153+
return 'Self-update cancelled for NovaModuleTools.'
154+
}
155+
156+
return "Updated NovaModuleTools to version $( $Result.TargetVersion )."
157+
}
158+
159+
function Get-NovaModuleSelfUpdateWorkflowStatusColor {
160+
[CmdletBinding()]
161+
param(
162+
[Parameter(Mandatory)][pscustomobject]$Result
163+
)
164+
165+
if ($Result.Cancelled) {
166+
return 'Blue'
167+
}
168+
169+
return 'Green'
170+
}
171+
172+
function Get-NovaModuleSelfUpdateWorkflowTargetVersionLine {
173+
[CmdletBinding()]
174+
param(
175+
[Parameter(Mandatory)][pscustomobject]$Result
176+
)
177+
178+
if (-not $Result.UpdateAvailable) {
179+
return $null
180+
}
181+
182+
return "Target version: $( $Result.TargetVersion )"
183+
}
184+
185+
function Get-NovaModuleSelfUpdateWorkflowRepositoryLine {
186+
[CmdletBinding()]
187+
param(
188+
[Parameter(Mandatory)][pscustomobject]$Result
189+
)
190+
191+
if ([string]::IsNullOrWhiteSpace($Result.LookupRepository)) {
192+
return $null
193+
}
194+
195+
return "Repository: $( $Result.LookupRepository )"
196+
}
197+
198+
function Get-NovaModuleSelfUpdateWorkflowNextStepLine {
199+
[CmdletBinding()]
200+
param(
201+
[Parameter(Mandatory)][pscustomobject]$Result,
202+
[switch]$WhatIfEnabled
203+
)
204+
205+
if (-not $Result.UpdateAvailable -or $Result.Cancelled) {
206+
return @()
207+
}
208+
209+
if ($WhatIfEnabled) {
210+
return @(
211+
'Next step:'
212+
"Run Update-NovaModuleTool without -WhatIf when you are ready to install version $( $Result.TargetVersion )."
213+
)
214+
}
215+
216+
return @(
217+
'Next step:'
218+
'Get-NovaProjectInfo -Installed'
219+
)
45220
}

src/public/UpdateNovaModuleTools.ps1

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,22 @@ function Update-NovaModuleTool {
33
[Alias('Update-NovaModuleTools')]
44
param()
55

6-
$workflowContext = Get-NovaModuleSelfUpdateWorkflowContext
6+
$workflowContext = Get-NovaModuleSelfUpdateWorkflowContext -WorkflowParams @{WhatIf = [bool]$WhatIfPreference}
77
$plan = $workflowContext.Plan
8-
if (-not $plan.UpdateAvailable) {
9-
return Complete-NovaModuleSelfUpdateResult -Plan $plan -ReleaseNotesUri $null
8+
if ($plan.IsPrereleaseTarget -and -not $WhatIfPreference) {
9+
$prereleaseConfirmed = Confirm-NovaPrereleaseModuleUpdate -Cmdlet $PSCmdlet -CurrentVersion $plan.CurrentVersion -TargetVersion $plan.TargetVersion
10+
if (-not $prereleaseConfirmed) {
11+
$plan.Cancelled = $true
12+
return Invoke-NovaModuleSelfUpdateWorkflow -WorkflowContext $workflowContext -ShouldRun:$false
13+
}
1014
}
1115

12-
if ($plan.IsPrereleaseTarget -and -not (Confirm-NovaPrereleaseModuleUpdate -Cmdlet $PSCmdlet -CurrentVersion $plan.CurrentVersion -TargetVersion $plan.TargetVersion)) {
13-
$plan.Cancelled = $true
14-
return Complete-NovaModuleSelfUpdateResult -Plan $plan -ReleaseNotesUri $null
16+
$shouldRun = $plan.UpdateAvailable -and $PSCmdlet.ShouldProcess($plan.ModuleName, $workflowContext.Action)
17+
if (-not $shouldRun) {
18+
$plan.Cancelled = (-not $WhatIfPreference) -and $plan.UpdateAvailable
1519
}
1620

17-
if (-not $PSCmdlet.ShouldProcess($plan.ModuleName, $workflowContext.Action)) {
18-
return Complete-NovaModuleSelfUpdateResult -Plan $plan -ReleaseNotesUri $null
19-
}
20-
21-
$result = Invoke-NovaModuleSelfUpdateWorkflow -WorkflowContext $workflowContext
21+
$result = Invoke-NovaModuleSelfUpdateWorkflow -WorkflowContext $workflowContext -ShouldRun:$shouldRun
2222
Write-NovaModuleReleaseNotesLink -ReleaseNotesUri $result.ReleaseNotesUri
2323
return $result
2424
}

tests/ArchitectureGuardrails.Tests.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ Describe 'Architecture guardrails' {
9595
[pscustomobject]@{Path = 'src/public/PublishNovaModule.ps1'; ExpectedHelpers = @('Get-NovaDynamicDeliveryParameterDictionary', 'Get-NovaProjectInfo', 'Get-NovaPublishWorkflowContext', 'Get-NovaShouldProcessForwardingParameter', 'Invoke-NovaPublishWorkflow', 'Write-NovaPublishWorkflowContext')}
9696
[pscustomobject]@{Path = 'src/public/SetNovaUpdateNotificationPreference.ps1'; ExpectedHelpers = @('Get-NovaUpdateNotificationPreferenceChangeContext', 'Invoke-NovaUpdateNotificationPreferenceChange')}
9797
[pscustomobject]@{Path = 'src/public/TestNovaBuild.ps1'; ExpectedHelpers = @('Get-NovaTestWorkflowContext', 'Invoke-NovaTestWorkflow', 'New-NovaTestDynamicParameterDictionary')}
98-
[pscustomobject]@{Path = 'src/public/UpdateNovaModuleTools.ps1'; ExpectedHelpers = @('Complete-NovaModuleSelfUpdateResult', 'Confirm-NovaPrereleaseModuleUpdate', 'Get-NovaModuleSelfUpdateWorkflowContext', 'Invoke-NovaModuleSelfUpdateWorkflow', 'Write-NovaModuleReleaseNotesLink')}
98+
[pscustomobject]@{Path = 'src/public/UpdateNovaModuleTools.ps1'; ExpectedHelpers = @('Confirm-NovaPrereleaseModuleUpdate', 'Get-NovaModuleSelfUpdateWorkflowContext', 'Invoke-NovaModuleSelfUpdateWorkflow', 'Write-NovaModuleReleaseNotesLink')}
9999
[pscustomobject]@{Path = 'src/public/UpdateNovaModuleVersion.ps1'; ExpectedHelpers = @('Get-NovaVersionUpdateWorkflowContext', 'Invoke-NovaVersionUpdateCiActivation', 'Invoke-NovaVersionUpdateWorkflow', 'Write-NovaVersionUpdateResultOutput')}
100100
)
101101
$expectedPaths = @($testCases | ForEach-Object Path | Sort-Object)

tests/private/update/GetNovaModuleSelfUpdateWorkflowContext.Tests.ps1

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,17 @@ Describe 'Get-NovaModuleSelfUpdateWorkflowContext' {
2828
$ctx.Action | Should -Be 'Update NovaModuleTools to prerelease version 3.0.0-rc1'
2929
}
3030

31+
It 'carries forwarded workflow parameters into the context' {
32+
Mock Read-NovaUpdateNotificationPreference {[pscustomobject]@{PrereleaseNotificationsEnabled=$false}}
33+
Mock Get-NovaInstalledModuleVersionInfo {[pscustomobject]@{Version='1.0.0'}}
34+
Mock Invoke-NovaModuleUpdateLookup {[pscustomobject]@{Version='2.0.0'}}
35+
Mock Get-NovaModuleSelfUpdatePlan {[pscustomobject]@{TargetVersion='2.0.0'; IsPrereleaseTarget=$false}}
36+
37+
$ctx = Get-NovaModuleSelfUpdateWorkflowContext -WorkflowParams @{WhatIf = $true}
38+
39+
$ctx.WorkflowParams.WhatIf | Should -BeTrue
40+
}
41+
3142
It 'throws when no lookup result is available' {
3243
Mock Read-NovaUpdateNotificationPreference {[pscustomobject]@{PrereleaseNotificationsEnabled=$false}}
3344
Mock Get-NovaInstalledModuleVersionInfo {[pscustomobject]@{Version='1.0.0'}}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
function Invoke-NovaModuleSelfUpdate {param([string]$ModuleName,[switch]$AllowPrerelease)}
22
function Get-NovaModuleReleaseNotesUri {'https://example.com/notes'}
33
function Stop-NovaOperation {param([string]$Message,[string]$ErrorId,$Category,$TargetObject) throw [System.Management.Automation.ErrorRecord]::new([System.Exception]::new($Message),$ErrorId,$Category,$TargetObject)}
4+
function Write-Message {param([string]$Text, [string]$color)}
5+
function Write-Progress {param([string]$Activity, [string]$Status, [int]$PercentComplete, [switch]$Completed)}

0 commit comments

Comments
 (0)