diff --git a/CHANGELOG.md b/CHANGELOG.md index 88e3886..fda1aab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), - `Get-NovaProjectInfo` now fails with clearer recovery guidance when `-Path` does not exist, points to a file, or the target folder is missing `project.json`. - `Get-NovaUpdateNotificationPreference -Verbose` now explains whether Nova is reading a stored preference or the built-in default, and the command help now points read-only PowerShell users to the matching `% nova notification` workflow. - `Initialize-NovaModule` now shows scaffold progress, ends with the created project root plus the next suggested cmdlet, and fails with clearer guidance when the target project folder already exists. +- `Install-NovaCli` now prints the installed launcher path, suggests the next command to run, and gives a clearer `PATH` warning when the destination directory is not yet available from the shell. +- `Invoke-NovaAgenticCopilotScaffold` now shows apply progress, ends with the project root plus suggested review and validation steps, and fails with clearer cancellation guidance when the overwrite warning is declined. +- `Invoke-NovaBuild` now shows progress for the main build phases, ends with the output module directory plus the next suggested validation step, and explains the refreshed session when `-ContinuousIntegration` reloads the built module. +- `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. +- `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. +- `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. +- `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. +- `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. +- `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. +- `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. +- `Update-NovaModuleVersion` now ends with a visible preview, cancelled, or updated summary, prints the resolved version file plus the detected/applied release label, shows progress while writing `project.json`, and suggests the next command to run after the bump. ### Deprecated diff --git a/RELEASE_NOTE.md b/RELEASE_NOTE.md index 8d6b3ce..d7cae15 100644 --- a/RELEASE_NOTE.md +++ b/RELEASE_NOTE.md @@ -17,6 +17,17 @@ This file summarizes the release notes for NovaModuleTools. **UNRELEASED** chang - `Get-NovaProjectInfo` now explains how to recover when `-Path` is invalid or the target folder is not a Nova project root. - `Get-NovaUpdateNotificationPreference -Verbose` now tells you whether Nova is using a stored update-notification preference or the built-in default, and the help now points to the matching `% nova notification` workflow. - `Initialize-NovaModule` now shows scaffold progress, ends with the project root and the next suggested cmdlet, and gives clearer recovery guidance when the target project folder already exists. +- `Install-NovaCli` now prints the installed launcher path, suggests the next command to run, and gives a clearer `PATH` warning when the launcher directory is not yet available from the shell. +- `Invoke-NovaAgenticCopilotScaffold` now shows apply progress, ends with the project root plus suggested review and validation steps, and gives clearer cancellation guidance when the overwrite warning is declined. +- `Invoke-NovaBuild` now shows progress for the main build phases, ends with the output module directory plus the next suggested validation step, and explains the refreshed session when `-ContinuousIntegration` reloads the built module. +- `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. +- `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. +- `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. +- `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. +- `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. +- `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. +- `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. +- `Update-NovaModuleVersion` now ends with a visible preview, cancelled, or updated summary, prints the resolved version file plus the detected/applied release label, shows progress while writing `project.json`, and suggests the next command to run after the bump. ### Deprecated diff --git a/docs/NovaModuleTools/en-US/Install-NovaCli.md b/docs/NovaModuleTools/en-US/Install-NovaCli.md index 03dc02d..35647ec 100644 --- a/docs/NovaModuleTools/en-US/Install-NovaCli.md +++ b/docs/NovaModuleTools/en-US/Install-NovaCli.md @@ -30,7 +30,8 @@ PS> Install-NovaCli [[-DestinationDirectory] ] [-Force] [-WhatIf] [-Conf By default, the launcher is installed to `~/.local/bin/nova` on macOS and Linux. If that directory is not on your `PATH`, the command warns so you can update your shell profile. -After a successful install, `Install-NovaCli` also prints the release notes link from the installed module manifest. +After a successful install, `Install-NovaCli` prints the installed launcher path, suggests the next command to run, +and also prints the release notes link from the installed module manifest. Use this command when you want `nova` to be available directly from your shell as the launcher-oriented CLI entrypoint. @@ -168,8 +169,13 @@ Returns the installed command name, destination directory, installed path, and w ## NOTES -After running `Install-NovaCli`, add the destination directory to your shell `PATH` if needed. +`Install-NovaCli` currently supports macOS and Linux. On Windows, use `Invoke-NovaCli` from `pwsh` after importing +NovaModuleTools. + +After running `Install-NovaCli`, add the destination directory to your shell `PATH` if needed, start a new shell, +and then run `nova --help` to confirm the launcher is available. ## RELATED LINKS +- [Update-NovaModuleTool](./Update-NovaModuleTools.md) - [Publish-NovaModule](./Publish-NovaModule.md) diff --git a/docs/NovaModuleTools/en-US/Invoke-NovaAgenticCopilotScaffold.md b/docs/NovaModuleTools/en-US/Invoke-NovaAgenticCopilotScaffold.md index 3d2bbc9..351f34f 100644 --- a/docs/NovaModuleTools/en-US/Invoke-NovaAgenticCopilotScaffold.md +++ b/docs/NovaModuleTools/en-US/Invoke-NovaAgenticCopilotScaffold.md @@ -34,6 +34,8 @@ By default the workflow shows an overwrite warning before it updates the managed The target path must contain a valid `project.json`. Invalid project metadata or an invalid short name stops the command with a clear validation error. +During apply or refresh, Nova shows progress for the main scaffold phases and ends with the project root plus the next suggested review and validation steps. + ## EXAMPLES ### EXAMPLE 1 @@ -201,6 +203,8 @@ This workflow does not persist `ShortName` to `project.json` and does not infer `README.md`, `CHANGELOG.md`, and `RELEASE_NOTE.md` are created only when they are missing. +Press `Ctrl+C` before confirming the overwrite warning if you want to cancel the apply without changing files. + ## RELATED LINKS - [Initialize-NovaModule](./Initialize-NovaModule.md) diff --git a/docs/NovaModuleTools/en-US/Invoke-NovaBuild.md b/docs/NovaModuleTools/en-US/Invoke-NovaBuild.md index 073ad7a..1dffe62 100644 --- a/docs/NovaModuleTools/en-US/Invoke-NovaBuild.md +++ b/docs/NovaModuleTools/en-US/Invoke-NovaBuild.md @@ -29,6 +29,8 @@ PS> Invoke-NovaBuild [-ContinuousIntegration] [-OverrideWarning] [-WhatIf] [-Con This command supports `-WhatIf` and `-Confirm` through PowerShell `SupportsShouldProcess`. Use `-WhatIf` to preview the build target without clearing `dist/` or generating new build output. +During a real build, Nova shows progress for the main build phases and ends with the output module directory plus the next suggested validation step. + Use `-ContinuousIntegration` when the same PowerShell session needs to keep using the freshly built `dist/` module after the build completes. In CI/self-hosting flows, that re-activates the built module before the command returns. Use `-OverrideWarning` only when you intentionally want to continue a build even though a file under `src/public` @@ -62,7 +64,7 @@ If `Preamble` is configured, those lines are written at the very top of the gene PS> Invoke-NovaBuild ``` -Builds the current project into `dist//`. +Builds the current project into `dist//`, prints the output module directory, and suggests `Test-NovaBuild` as the next validation step. ### EXAMPLE 2 @@ -70,7 +72,7 @@ Builds the current project into `dist//`. PS> Invoke-NovaBuild -Verbose ``` -Builds the current project and writes verbose progress for the build workflow. +Builds the current project, shows phase progress for the build workflow, and writes verbose details from the underlying build helpers. ### EXAMPLE 3 @@ -172,6 +174,8 @@ When `-ContinuousIntegration` is used together with a real build, the command re Files under `src/public` are expected to contain exactly one top-level function each. Use `-OverrideWarning` only when you intentionally want to bypass that guard for the current build. +Use `Ctrl+C` if you need to stop a running build before all phases complete. + ## RELATED LINKS - [Get-NovaProjectInfo](./Get-NovaProjectInfo.md) diff --git a/docs/NovaModuleTools/en-US/Invoke-NovaCli.md b/docs/NovaModuleTools/en-US/Invoke-NovaCli.md new file mode 100644 index 0000000..5ebe9a7 --- /dev/null +++ b/docs/NovaModuleTools/en-US/Invoke-NovaCli.md @@ -0,0 +1,191 @@ +--- +document type: cmdlet +external help file: NovaModuleTools-Help.xml +HelpUri: '' +Locale: en-US +Module Name: NovaModuleTools +ms.date: 05/24/2026 +PlatyPS schema version: 2024-05-01 +title: Invoke-NovaCli +--- + +# Invoke-NovaCli + +## SYNOPSIS + +Runs Nova's launcher-style command routing from PowerShell. + +## SYNTAX + +### __AllParameterSets + +```text +PS> Invoke-NovaCli [[-Command] ] [[-Arguments] ] [-WhatIf] [-Confirm] [] +``` + +## DESCRIPTION + +`Invoke-NovaCli` is the PowerShell entrypoint behind the launcher-style `nova ` workflow. + +Use this cmdlet when you want Nova's routed CLI experience from inside PowerShell, including root help, per-command help, and the routed command surfaces such as `build`, `test`, `package`, `publish`, `release`, `update`, and `notification`. + +When `-Command` is omitted or blank, Nova falls back to the root CLI help. + +This cmdlet forwards `-WhatIf` and `-Confirm` semantics to the routed command surface instead of treating `Invoke-NovaCli` itself as the mutating operation owner. + +Use `Get-Help Invoke-NovaCli -Full` when you want the PowerShell wrapper help. Use `nova --help`, `nova --help`, or `nova --help ` when you want launcher-native CLI help. + +## EXAMPLES + +### EXAMPLE 1 + +```text +PS> Invoke-NovaCli +``` + +Shows the root Nova CLI help. + +### EXAMPLE 2 + +```text +PS> Invoke-NovaCli build --help +``` + +Shows the short help for the routed `build` command by using launcher-style arguments from PowerShell. + +### EXAMPLE 3 + +```text +PS> Invoke-NovaCli --help build +``` + +Shows the long help for the routed `build` command. + +### EXAMPLE 4 + +```text +PS> Invoke-NovaCli publish --repository PSGallery --api-key -WhatIf +``` + +Previews the routed publish workflow without changing files or publishing artifacts. + +## PARAMETERS + +### -Arguments + +Pass the remaining launcher-style arguments for the routed command. + +Use CLI syntax here, for example `--help`, `--verbose`, `--what-if`, or route-specific flags such as `--repository`. + +```yaml +Type: System.String[] +DefaultValue: '' +SupportsWildcards: false +Aliases: [] +ParameterSets: + - Name: (All) + Position: 1 + IsRequired: false + ValueFromPipeline: false + ValueFromPipelineByPropertyName: false + ValueFromRemainingArguments: true +DontShow: false +AcceptedValues: [] +HelpMessage: '' +``` + +### -Command + +Choose the routed Nova command to run, for example `build`, `test`, `package`, `publish`, `release`, `notification`, `update`, or the root help/version forms such as `--help` and `--version`. + +When this parameter is omitted or blank, Nova shows the root CLI help. + +```yaml +Type: System.String +DefaultValue: '' +SupportsWildcards: false +Aliases: [] +ParameterSets: + - Name: (All) + Position: 0 + IsRequired: false + ValueFromPipeline: false + ValueFromPipelineByPropertyName: false + ValueFromRemainingArguments: false +DontShow: false +AcceptedValues: [] +HelpMessage: '' +``` + +### -Confirm + +Forward confirmation behavior to routed mutating commands that support Nova's CLI confirmation flow. + +```yaml +Type: System.Management.Automation.SwitchParameter +DefaultValue: '' +SupportsWildcards: false +Aliases: + - cf +ParameterSets: + - Name: (All) + Position: Named + IsRequired: false + ValueFromPipeline: false + ValueFromPipelineByPropertyName: false + ValueFromRemainingArguments: false +DontShow: false +AcceptedValues: [] +HelpMessage: '' +``` + +### -WhatIf + +Forward preview behavior to routed commands that support Nova's non-destructive preview flow. + +```yaml +Type: System.Management.Automation.SwitchParameter +DefaultValue: '' +SupportsWildcards: false +Aliases: + - wi +ParameterSets: + - Name: (All) + Position: Named + IsRequired: false + ValueFromPipeline: false + ValueFromPipelineByPropertyName: false + ValueFromRemainingArguments: false +DontShow: false +AcceptedValues: [] +HelpMessage: '' +``` + +### CommonParameters + +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 + +### None + +You can't pipe objects to this cmdlet. + +## OUTPUTS + +### System.Object + +This cmdlet returns the routed command result or CLI help text for help and version requests. + +## NOTES + +`Invoke-NovaCli` is a PowerShell wrapper around Nova's launcher-style command routing. The routed command surfaces keep CLI syntax such as `--help`, `--verbose`, and `--what-if`. + +Use `Ctrl+C` if you need to cancel a running routed workflow. + +## RELATED LINKS + +- [Invoke-NovaBuild](./Invoke-NovaBuild.md) +- [Test-NovaBuild](./Test-NovaBuild.md) +- [Install-NovaCli](./Install-NovaCli.md) +- [NovaModuleTools Module](./NovaModuleTools.md) diff --git a/docs/NovaModuleTools/en-US/Invoke-NovaRelease.md b/docs/NovaModuleTools/en-US/Invoke-NovaRelease.md index c8e3efe..30f32bb 100644 --- a/docs/NovaModuleTools/en-US/Invoke-NovaRelease.md +++ b/docs/NovaModuleTools/en-US/Invoke-NovaRelease.md @@ -57,6 +57,8 @@ When local release mode is selected, the resolved local publish target is previe This command supports `-WhatIf` and `-Confirm` through PowerShell `SupportsShouldProcess`. Use `-WhatIf` to preview the entire release workflow and resolved publish target without building, testing, versioning, or publishing. +During the workflow, Nova shows progress for the main release phases. When the workflow completes, Nova prints the publish target and a short next-step hint. In `-WhatIf` mode, Nova ends with a release-plan summary instead of a release-complete message. + ## EXAMPLES ### EXAMPLE 1 @@ -323,6 +325,8 @@ When `-ContinuousIntegration` is used, the release workflow restores the built ` `Invoke-NovaRelease` uses `SupportsShouldProcess`, so `Get-Help Invoke-NovaRelease -Full` surfaces native `-WhatIf` and `-Confirm` support. +Use `Ctrl+C` if you need to stop a running release before all phases complete. + ## RELATED LINKS - [Invoke-NovaBuild](./Invoke-NovaBuild.md) diff --git a/docs/NovaModuleTools/en-US/New-NovaModulePackage.md b/docs/NovaModuleTools/en-US/New-NovaModulePackage.md index ae12493..772b4ba 100644 --- a/docs/NovaModuleTools/en-US/New-NovaModulePackage.md +++ b/docs/NovaModuleTools/en-US/New-NovaModulePackage.md @@ -35,6 +35,8 @@ Use `-OverrideWarning` only when you intentionally want the nested build to cont The package is written to `artifacts/packages/` by default. You can override generic package metadata through the optional `Package` section in `project.json`. +During a packaging run, Nova shows progress for the build-validation phase and the artifact-creation phase. When packaging completes, Nova prints the package target together with the next suggested step. In `-WhatIf` mode, Nova ends with a package-plan summary instead of a package-created summary. + Use this `project.json` shape when you want to control package types and the package output directory: ```json @@ -275,6 +277,8 @@ When `-SkipTests` is used, only `Test-NovaBuild` is skipped. Build still runs. Files under `src/public` are expected to contain exactly one top-level function each. `-OverrideWarning` bypasses that guard only for the current packaging run. +Use `Ctrl+C` if you need to stop a running packaging workflow before the package artifacts are created. + ## RELATED LINKS - [Invoke-NovaBuild](./Invoke-NovaBuild.md) diff --git a/docs/NovaModuleTools/en-US/NovaModuleTools.md b/docs/NovaModuleTools/en-US/NovaModuleTools.md index 840c2a4..0dd02f5 100644 --- a/docs/NovaModuleTools/en-US/NovaModuleTools.md +++ b/docs/NovaModuleTools/en-US/NovaModuleTools.md @@ -32,6 +32,10 @@ Shows whether prerelease update notifications are enabled. Stable release notifi Installs the bundled command-line launcher into a user command directory on macOS or Linux. +### `PS> Invoke-NovaCli` + +Runs Nova's launcher-style command routing from inside PowerShell. + ### `PS> Invoke-NovaBuild` Builds the current NovaModuleTools project into a ready-to-import PowerShell module. diff --git a/docs/NovaModuleTools/en-US/Publish-NovaModule.md b/docs/NovaModuleTools/en-US/Publish-NovaModule.md index a1ae783..2fd8e6c 100644 --- a/docs/NovaModuleTools/en-US/Publish-NovaModule.md +++ b/docs/NovaModuleTools/en-US/Publish-NovaModule.md @@ -45,10 +45,12 @@ Use `-ContinuousIntegration` when the same CI/self-hosting session should switch Use `-OverrideWarning` only when you intentionally want the nested build to continue even though a file under `src/public` contains zero or multiple top-level functions. -This command supports `-WhatIf` and `-Confirm` through PowerShell `SupportsShouldProcess`. Use `-WhatIf` to preview the resolved publish target and workflow without building, testing, or publishing. +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. Use `-Confirm` when you want PowerShell to prompt before the publish workflow starts. +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. + ## EXAMPLES ### EXAMPLE 1 @@ -89,7 +91,7 @@ Prompts before the repository publish workflow starts. PS> Publish-NovaModule -Local -WhatIf ``` -Previews the local publish workflow and target directory without making changes. No module copy or import happens when `-WhatIf` is used. +Runs the normal build-validation flow, then previews the local publish target directory without copying or importing the module. ### EXAMPLE 6 @@ -300,6 +302,8 @@ When `-ContinuousIntegration` is used, Nova restores the built `dist/` module af `Publish-NovaModule` uses `SupportsShouldProcess`, so `Get-Help Publish-NovaModule -Full` should surface native `-WhatIf` and `-Confirm` support. +Use `Ctrl+C` if you need to stop a running publish workflow before the publish step finishes. + ## RELATED LINKS - [Invoke-NovaBuild](./Invoke-NovaBuild.md) diff --git a/docs/NovaModuleTools/en-US/Set-NovaUpdateNotificationPreference.md b/docs/NovaModuleTools/en-US/Set-NovaUpdateNotificationPreference.md index 6c09762..be8b3ee 100644 --- a/docs/NovaModuleTools/en-US/Set-NovaUpdateNotificationPreference.md +++ b/docs/NovaModuleTools/en-US/Set-NovaUpdateNotificationPreference.md @@ -38,6 +38,8 @@ The same stored preference is also used by `Update-NovaModuleTool` (alias: `Upda Stable self-updates remain available and do not require prerelease eligibility. +When the preference changes, Nova prints the settings file path together with the next recommended verification command. In `-WhatIf` mode, Nova ends with a preview summary instead of writing the preference file. + ## EXAMPLES ### EXAMPLE 1 @@ -46,7 +48,7 @@ Stable self-updates remain available and do not require prerelease eligibility. PS> Set-NovaUpdateNotificationPreference -DisablePrereleaseNotifications ``` -Turns off prerelease self-update eligibility and restricts `Update-NovaModuleTool` to stable releases only. +Turns off prerelease self-update eligibility, keeps stable releases available, and suggests `Get-NovaUpdateNotificationPreference` as the next verification step. ### EXAMPLE 2 @@ -55,7 +57,7 @@ PS> Set-NovaUpdateNotificationPreference -EnablePrereleaseNotifications ``` Turns prerelease self-update eligibility back on, which allows `Update-NovaModuleTool` / -`Update-NovaModuleTools` to consider a prerelease target again. +`Update-NovaModuleTools` to consider a prerelease target again and suggests `Get-NovaUpdateNotificationPreference` as the next verification step. ### EXAMPLE 3 @@ -63,7 +65,7 @@ Turns prerelease self-update eligibility back on, which allows `Update-NovaModul PS> Set-NovaUpdateNotificationPreference -DisablePrereleaseNotifications -WhatIf ``` -Previews the change that would disable prerelease self-update eligibility. +Previews the change that would disable prerelease self-update eligibility without writing the settings file. ### EXAMPLE 4 @@ -195,6 +197,8 @@ Returns the current prerelease self-update state, the always-available stable-up Use this command together with `Get-NovaUpdateNotificationPreference` when you want to confirm the stored setting. +Use `-WhatIf` or `-Confirm` when you want an easy way to preview or stop the change before the settings file is updated. + ## RELATED LINKS - [Get-NovaUpdateNotificationPreference](./Get-NovaUpdateNotificationPreference.md) diff --git a/docs/NovaModuleTools/en-US/Test-NovaBuild.md b/docs/NovaModuleTools/en-US/Test-NovaBuild.md index 26a5b9d..e2a5146 100644 --- a/docs/NovaModuleTools/en-US/Test-NovaBuild.md +++ b/docs/NovaModuleTools/en-US/Test-NovaBuild.md @@ -40,6 +40,8 @@ With the default This command supports `-WhatIf` and `-Confirm` through PowerShell `SupportsShouldProcess`. Use `-WhatIf` to preview the planned test run and XML output path without creating `artifacts/` or invoking Pester. +During a test run, Nova shows progress for the optional pre-test build, test-result preparation, the Pester run, result writing, and code-coverage validation. When tests pass, Nova prints the result file path, a coverage summary when one is available, and a suggested next step. In `-WhatIf` mode, Nova ends with a test-plan summary instead of invoking Pester. + ## EXAMPLES ### EXAMPLE 1 @@ -48,7 +50,7 @@ This command supports `-WhatIf` and `-Confirm` through PowerShell `SupportsShoul PS> Test-NovaBuild ``` -Runs the Pester tests for the current project. +Runs the Pester tests for the current project and prints the result file path plus the next suggested command when the run succeeds. ### EXAMPLE 2 @@ -56,7 +58,7 @@ Runs the Pester tests for the current project. PS> Test-NovaBuild -Build ``` -Builds the project first, then runs the configured Pester test workflow. +Builds the project first, then runs the configured Pester test workflow with the same completion summary as a normal test run. ### EXAMPLE 3 @@ -88,7 +90,7 @@ Overrides the console output settings for the current test run while keeping col PS> Test-NovaBuild -WhatIf ``` -Previews the planned Pester run without executing tests or writing `artifacts/TestResults.xml`. +Previews the planned Pester run, prints the planned result file path, and does not execute tests or write `artifacts/TestResults.xml`. ### EXAMPLE 7 @@ -96,7 +98,7 @@ Previews the planned Pester run without executing tests or writing `artifacts/Te PS> Test-NovaBuild -Build -WhatIf ``` -Previews the build-before-test workflow without rebuilding the project or running Pester. +Previews the build-before-test workflow, including the planned result file path and configured coverage target, without rebuilding the project or running Pester. ## PARAMETERS @@ -258,6 +260,8 @@ If `project.json` configures `Pester.CodeCoverage.CoveragePercentTarget`, `Test- `Test-NovaBuild` uses `SupportsShouldProcess`, so `Get-Help Test-NovaBuild -Full` surfaces native `-WhatIf` and `-Confirm` support. +Use `Ctrl+C` if you need to stop a running test workflow before Pester completes. + ## RELATED LINKS - [Get-NovaProjectInfo](./Get-NovaProjectInfo.md) diff --git a/docs/NovaModuleTools/en-US/Update-NovaModuleTools.md b/docs/NovaModuleTools/en-US/Update-NovaModuleTools.md index 2bc054a..2b4bda4 100644 --- a/docs/NovaModuleTools/en-US/Update-NovaModuleTools.md +++ b/docs/NovaModuleTools/en-US/Update-NovaModuleTools.md @@ -38,7 +38,9 @@ When prerelease notifications are enabled, `Update-NovaModuleTool` may target a Stable updates do not require prerelease confirmation. -After a successful update, `Update-NovaModuleTool` prints the release notes link from the installed module manifest. +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. + +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. ## EXAMPLES @@ -48,7 +50,7 @@ After a successful update, `Update-NovaModuleTool` prints the release notes link PS> Update-NovaModuleTool ``` -Updates the installed `NovaModuleTools` module by using the stored prerelease preference to resolve the update candidate. +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. ### EXAMPLE 2 @@ -76,13 +78,13 @@ Successful updates print the release notes link from the installed module manife PS> Update-NovaModuleTool -WhatIf ``` -Previews the resolved update action without running `Update-Module`. +Previews the resolved update action without prompting for prerelease confirmation or running `Update-Module`. ## PARAMETERS ### -WhatIf -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. +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. ```yaml Type: System.Management.Automation.SwitchParameter @@ -147,8 +149,11 @@ If the PowerShell Gallery cannot be reached well enough to resolve an update can Use `Get-NovaUpdateNotificationPreference` and `Set-NovaUpdateNotificationPreference` to inspect or change the stored prerelease setting. +Use `Ctrl+C` if you need to stop an active self-update before `Update-Module` finishes. + ## RELATED LINKS - [Get-NovaUpdateNotificationPreference](./Get-NovaUpdateNotificationPreference.md) - [Set-NovaUpdateNotificationPreference](./Set-NovaUpdateNotificationPreference.md) +- [Get-NovaProjectInfo](./Get-NovaProjectInfo.md) - [Invoke-NovaBuild](./Invoke-NovaBuild.md) diff --git a/docs/NovaModuleTools/en-US/Update-NovaModuleVersion.md b/docs/NovaModuleTools/en-US/Update-NovaModuleVersion.md index 37ad859..989e097 100644 --- a/docs/NovaModuleTools/en-US/Update-NovaModuleVersion.md +++ b/docs/NovaModuleTools/en-US/Update-NovaModuleVersion.md @@ -43,7 +43,7 @@ When Git tags exist, only commits since the latest tag are considered. If Git-ba If the repository exists but has no commits yet, the command stops with: `Cannot bump version because the repository has no commits yet. Create an initial commit first.` -This command supports `-WhatIf` and `-Confirm` through PowerShell `SupportsShouldProcess`. Use `-WhatIf` to preview the calculated release label and the exact next version without changing the stored version. +This command supports `-WhatIf` and `-Confirm` through PowerShell `SupportsShouldProcess`. Use `-WhatIf` to preview the calculated release label and the exact next version without changing the stored version. The command now ends with a clear plan, cancellation, or success summary, prints the resolved version file plus the detected/applied release label, and suggests the next step to run. Use `-ContinuousIntegration` when the same session should first re-activate the built `dist/` module before the version bump workflow starts. This is useful in CI/self-hosting flows where an earlier command changed the active module state. @@ -58,7 +58,7 @@ When the current version is already a prerelease for the selected release line, PS> Update-NovaModuleVersion ``` -Updates the version in the current project using the release label inferred from recent commit messages. +Updates the version in the current project using the release label inferred from recent commit messages, then prints the version file plus the next suggested validation step. ### EXAMPLE 2 @@ -81,7 +81,7 @@ Label: Minor CommitCount: 12 ``` -Shows the calculated version update without modifying `project.json`. +Shows the calculated version update without modifying `project.json`, then ends with a version-update plan summary and a reminder to rerun without `-WhatIf` when you are ready to write the new version. ### EXAMPLE 4 @@ -111,7 +111,7 @@ Label: Minor CommitCount: 12 ``` -Shows how `-Preview` keeps the detected semantic label for reporting but deterministically enters the next patch preview track when the current version is stable. +Shows how `-Preview` keeps the detected semantic label for reporting but deterministically enters the next patch preview track when the current version is stable, while the plan summary still points to the exact next version that would be written. ### EXAMPLE 6 @@ -165,7 +165,7 @@ Label: Major CommitCount: 34 ``` -Shows how stable `0.y.z` bumps still warn that `1.0.0` must be set manually when the API becomes stable, while breaking-change commits on that line continue to plan the next minor version instead of jumping straight to `1.0.0`. +Shows how stable `0.y.z` bumps still warn that `1.0.0` must be set manually when the API becomes stable, while breaking-change commits on that line continue to plan the next minor version instead of jumping straight to `1.0.0`. The plan output also keeps both the detected and applied release labels visible. ### EXAMPLE 10 diff --git a/src/private/build/InvokeNovaBuildWorkflow.ps1 b/src/private/build/InvokeNovaBuildWorkflow.ps1 index 79063e4..b59ba8f 100644 --- a/src/private/build/InvokeNovaBuildWorkflow.ps1 +++ b/src/private/build/InvokeNovaBuildWorkflow.ps1 @@ -7,19 +7,81 @@ function Invoke-NovaBuildWorkflow { $projectInfo = $WorkflowContext.ProjectInfo $continuousIntegrationRequested = ($WorkflowContext.PSObject.Properties.Name -contains 'ContinuousIntegrationRequested') -and $WorkflowContext.ContinuousIntegrationRequested $overrideWarningRequested = ($WorkflowContext.PSObject.Properties.Name -contains 'OverrideWarningRequested') -and $WorkflowContext.OverrideWarningRequested + $progressActivity = 'Building Nova module' - Assert-NovaPublicFunctionFileLayout -ProjectInfo $projectInfo -OverrideWarningRequested:$overrideWarningRequested - Reset-ProjectDist -ProjectInfo $projectInfo -Confirm:$false - Build-Module -ProjectInfo $projectInfo - Invoke-NovaBuildDuplicateValidation -ProjectInfo $projectInfo - Build-Manifest -ProjectInfo $projectInfo - Build-Help -ProjectInfo $projectInfo - Copy-ProjectResource -ProjectInfo $projectInfo - Invoke-NovaModuleUpdateNotificationSafely - - if ($continuousIntegrationRequested) { - $null = Import-NovaBuiltModuleForCi -ProjectInfo $projectInfo + try { + Invoke-NovaBuildWorkflowStep -Activity $progressActivity -Status 'Validating public command layout' -PercentComplete 10 -Action { + Assert-NovaPublicFunctionFileLayout -ProjectInfo $projectInfo -OverrideWarningRequested:$overrideWarningRequested + } + + Invoke-NovaBuildWorkflowStep -Activity $progressActivity -Status 'Resetting dist output' -PercentComplete 20 -Action { + Reset-ProjectDist -ProjectInfo $projectInfo -Confirm:$false + } + + Invoke-NovaBuildWorkflowStep -Activity $progressActivity -Status 'Building module output' -PercentComplete 35 -Action { + Build-Module -ProjectInfo $projectInfo + } + + Invoke-NovaBuildWorkflowStep -Activity $progressActivity -Status 'Validating duplicate function names' -PercentComplete 50 -Action { + Invoke-NovaBuildDuplicateValidation -ProjectInfo $projectInfo + } + + Invoke-NovaBuildWorkflowStep -Activity $progressActivity -Status 'Writing module manifest' -PercentComplete 65 -Action { + Build-Manifest -ProjectInfo $projectInfo + } + + Invoke-NovaBuildWorkflowStep -Activity $progressActivity -Status 'Building command help' -PercentComplete 78 -Action { + Build-Help -ProjectInfo $projectInfo + } + + Invoke-NovaBuildWorkflowStep -Activity $progressActivity -Status 'Copying project resources' -PercentComplete 88 -Action { + Copy-ProjectResource -ProjectInfo $projectInfo + } + + Invoke-NovaBuildWorkflowStep -Activity $progressActivity -Status 'Checking update notifications' -PercentComplete 94 -Action { + Invoke-NovaModuleUpdateNotificationSafely + } + + if ($continuousIntegrationRequested) { + Invoke-NovaBuildWorkflowStep -Activity $progressActivity -Status 'Refreshing the current session with the built module' -PercentComplete 98 -Action { + $null = Import-NovaBuiltModuleForCi -ProjectInfo $projectInfo + } + } + } finally { + Write-Progress -Activity $progressActivity -Completed } + + Write-NovaBuildWorkflowResult -ProjectInfo $projectInfo -ContinuousIntegrationRequested:$continuousIntegrationRequested +} + +function Invoke-NovaBuildWorkflowStep { + [CmdletBinding()] + param( + [Parameter(Mandatory)][string]$Activity, + [Parameter(Mandatory)][string]$Status, + [Parameter(Mandatory)][int]$PercentComplete, + [Parameter(Mandatory)][scriptblock]$Action + ) + + Write-Progress -Activity $Activity -Status $Status -PercentComplete $PercentComplete + & $Action +} + +function Write-NovaBuildWorkflowResult { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$ProjectInfo, + [switch]$ContinuousIntegrationRequested + ) + + Write-Message "Built Nova module: $( $ProjectInfo.ProjectName )" -color Green + Write-Message "Output module: $( $ProjectInfo.OutputModuleDir )" + + if ($ContinuousIntegrationRequested) { + Write-Message 'The freshly built dist module is loaded for later commands in this session.' + } + + Write-Message 'Next step: Test-NovaBuild' } function Invoke-NovaBuildDuplicateValidation { @@ -34,4 +96,3 @@ function Invoke-NovaBuildDuplicateValidation { Assert-BuiltModuleHasNoDuplicateFunctionName -ProjectInfo $ProjectInfo } - diff --git a/src/private/cli/InvokeNovaCliCommandRoute.ps1 b/src/private/cli/InvokeNovaCliCommandRoute.ps1 index 3e4c570..d63f499 100644 --- a/src/private/cli/InvokeNovaCliCommandRoute.ps1 +++ b/src/private/cli/InvokeNovaCliCommandRoute.ps1 @@ -216,7 +216,7 @@ function Invoke-NovaCliCommandRoute { return Get-NovaCliHelp } default { - Stop-NovaOperation -Message "Unknown command: <$command> | Use 'nova --help' to see available commands." -ErrorId 'Nova.Validation.UnknownCliCommand' -Category InvalidArgument -TargetObject $command + Stop-NovaOperation -Message "Unknown nova command: $command. Run 'nova --help' to list available commands, or 'nova --help ' for command-specific help." -ErrorId 'Nova.Validation.UnknownCliCommand' -Category InvalidArgument -TargetObject $command } } } diff --git a/src/private/cli/InvokeNovaCliInstallWorkflow.ps1 b/src/private/cli/InvokeNovaCliInstallWorkflow.ps1 index 2a07714..1920c60 100644 --- a/src/private/cli/InvokeNovaCliInstallWorkflow.ps1 +++ b/src/private/cli/InvokeNovaCliInstallWorkflow.ps1 @@ -7,16 +7,52 @@ function Invoke-NovaCliInstallWorkflow { $installedPath = Copy-NovaCliLauncher -SourcePath $WorkflowContext.SourcePath -TargetPath $WorkflowContext.TargetPath -Force:$WorkflowContext.Force $directoryOnPath = Test-NovaCliDirectoryOnPath -Directory $WorkflowContext.TargetDirectory if (-not $directoryOnPath) { - Write-Warning "Installed nova to $( $WorkflowContext.TargetDirectory ), but that directory is not currently in PATH. Add it to your shell profile before using nova directly from zsh/bash." + Write-Warning "Installed nova to $( $WorkflowContext.TargetDirectory ), but that directory is not currently in PATH. Add it to your shell profile, start a new shell, and then run 'nova --help'." } $releaseNotesUri = Get-NovaModuleReleaseNotesUri - - return [pscustomobject]@{ + $result = [pscustomobject]@{ CommandName = 'nova' InstalledPath = $installedPath DestinationDirectory = $WorkflowContext.TargetDirectory DirectoryOnPath = $directoryOnPath ReleaseNotesUri = $releaseNotesUri } + + Write-NovaCliInstallResult -Result $result + return $result +} + +function Write-NovaCliInstallResult { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$Result + ) + + Write-Host "Installed nova launcher: $( $Result.InstalledPath )" + + foreach ($line in (Get-NovaCliInstallNextStepLine -Result $Result)) { + Write-Host $line + } +} + +function Get-NovaCliInstallNextStepLine { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$Result + ) + + if ($Result.DirectoryOnPath) { + return @( + 'Next step:' + 'nova --help' + ) + } + + return @( + 'Next steps:' + "Add $( $Result.DestinationDirectory ) to your PATH" + 'Start a new shell' + 'nova --help' + ) } diff --git a/src/private/package/InvokeNovaPackageWorkflow.ps1 b/src/private/package/InvokeNovaPackageWorkflow.ps1 index 8df2045..c3b2978 100644 --- a/src/private/package/InvokeNovaPackageWorkflow.ps1 +++ b/src/private/package/InvokeNovaPackageWorkflow.ps1 @@ -5,11 +5,140 @@ function Invoke-NovaPackageWorkflow { [switch]$ShouldRun ) - Invoke-NovaBuildValidation -WorkflowContext $WorkflowContext + $whatIfEnabled = Test-NovaPackageWorkflowWhatIfEnabled -WorkflowContext $WorkflowContext + $progressActivity = 'Creating Nova package artifacts' - if (-not $ShouldRun) { - return + try { + Invoke-NovaPackageWorkflowStep -Activity $progressActivity -Status (Get-NovaPackageValidationStatus -WorkflowContext $WorkflowContext) -PercentComplete 30 -Action { + Invoke-NovaBuildValidation -WorkflowContext $WorkflowContext + } + + if (-not $ShouldRun) { + if ($whatIfEnabled) { + Write-NovaPackageWorkflowResult -WorkflowContext $WorkflowContext -WhatIfEnabled + } + + return + } + + $artifacts = Invoke-NovaPackageWorkflowStep -Activity $progressActivity -Status 'Creating package artifacts' -PercentComplete 85 -Action { + Invoke-NovaPackageArtifactCreation -WorkflowContext $WorkflowContext + } + } finally { + Write-Progress -Activity $progressActivity -Completed + } + + Write-NovaPackageWorkflowResult -WorkflowContext $WorkflowContext -Artifacts $artifacts + return $artifacts +} + +function Invoke-NovaPackageWorkflowStep { + [CmdletBinding()] + param( + [Parameter(Mandatory)][string]$Activity, + [Parameter(Mandatory)][string]$Status, + [Parameter(Mandatory)][int]$PercentComplete, + [Parameter(Mandatory)][scriptblock]$Action + ) + + Write-Progress -Activity $Activity -Status $Status -PercentComplete $PercentComplete + & $Action +} + +function Test-NovaPackageWorkflowWhatIfEnabled { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext + ) + + return ($WorkflowContext.WorkflowParams.ContainsKey('WhatIf') -and $WorkflowContext.WorkflowParams.WhatIf) +} + +function Get-NovaPackageValidationStatus { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext + ) + + if ($WorkflowContext.SkipTestsRequested) { + return 'Building package input with tests skipped' } - return Invoke-NovaPackageArtifactCreation -WorkflowContext $WorkflowContext + return 'Building and testing package input' +} + +function Write-NovaPackageWorkflowResult { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext, + [AllowNull()][AllowEmptyCollection()][object[]]$Artifacts = @(), + [switch]$WhatIfEnabled + ) + + $resolvedArtifacts = @($Artifacts) + $statusMessage = Get-NovaPackageWorkflowStatusMessage -WorkflowContext $WorkflowContext -ArtifactCount $resolvedArtifacts.Count -WhatIfEnabled:$WhatIfEnabled + Write-Message $statusMessage -color Green + Write-Message "Package target: $( Get-NovaPackageWorkflowResultTarget -WorkflowContext $WorkflowContext -Artifacts $resolvedArtifacts -WhatIfEnabled:$WhatIfEnabled )" + + foreach ($line in (Get-NovaPackageWorkflowNextStepLine -WhatIfEnabled:$WhatIfEnabled)) { + Write-Message $line + } +} + +function Get-NovaPackageWorkflowStatusMessage { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext, + [Parameter(Mandatory)][int]$ArtifactCount, + [switch]$WhatIfEnabled + ) + + if ($WhatIfEnabled) { + return "Package plan ready for $( $WorkflowContext.ProjectInfo.ProjectName )" + } + + if ($ArtifactCount -eq 1) { + return "Created 1 package artifact for $( $WorkflowContext.ProjectInfo.ProjectName )" + } + + return "Created $ArtifactCount package artifacts for $( $WorkflowContext.ProjectInfo.ProjectName )" +} + +function Get-NovaPackageWorkflowResultTarget { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext, + [Parameter(Mandatory)][AllowEmptyCollection()][object[]]$Artifacts, + [switch]$WhatIfEnabled + ) + + if ($WhatIfEnabled) { + return $WorkflowContext.Target + } + + $resolvedOutputDirectories = @($Artifacts | ForEach-Object OutputDirectory | Sort-Object -Unique) + if ($resolvedOutputDirectories.Count -eq 0) { + return $WorkflowContext.Target + } + + return ($resolvedOutputDirectories -join ', ') +} + +function Get-NovaPackageWorkflowNextStepLine { + [CmdletBinding()] + param( + [switch]$WhatIfEnabled + ) + + if ($WhatIfEnabled) { + return @( + 'Next step:' + 'Run New-NovaModulePackage without -WhatIf when you are ready to create the package artifacts.' + ) + } + + return @( + 'Next step:' + 'Deploy-NovaPackage' + ) } diff --git a/src/private/quality/InvokeNovaTestWorkflow.ps1 b/src/private/quality/InvokeNovaTestWorkflow.ps1 index bc2ace3..9e31450 100644 --- a/src/private/quality/InvokeNovaTestWorkflow.ps1 +++ b/src/private/quality/InvokeNovaTestWorkflow.ps1 @@ -5,34 +5,225 @@ function Invoke-NovaTestWorkflow { [switch]$ShouldRun ) - if (Test-NovaTestWorkflowBuildRequested -WorkflowContext $WorkflowContext) { - $workflowParams = Get-NovaBuildCommandParameterMap -WorkflowParams $WorkflowContext.WorkflowParams -OverrideWarningRequested:(($WorkflowContext.PSObject.Properties.Name -contains 'OverrideWarningRequested') -and $WorkflowContext.OverrideWarningRequested) - Invoke-NovaBuild @workflowParams + $progressActivity = 'Running Nova test workflow' + $whatIfEnabled = Test-NovaWhatIfWorkflowContext -WorkflowContext $WorkflowContext + $testResult = $null + $shouldRunWorkflow = Test-NovaTestWorkflowShouldRun -WorkflowContext $WorkflowContext -BoundParameters $PSBoundParameters -ShouldRun:$ShouldRun + + try { + Invoke-NovaTestWorkflowBuildStep -WorkflowContext $WorkflowContext -Activity $progressActivity -WhatIfEnabled:$whatIfEnabled + + if (-not $shouldRunWorkflow) { + Write-NovaTestWorkflowPreviewResult -WorkflowContext $WorkflowContext -WhatIfEnabled:$whatIfEnabled + return + } + + $testResult = Invoke-NovaTestWorkflowExecution -WorkflowContext $WorkflowContext -Activity $progressActivity + } finally { + Write-Progress -Activity $progressActivity -Completed + } + + Write-NovaTestWorkflowResult -WorkflowContext $WorkflowContext -TestResult $testResult +} + +function Invoke-NovaTestWorkflowBuildStep { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext, + [Parameter(Mandatory)][string]$Activity, + [switch]$WhatIfEnabled + ) + + if (-not (Test-NovaTestWorkflowBuildRequested -WorkflowContext $WorkflowContext)) { + return } - if (-not (Test-NovaTestWorkflowShouldRun -WorkflowContext $WorkflowContext -BoundParameters $PSBoundParameters -ShouldRun:$ShouldRun)) { + $buildCommandParameters = Get-NovaBuildCommandParameterMap -WorkflowParams (Get-NovaPropertyValue -InputObject $WorkflowContext -Name 'WorkflowParams') -OverrideWarningRequested:(($WorkflowContext.PSObject.Properties.Name -contains 'OverrideWarningRequested') -and $WorkflowContext.OverrideWarningRequested) + Invoke-NovaTestWorkflowStep -Activity $Activity -Status (Get-NovaTestWorkflowBuildStatus -WhatIfEnabled:$WhatIfEnabled) -PercentComplete 20 -Action { + Invoke-NovaBuild @buildCommandParameters + } +} + +function Write-NovaTestWorkflowPreviewResult { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext, + [switch]$WhatIfEnabled + ) + + if (-not $WhatIfEnabled) { return } - Initialize-NovaPesterArtifactDirectory -WorkflowContext $WorkflowContext + Write-NovaTestWorkflowResult -WorkflowContext $WorkflowContext -WhatIfEnabled +} + +function Invoke-NovaTestWorkflowExecution { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext, + [Parameter(Mandatory)][string]$Activity + ) + + Invoke-NovaTestWorkflowStep -Activity $Activity -Status 'Preparing the test result directory' -PercentComplete 40 -Action { + Initialize-NovaPesterArtifactDirectory -WorkflowContext $WorkflowContext + } + $WorkflowContext.PesterConfig.TestResult.OutputPath = $WorkflowContext.TestResultPath $coverageTargetAssertion = Get-NovaCoverageTargetAssertionScriptBlock -WorkflowContext $WorkflowContext + $testResult = Invoke-NovaTestWorkflowStep -Activity $Activity -Status 'Running Pester tests' -PercentComplete 70 -Action { + Invoke-NovaPesterWithSuppressedProgress -Configuration $WorkflowContext.PesterConfig + } + + Invoke-NovaTestWorkflowStep -Activity $Activity -Status 'Writing the test result report' -PercentComplete 85 -Action { + & $WorkflowContext.TestResultArtifactWriter.ScriptBlock -TestResult $testResult -OutputPath $WorkflowContext.TestResultPath -ReportWriter $WorkflowContext.TestResultReportWriter.ScriptBlock + } + + if ($testResult.Result -ne 'Passed') { + Stop-NovaOperation -Message (Get-NovaTestWorkflowFailureMessage -WorkflowContext $WorkflowContext) -ErrorId 'Nova.Workflow.TestRunFailed' -Category InvalidOperation -TargetObject $WorkflowContext.TestResultPath + } + + Invoke-NovaTestWorkflowStep -Activity $Activity -Status 'Checking the configured code coverage target' -PercentComplete 95 -Action { + & $coverageTargetAssertion -WorkflowContext $WorkflowContext -TestResult $testResult + } + + return $testResult +} + +function Invoke-NovaTestWorkflowStep { + [CmdletBinding()] + param( + [Parameter(Mandatory)][string]$Activity, + [Parameter(Mandatory)][string]$Status, + [Parameter(Mandatory)][int]$PercentComplete, + [Parameter(Mandatory)][scriptblock]$Action + ) + + Write-Progress -Activity $Activity -Status $Status -PercentComplete $PercentComplete + & $Action +} + +function Get-NovaTestWorkflowBuildStatus { + [CmdletBinding()] + param( + [switch]$WhatIfEnabled + ) + + if ($WhatIfEnabled) { + return 'Previewing the build-before-test workflow' + } + + return 'Building the current project state' +} + +function Invoke-NovaPesterWithSuppressedProgress { + [CmdletBinding()] + param( + [Parameter(Mandatory)][object]$Configuration + ) $previousProgressPreference = $global:ProgressPreference $global:ProgressPreference = 'SilentlyContinue' try { - $testResult = Invoke-NovaPester -Configuration $WorkflowContext.PesterConfig + return Invoke-NovaPester -Configuration $Configuration } finally { $global:ProgressPreference = $previousProgressPreference } +} - & $WorkflowContext.TestResultArtifactWriter.ScriptBlock -TestResult $testResult -OutputPath $WorkflowContext.TestResultPath -ReportWriter $WorkflowContext.TestResultReportWriter.ScriptBlock +function Get-NovaTestWorkflowFailureMessage { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext + ) - if ($testResult.Result -ne 'Passed') { - Stop-NovaOperation -Message 'Tests failed' -ErrorId 'Nova.Workflow.TestRunFailed' -Category InvalidOperation -TargetObject $WorkflowContext.TestResultPath + return "Pester reported one or more failing tests. Review the output above and the test result file at $( $WorkflowContext.TestResultPath ), then rerun Test-NovaBuild." +} + +function Write-NovaTestWorkflowResult { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext, + [AllowNull()][object]$TestResult, + [switch]$WhatIfEnabled + ) + + Write-Message (Get-NovaTestWorkflowStatusMessage -WorkflowContext $WorkflowContext -WhatIfEnabled:$WhatIfEnabled) -color Green + Write-Message "Results file: $( $WorkflowContext.TestResultPath )" + + $coverageMessage = Get-NovaTestWorkflowCoverageMessage -WorkflowContext $WorkflowContext -TestResult $TestResult -WhatIfEnabled:$WhatIfEnabled + if (-not [string]::IsNullOrWhiteSpace($coverageMessage)) { + Write-Message $coverageMessage + } + + foreach ($line in (Get-NovaTestWorkflowNextStepLine -WhatIfEnabled:$WhatIfEnabled)) { + Write-Message $line + } +} + +function Get-NovaTestWorkflowStatusMessage { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext, + [switch]$WhatIfEnabled + ) + + if ($WhatIfEnabled) { + return "Test plan ready for $( $WorkflowContext.ProjectInfo.ProjectName )" + } + + return "Pester tests passed for $( $WorkflowContext.ProjectInfo.ProjectName )" +} + +function Get-NovaTestWorkflowCoverageMessage { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext, + [AllowNull()][object]$TestResult, + [switch]$WhatIfEnabled + ) + + $coveragePercentTarget = Get-NovaConfiguredCoveragePercentTarget -WorkflowContext $WorkflowContext + if ($WhatIfEnabled) { + if ($null -eq $coveragePercentTarget) { + return $null + } + + return "Configured coverage target: $( Format-NovaCoveragePercentValue -Value ([double]$coveragePercentTarget) )%" } - & $coverageTargetAssertion -WorkflowContext $WorkflowContext -TestResult $testResult + $codeCoverage = Get-NovaPropertyValue -InputObject $TestResult -Name 'CodeCoverage' + $coveragePercent = Get-NovaPropertyValue -InputObject $codeCoverage -Name 'CoveragePercent' + if ($null -eq $coveragePercent -or [string]::IsNullOrWhiteSpace([string]$coveragePercent)) { + return $null + } + + $formattedCoverage = Format-NovaCoveragePercentValue -Value ([double]$coveragePercent) + if ($null -eq $coveragePercentTarget) { + return "Measured code coverage: $formattedCoverage%" + } + + $formattedTarget = Format-NovaCoveragePercentValue -Value ([double]$coveragePercentTarget) + return "Measured code coverage: $formattedCoverage% (target: $formattedTarget%)" +} + +function Get-NovaTestWorkflowNextStepLine { + [CmdletBinding()] + param( + [switch]$WhatIfEnabled + ) + + if ($WhatIfEnabled) { + return @( + 'Next step:' + 'Run Test-NovaBuild without -WhatIf when you are ready to execute the test workflow.' + ) + } + + return @( + 'Next step:' + 'Publish-NovaModule -Local' + ) } function Test-NovaTestWorkflowBuildRequested { @@ -129,7 +320,7 @@ function Get-NovaDefaultCoverageTargetAssertionScriptBlock { $codeCoverage = & $propertyReader -InputObject $TestResult -Name 'CodeCoverage' $coveragePercent = & $propertyReader -InputObject $codeCoverage -Name 'CoveragePercent' if ($null -eq $coveragePercent -or [string]::IsNullOrWhiteSpace([string]$coveragePercent)) { - $exception = [System.IO.InvalidDataException]::new("Code coverage target $formattedTarget% is configured, but the Pester result did not include a coverage percentage.") + $exception = [System.IO.InvalidDataException]::new("Code coverage target $formattedTarget% is configured, but the Pester result did not include a coverage percentage. Review the coverage settings in project.json and the test result file at $resolvedTargetObject.") $errorRecord = [System.Management.Automation.ErrorRecord]::new($exception, 'Nova.Workflow.CodeCoveragePercentMissing', [System.Management.Automation.ErrorCategory]::InvalidData, $resolvedTargetObject) throw $errorRecord } @@ -140,7 +331,7 @@ function Get-NovaDefaultCoverageTargetAssertionScriptBlock { } $formattedCoverage = & $percentFormatter -Value $coveragePercent - $exception = [System.InvalidOperationException]::new("Code coverage $formattedCoverage% did not meet the configured target $formattedTarget%.") + $exception = [System.InvalidOperationException]::new("Code coverage $formattedCoverage% did not meet the configured target $formattedTarget%. Review the failing tests or coverage settings, then rerun Test-NovaBuild.") $errorRecord = [System.Management.Automation.ErrorRecord]::new($exception, 'Nova.Workflow.CodeCoverageTargetNotMet', [System.Management.Automation.ErrorCategory]::InvalidOperation, $resolvedTargetObject) throw $errorRecord }.GetNewClosure() diff --git a/src/private/release/InvokeNovaPublishWorkflow.ps1 b/src/private/release/InvokeNovaPublishWorkflow.ps1 index c92343b..97f34dc 100644 --- a/src/private/release/InvokeNovaPublishWorkflow.ps1 +++ b/src/private/release/InvokeNovaPublishWorkflow.ps1 @@ -8,6 +8,15 @@ function Test-NovaPublishWorkflowShouldImportLocalModule { return $ShouldRun -and $null -ne $WorkflowContext.LocalPublishActivation } +function Test-NovaPublishWorkflowWhatIfEnabled { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext + ) + + return $WorkflowContext.WorkflowParams.ContainsKey('WhatIf') -and $WorkflowContext.WorkflowParams.WhatIf +} + function Invoke-NovaPublishWorkflowCiRestore { [CmdletBinding()] param( @@ -22,6 +31,19 @@ function Invoke-NovaPublishWorkflowCiRestore { } } +function Invoke-NovaPublishWorkflowStep { + [CmdletBinding()] + param( + [Parameter(Mandatory)][string]$Activity, + [Parameter(Mandatory)][string]$Status, + [Parameter(Mandatory)][int]$PercentComplete, + [Parameter(Mandatory)][scriptblock]$Action + ) + + Write-Progress -Activity $Activity -Status $Status -PercentComplete $PercentComplete + & $Action +} + function Invoke-NovaPublishWorkflow { [CmdletBinding()] param( @@ -32,20 +54,140 @@ function Invoke-NovaPublishWorkflow { $publishParams = $WorkflowContext.PublishParams $continuousIntegrationRequested = ($WorkflowContext.PSObject.Properties.Name -contains 'ContinuousIntegrationRequested') -and $WorkflowContext.ContinuousIntegrationRequested $shouldImportPublishedLocalModule = Test-NovaPublishWorkflowShouldImportLocalModule -WorkflowContext $WorkflowContext -ShouldRun:$ShouldRun - $ciRestoreAction = ${function:Invoke-NovaPublishWorkflowCiRestore} - $importBuiltModuleForCiAction = ${function:Import-NovaBuiltModuleForCi} + $whatIfEnabled = Test-NovaPublishWorkflowWhatIfEnabled -WorkflowContext $WorkflowContext + $progressActivity = 'Running Nova publish workflow' + $importedLocalModule = $false + $restoredBuiltModule = $false + + try { + Invoke-NovaPublishWorkflowStep -Activity $progressActivity -Status (Get-NovaPublishWorkflowValidationStatus -WorkflowContext $WorkflowContext) -PercentComplete 35 -Action { + Invoke-NovaBuildValidation -WorkflowContext $WorkflowContext + } + + Invoke-NovaPublishWorkflowStep -Activity $progressActivity -Status (Get-NovaPublishWorkflowPublishStatus -WorkflowContext $WorkflowContext -WhatIfEnabled:$whatIfEnabled) -PercentComplete 75 -Action { + & $WorkflowContext.PublishInvocation.Action @publishParams + } + + if ($shouldImportPublishedLocalModule) { + Invoke-NovaPublishWorkflowStep -Activity $progressActivity -Status 'Importing the published local module' -PercentComplete 90 -Action { + $null = & $WorkflowContext.LocalPublishActivation.ImportAction -ProjectName $WorkflowContext.PublishInvocation.Parameters.ProjectInfo.ProjectName -ManifestPath $WorkflowContext.LocalPublishActivation.ManifestPath + } + $importedLocalModule = $true + Write-Verbose "Module copy to local path complete and imported from $( $WorkflowContext.LocalPublishActivation.ManifestPath )" + } + + if ($ShouldRun -and $continuousIntegrationRequested) { + Invoke-NovaPublishWorkflowStep -Activity $progressActivity -Status 'Refreshing the current session with the built module' -PercentComplete 98 -Action { + $null = Import-NovaBuiltModuleForCi -ProjectInfo $WorkflowContext.ProjectInfo + } + $restoredBuiltModule = $true + } + } finally { + Write-Progress -Activity $progressActivity -Completed + } - Invoke-NovaBuildValidation -WorkflowContext $WorkflowContext + Write-NovaPublishWorkflowResult -WorkflowContext $WorkflowContext -WhatIfEnabled:$whatIfEnabled -ImportedLocalModule:$importedLocalModule -RestoredBuiltModule:$restoredBuiltModule +} - & $WorkflowContext.PublishInvocation.Action @publishParams +function Get-NovaPublishWorkflowValidationStatus { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext + ) - if (-not $shouldImportPublishedLocalModule) { - & $ciRestoreAction -WorkflowContext $WorkflowContext -ShouldRun:$ShouldRun -ContinuousIntegrationRequested:$continuousIntegrationRequested -ImportBuiltModuleForCiAction $importBuiltModuleForCiAction - return + if ($WorkflowContext.SkipTestsRequested) { + return 'Building publish output with tests skipped' } - $null = & $WorkflowContext.LocalPublishActivation.ImportAction -ProjectName $WorkflowContext.PublishInvocation.Parameters.ProjectInfo.ProjectName -ManifestPath $WorkflowContext.LocalPublishActivation.ManifestPath - Write-Verbose "Module copy to local path complete and imported from $( $WorkflowContext.LocalPublishActivation.ManifestPath )" + return 'Building and testing publish output' +} - & $ciRestoreAction -WorkflowContext $WorkflowContext -ShouldRun:$ShouldRun -ContinuousIntegrationRequested:$continuousIntegrationRequested -ImportBuiltModuleForCiAction $importBuiltModuleForCiAction +function Get-NovaPublishWorkflowPublishStatus { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext, + [switch]$WhatIfEnabled + ) + + $targetDescription = if ($WorkflowContext.PublishInvocation.IsLocal) { + 'the local module path' + } else { + "repository $( $WorkflowContext.PublishInvocation.Target )" + } + + if ($WhatIfEnabled) { + return "Previewing publish to $targetDescription" + } + + return "Publishing to $targetDescription" +} + +function Write-NovaPublishWorkflowResult { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext, + [switch]$WhatIfEnabled, + [switch]$ImportedLocalModule, + [switch]$RestoredBuiltModule + ) + + Write-Message (Get-NovaPublishWorkflowStatusMessage -WorkflowContext $WorkflowContext -WhatIfEnabled:$WhatIfEnabled) -color Green + Write-Message "Publish target: $( $WorkflowContext.PublishInvocation.Target )" + + if ($WorkflowContext.SkipTestsRequested) { + Write-Message 'Pre-publish tests were skipped for this run.' + } + + if ($ImportedLocalModule) { + Write-Message "The published local module is loaded from $( $WorkflowContext.LocalPublishActivation.ManifestPath )." + } + + if ($RestoredBuiltModule) { + Write-Message 'The freshly built dist module is loaded again for later commands in this session.' + } + + foreach ($line in (Get-NovaPublishWorkflowNextStepLine -WorkflowContext $WorkflowContext -WhatIfEnabled:$WhatIfEnabled)) { + Write-Message $line + } +} + +function Get-NovaPublishWorkflowStatusMessage { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext, + [switch]$WhatIfEnabled + ) + + if ($WhatIfEnabled) { + return "Publish plan ready for $( $WorkflowContext.ProjectInfo.ProjectName )" + } + + return "Published Nova module: $( $WorkflowContext.ProjectInfo.ProjectName )" +} + +function Get-NovaPublishWorkflowNextStepLine { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext, + [switch]$WhatIfEnabled + ) + + if ($WhatIfEnabled) { + return @( + 'Next step:' + 'Run Publish-NovaModule without -WhatIf when you are ready to publish the module.' + ) + } + + if ($WorkflowContext.PublishInvocation.IsLocal) { + return @( + 'Next step:' + 'Get-NovaProjectInfo -Installed' + ) + } + + return @( + 'Next step:' + "Find-Module $( $WorkflowContext.ProjectInfo.ProjectName ) -Repository $( $WorkflowContext.PublishInvocation.Target )" + ) } diff --git a/src/private/release/InvokeNovaReleaseWorkflow.ps1 b/src/private/release/InvokeNovaReleaseWorkflow.ps1 index dc54fa8..9661b7b 100644 --- a/src/private/release/InvokeNovaReleaseWorkflow.ps1 +++ b/src/private/release/InvokeNovaReleaseWorkflow.ps1 @@ -52,20 +52,195 @@ function Invoke-NovaReleaseWorkflow { $testWorkflowParams = Get-NovaReleaseBuildWorkflowParameterMap -WorkflowParams $workflowParams -OverrideWarningRequested:$overrideWarningRequested $shouldRestoreBuiltModule = Test-NovaReleaseWorkflowShouldRestoreBuiltModule -WorkflowParams $workflowParams -ContinuousIntegrationRequested:$continuousIntegrationRequested $publishParams = $WorkflowContext.PublishParams + $whatIfEnabled = Test-NovaReleaseWorkflowWhatIfEnabled -WorkflowParams $workflowParams + $progressActivity = 'Running Nova release workflow' + $versionResult = $null - Invoke-NovaBuild @buildWorkflowParams - if (-not $skipTestsRequested) { - Test-NovaBuild @testWorkflowParams - } + try { + Invoke-NovaReleaseWorkflowStep -Activity $progressActivity -Status 'Building the current project state' -PercentComplete 15 -Action { + Invoke-NovaBuild @buildWorkflowParams + } + + if (-not $skipTestsRequested) { + Invoke-NovaReleaseWorkflowStep -Activity $progressActivity -Status 'Running pre-release tests' -PercentComplete 35 -Action { + Test-NovaBuild @testWorkflowParams + } + } + + $versionResult = Invoke-NovaReleaseWorkflowStep -Activity $progressActivity -Status (Get-NovaReleaseVersionStepStatus -WhatIfEnabled:$whatIfEnabled) -PercentComplete 55 -Action { + Update-NovaModuleVersion @ciWorkflowParams + } - $versionResult = Update-NovaModuleVersion @ciWorkflowParams - Invoke-NovaBuild @buildWorkflowParams + Invoke-NovaReleaseWorkflowStep -Activity $progressActivity -Status 'Rebuilding release output' -PercentComplete 75 -Action { + Invoke-NovaBuild @buildWorkflowParams + } - & $WorkflowContext.PublishInvocation.Action @publishParams + Invoke-NovaReleaseWorkflowStep -Activity $progressActivity -Status (Get-NovaReleasePublishStepStatus -WorkflowContext $WorkflowContext -WhatIfEnabled:$whatIfEnabled) -PercentComplete 90 -Action { + & $WorkflowContext.PublishInvocation.Action @publishParams + } - if ($shouldRestoreBuiltModule) { - $null = Import-NovaBuiltModuleForCi -ProjectInfo $WorkflowContext.ProjectInfo + if ($shouldRestoreBuiltModule) { + Invoke-NovaReleaseWorkflowStep -Activity $progressActivity -Status 'Refreshing the current session with the built module' -PercentComplete 98 -Action { + $null = Import-NovaBuiltModuleForCi -ProjectInfo $WorkflowContext.ProjectInfo + } + } + } finally { + Write-Progress -Activity $progressActivity -Completed } + Write-NovaReleaseWorkflowResult -WorkflowContext $WorkflowContext -VersionResult $versionResult -WhatIfEnabled:$whatIfEnabled -ShouldRestoreBuiltModule:$shouldRestoreBuiltModule return $versionResult } + +function Invoke-NovaReleaseWorkflowStep { + [CmdletBinding()] + param( + [Parameter(Mandatory)][string]$Activity, + [Parameter(Mandatory)][string]$Status, + [Parameter(Mandatory)][int]$PercentComplete, + [Parameter(Mandatory)][scriptblock]$Action + ) + + Write-Progress -Activity $Activity -Status $Status -PercentComplete $PercentComplete + & $Action +} + +function Test-NovaReleaseWorkflowWhatIfEnabled { + [CmdletBinding()] + param( + [hashtable]$WorkflowParams = @{} + ) + + return $WorkflowParams.ContainsKey('WhatIf') -and $WorkflowParams.WhatIf +} + +function Get-NovaReleaseVersionStepStatus { + [CmdletBinding()] + param( + [switch]$WhatIfEnabled + ) + + if ($WhatIfEnabled) { + return 'Planning the next release version' + } + + return 'Updating the project version' +} + +function Get-NovaReleasePublishStepStatus { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext, + [switch]$WhatIfEnabled + ) + + $targetDescription = if ($WorkflowContext.PublishInvocation.IsLocal) { + 'the local module path' + } else { + "repository $( $WorkflowContext.PublishInvocation.Target )" + } + + if ($WhatIfEnabled) { + return "Previewing publish to $targetDescription" + } + + return "Publishing release to $targetDescription" +} + +function Write-NovaReleaseWorkflowResult { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext, + [AllowNull()][object]$VersionResult, + [switch]$WhatIfEnabled, + [switch]$ShouldRestoreBuiltModule + ) + + $resolvedVersion = Get-NovaReleaseWorkflowResultVersion -VersionResult $VersionResult + $statusMessage = Get-NovaReleaseWorkflowStatusMessage -ProjectInfo $WorkflowContext.ProjectInfo -Version $resolvedVersion -WhatIfEnabled:$WhatIfEnabled + Write-Message $statusMessage -color Green + Write-Message "Publish target: $( $WorkflowContext.PublishInvocation.Target )" + + if ($WorkflowContext.SkipTestsRequested) { + Write-Message 'Pre-release tests were skipped for this run.' + } + + if ($ShouldRestoreBuiltModule) { + Write-Message 'The freshly built dist module is loaded again for later commands in this session.' + } + + foreach ($line in (Get-NovaReleaseWorkflowNextStepLine -WorkflowContext $WorkflowContext -WhatIfEnabled:$WhatIfEnabled)) { + Write-Message $line + } +} + +function Get-NovaReleaseWorkflowResultVersion { + [CmdletBinding()] + param( + [AllowNull()][object]$VersionResult + ) + + if ($null -eq $VersionResult) { + return $null + } + + if ($VersionResult.PSObject.Properties.Name -contains 'NewVersion') { + return $VersionResult.NewVersion + } + + if ($VersionResult.PSObject.Properties.Name -contains 'Version') { + return $VersionResult.Version + } + + return $null +} + +function Get-NovaReleaseWorkflowStatusMessage { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$ProjectInfo, + [AllowNull()][string]$Version, + [switch]$WhatIfEnabled + ) + + if ($WhatIfEnabled) { + if ([string]::IsNullOrWhiteSpace($Version)) { + return "Release plan ready for $( $ProjectInfo.ProjectName )" + } + + return "Release plan ready for $( $ProjectInfo.ProjectName ) -> $Version" + } + + if ([string]::IsNullOrWhiteSpace($Version)) { + return "Released Nova module: $( $ProjectInfo.ProjectName )" + } + + return "Released Nova module: $( $ProjectInfo.ProjectName ) $Version" +} + +function Get-NovaReleaseWorkflowNextStepLine { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext, + [switch]$WhatIfEnabled + ) + + if ($WhatIfEnabled) { + return @( + 'Next step:' + 'Run Invoke-NovaRelease without -WhatIf when you are ready to apply the release.' + ) + } + + if ($WorkflowContext.PublishInvocation.IsLocal) { + return @( + 'Next step:' + 'Get-NovaProjectInfo -Installed' + ) + } + + return @( + 'Next step:' + 'Get-NovaProjectInfo -Version' + ) +} diff --git a/src/private/release/InvokeNovaVersionUpdateWorkflow.ps1 b/src/private/release/InvokeNovaVersionUpdateWorkflow.ps1 index 4b6dfdf..cb67f71 100644 --- a/src/private/release/InvokeNovaVersionUpdateWorkflow.ps1 +++ b/src/private/release/InvokeNovaVersionUpdateWorkflow.ps1 @@ -7,25 +7,46 @@ function Invoke-NovaVersionUpdateWorkflow { ) $versionWriteResult = $null - if ($ShouldRun) { - $versionWriteResult = Set-NovaModuleVersion -ProjectInfo $WorkflowContext.ProjectInfo -Label (Get-NovaVersionUpdateEffectiveLabel -WorkflowContext $WorkflowContext) -PreviewRelease:$WorkflowContext.PreviewRelease -Confirm:$false + $progressActivity = 'Updating Nova module version' + try { + if ($ShouldRun) { + $versionWriteResult = Invoke-NovaVersionUpdateWorkflowStep -Activity $progressActivity -Status (Get-NovaVersionUpdateApplyStatus -WorkflowContext $WorkflowContext) -PercentComplete 80 -Action { + Set-NovaModuleVersion -ProjectInfo $WorkflowContext.ProjectInfo -Label (Get-NovaVersionUpdateEffectiveLabel -WorkflowContext $WorkflowContext) -PreviewRelease:$WorkflowContext.PreviewRelease -Confirm:$false + } + } } - - if (-not (Test-NovaVersionUpdateResultRequired -ShouldRun:$ShouldRun -WhatIfEnabled:$WhatIfEnabled)) { - return + finally { + if ($ShouldRun) { + Write-Progress -Activity $progressActivity -Completed + } } - return Get-NovaVersionUpdateResult -WorkflowContext $WorkflowContext -Applied:($null -ne $versionWriteResult -and $versionWriteResult.Applied) + $wasApplied = $null -ne $versionWriteResult -and $versionWriteResult.Applied + $wasCancelled = (-not $ShouldRun) -and (-not $WhatIfEnabled) + return Get-NovaVersionUpdateResult -WorkflowContext $WorkflowContext -Applied:$wasApplied -Previewed:$WhatIfEnabled -Cancelled:$wasCancelled } -function Test-NovaVersionUpdateResultRequired { +function Invoke-NovaVersionUpdateWorkflowStep { [CmdletBinding()] param( - [switch]$ShouldRun, - [switch]$WhatIfEnabled + [Parameter(Mandatory)][string]$Activity, + [Parameter(Mandatory)][string]$Status, + [Parameter(Mandatory)][int]$PercentComplete, + [Parameter(Mandatory)][scriptblock]$Action + ) + + Write-Progress -Activity $Activity -Status $Status -PercentComplete $PercentComplete + return & $Action +} + +function Get-NovaVersionUpdateApplyStatus { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext ) - return $ShouldRun -or $WhatIfEnabled + $target = Get-NovaVersionUpdateTarget -WorkflowContext $WorkflowContext + return "Writing version $( $WorkflowContext.NewVersion ) to $target" } function Get-NovaVersionUpdateEffectiveLabel { @@ -34,28 +55,35 @@ function Get-NovaVersionUpdateEffectiveLabel { [Parameter(Mandatory)][pscustomobject]$WorkflowContext ) - if ($WorkflowContext.PSObject.Properties.Name -contains 'EffectiveLabel' -and -not [string]::IsNullOrWhiteSpace($WorkflowContext.EffectiveLabel)) { - return $WorkflowContext.EffectiveLabel + $effectiveLabel = Get-NovaVersionUpdateWorkflowPropertyValue -WorkflowContext $WorkflowContext -Name 'EffectiveLabel' + if (-not [string]::IsNullOrWhiteSpace($effectiveLabel)) { + return $effectiveLabel } - return $WorkflowContext.Label + return Get-NovaVersionUpdateWorkflowPropertyValue -WorkflowContext $WorkflowContext -Name 'Label' } function Get-NovaVersionUpdateResult { [CmdletBinding()] param( [Parameter(Mandatory)][pscustomobject]$WorkflowContext, - [switch]$Applied + [switch]$Applied, + [switch]$Previewed, + [switch]$Cancelled ) return [pscustomobject]@{ - PreviousVersion = $WorkflowContext.PreviousVersion - NewVersion = $WorkflowContext.NewVersion - Label = $WorkflowContext.Label + PreviousVersion = Get-NovaVersionUpdateWorkflowPropertyValue -WorkflowContext $WorkflowContext -Name 'PreviousVersion' + NewVersion = Get-NovaVersionUpdateWorkflowPropertyValue -WorkflowContext $WorkflowContext -Name 'NewVersion' + Label = Get-NovaVersionUpdateWorkflowPropertyValue -WorkflowContext $WorkflowContext -Name 'Label' EffectiveLabel = Get-NovaVersionUpdateEffectiveLabel -WorkflowContext $WorkflowContext AdvisoryMessage = Get-NovaVersionUpdateAdvisoryMessage -WorkflowContext $WorkflowContext - CommitCount = $WorkflowContext.CommitCount + CommitCount = Get-NovaVersionUpdateWorkflowPropertyValue -WorkflowContext $WorkflowContext -Name 'CommitCount' + ProjectFile = Get-NovaVersionUpdateProjectFile -WorkflowContext $WorkflowContext + Target = Get-NovaVersionUpdateTarget -WorkflowContext $WorkflowContext Applied = [bool]$Applied + Previewed = [bool]$Previewed + Cancelled = [bool]$Cancelled } } @@ -65,10 +93,53 @@ function Get-NovaVersionUpdateAdvisoryMessage { [Parameter(Mandatory)][pscustomobject]$WorkflowContext ) - if ($WorkflowContext.PSObject.Properties.Name -notcontains 'AdvisoryMessage') { + return Get-NovaVersionUpdateWorkflowPropertyValue -WorkflowContext $WorkflowContext -Name 'AdvisoryMessage' +} + +function Get-NovaVersionUpdateProjectFile { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext + ) + + $projectInfo = Get-NovaVersionUpdateWorkflowPropertyValue -WorkflowContext $WorkflowContext -Name 'ProjectInfo' + if ($null -eq $projectInfo) { + return $null + } + + $projectFileProperty = $projectInfo.PSObject.Properties['ProjectJSON'] + if ($null -eq $projectFileProperty) { return $null } - return $WorkflowContext.AdvisoryMessage + return $projectFileProperty.Value +} + +function Get-NovaVersionUpdateTarget { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext + ) + + $projectFile = Get-NovaVersionUpdateProjectFile -WorkflowContext $WorkflowContext + if (-not [string]::IsNullOrWhiteSpace($projectFile)) { + return [System.IO.Path]::GetFileName($projectFile) + } + + return Get-NovaVersionUpdateWorkflowPropertyValue -WorkflowContext $WorkflowContext -Name 'Target' } +function Get-NovaVersionUpdateWorkflowPropertyValue { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext, + [Parameter(Mandatory)][string]$Name + ) + + $property = $WorkflowContext.PSObject.Properties[$Name] + if ($null -eq $property) { + return $null + } + + return $property.Value +} diff --git a/src/private/release/WriteNovaVersionUpdateResultMessages.ps1 b/src/private/release/WriteNovaVersionUpdateResultMessages.ps1 index 589bb10..958c74c 100644 --- a/src/private/release/WriteNovaVersionUpdateResultMessages.ps1 +++ b/src/private/release/WriteNovaVersionUpdateResultMessages.ps1 @@ -33,8 +33,14 @@ function Write-NovaVersionUpdateResultOutput { Write-Warning $advisoryMessage } - if ($Result.Applied) { - Write-Host "Version bumped to : $( $Result.NewVersion )" + Write-Message (Get-NovaVersionUpdateResultStatusMessage -Result $Result) -color (Get-NovaVersionUpdateResultStatusColor -Result $Result) + + foreach ($line in (Get-NovaVersionUpdateResultDetailText -Result $Result)) { + Write-Message $line + } + + foreach ($line in (Get-NovaVersionUpdateResultNextStepText -Result $Result)) { + Write-Message $line } } @@ -44,11 +50,137 @@ function Get-NovaVersionUpdateResultAdvisoryMessage { [Parameter(Mandatory)][object]$Result ) - if ($Result.PSObject.Properties.Name -notcontains 'AdvisoryMessage') { - return $null + return Get-NovaVersionUpdateResultPropertyValue -Result $Result -Name 'AdvisoryMessage' +} + +function Get-NovaVersionUpdateResultStatusMessage { + [CmdletBinding()] + param( + [Parameter(Mandatory)][object]$Result + ) + + if ([bool](Get-NovaVersionUpdateResultPropertyValue -Result $Result -Name 'Cancelled')) { + return 'Version update cancelled before changing project.json.' + } + + if ([bool](Get-NovaVersionUpdateResultPropertyValue -Result $Result -Name 'Previewed')) { + return "Version update plan ready -> $( Get-NovaVersionUpdateResultPropertyValue -Result $Result -Name 'NewVersion' )" + } + + return "Updated project version to $( Get-NovaVersionUpdateResultPropertyValue -Result $Result -Name 'NewVersion' )" +} + +function Get-NovaVersionUpdateResultStatusColor { + [CmdletBinding()] + param( + [Parameter(Mandatory)][object]$Result + ) + + if ([bool](Get-NovaVersionUpdateResultPropertyValue -Result $Result -Name 'Cancelled')) { + return 'Blue' + } + + return 'Green' +} + +function Get-NovaVersionUpdateResultDetailText { + [CmdletBinding()] + param( + [Parameter(Mandatory)][object]$Result + ) + + $lines = @() + $targetLine = Get-NovaVersionUpdateResultTargetLine -Result $Result + if (-not [string]::IsNullOrWhiteSpace($targetLine)) { + $lines += $targetLine + } + + $lines += "Previous version: $( Get-NovaVersionUpdateResultPropertyValue -Result $Result -Name 'PreviousVersion' )" + $lines += "New version: $( Get-NovaVersionUpdateResultPropertyValue -Result $Result -Name 'NewVersion' )" + $lines += Get-NovaVersionUpdateResultLabelText -Result $Result + + $commitCount = Get-NovaVersionUpdateResultPropertyValue -Result $Result -Name 'CommitCount' + if ($null -ne $commitCount) { + $lines += "Commits considered: $commitCount" + } + + return $lines +} + +function Get-NovaVersionUpdateResultTargetLine { + [CmdletBinding()] + param( + [Parameter(Mandatory)][object]$Result + ) + + $projectFile = Get-NovaVersionUpdateResultPropertyValue -Result $Result -Name 'ProjectFile' + if (-not [string]::IsNullOrWhiteSpace($projectFile)) { + return "Version file: $projectFile" + } + + $target = Get-NovaVersionUpdateResultPropertyValue -Result $Result -Name 'Target' + if (-not [string]::IsNullOrWhiteSpace($target)) { + return "Version file: $target" } - return $Result.AdvisoryMessage + return $null } +function Get-NovaVersionUpdateResultLabelText { + [CmdletBinding()] + param( + [Parameter(Mandatory)][object]$Result + ) + $effectiveLabel = [string](Get-NovaVersionUpdateResultPropertyValue -Result $Result -Name 'EffectiveLabel') + $detectedLabel = [string](Get-NovaVersionUpdateResultPropertyValue -Result $Result -Name 'Label') + if ($effectiveLabel -eq $detectedLabel) { + return @("Release label: $effectiveLabel") + } + + return @( + "Detected release label: $detectedLabel" + "Applied release label: $effectiveLabel" + ) +} + +function Get-NovaVersionUpdateResultNextStepText { + [CmdletBinding()] + param( + [Parameter(Mandatory)][object]$Result + ) + + if ([bool](Get-NovaVersionUpdateResultPropertyValue -Result $Result -Name 'Cancelled')) { + return @( + 'Next step:' + 'Run Update-NovaModuleVersion again when you are ready to write the new version to project.json.' + ) + } + + if ([bool](Get-NovaVersionUpdateResultPropertyValue -Result $Result -Name 'Previewed')) { + return @( + 'Next step:' + 'Run Update-NovaModuleVersion without -WhatIf when you are ready to apply the version change.' + ) + } + + return @( + 'Next step:' + 'Invoke-NovaBuild' + ) +} + +function Get-NovaVersionUpdateResultPropertyValue { + [CmdletBinding()] + param( + [Parameter(Mandatory)][object]$Result, + [Parameter(Mandatory)][string]$Name + ) + + $property = $Result.PSObject.Properties[$Name] + if ($null -eq $property) { + return $null + } + + return $property.Value +} diff --git a/src/private/scaffold/InvokeNovaAgenticCopilotScaffoldWorkflow.ps1 b/src/private/scaffold/InvokeNovaAgenticCopilotScaffoldWorkflow.ps1 index 5f73699..e479578 100644 --- a/src/private/scaffold/InvokeNovaAgenticCopilotScaffoldWorkflow.ps1 +++ b/src/private/scaffold/InvokeNovaAgenticCopilotScaffoldWorkflow.ps1 @@ -49,7 +49,7 @@ function Confirm-NovaAgenticCopilotScaffoldWarning { return } - Stop-NovaOperation -Message 'Operation cancelled.' -ErrorId 'Nova.Workflow.AgenticCopilotScaffoldCancelled' -Category OperationStopped -TargetObject $WorkflowContext.ProjectRoot + Stop-NovaOperation -Message "Agentic Copilot scaffold apply cancelled for $( $WorkflowContext.ProjectRoot ). Re-run the command and choose Yes when you are ready to overwrite the managed scaffold paths." -ErrorId 'Nova.Workflow.AgenticCopilotScaffoldCancelled' -Category OperationStopped -TargetObject $WorkflowContext.ProjectRoot } function Invoke-NovaAgenticCopilotScaffoldWorkflow { @@ -63,7 +63,54 @@ function Invoke-NovaAgenticCopilotScaffoldWorkflow { return } - Confirm-NovaAgenticCopilotScaffoldWarning -WorkflowContext $WorkflowContext - Initialize-NovaModuleAgenticCopilotScaffold -Answer $WorkflowContext.AnswerSet -ProjectRoot $WorkflowContext.ProjectRoot -ScaffoldPolicy $WorkflowContext.ScaffoldPolicy + $progressActivity = 'Applying Agentic Copilot scaffold' + + try { + Invoke-NovaAgenticCopilotScaffoldStep -Activity $progressActivity -Status 'Confirming overwrite warning' -PercentComplete 20 -Action { + Confirm-NovaAgenticCopilotScaffoldWarning -WorkflowContext $WorkflowContext + } + + Invoke-NovaAgenticCopilotScaffoldStep -Activity $progressActivity -Status 'Refreshing managed scaffold files' -PercentComplete 75 -Action { + Initialize-NovaModuleAgenticCopilotScaffold -Answer $WorkflowContext.AnswerSet -ProjectRoot $WorkflowContext.ProjectRoot -ScaffoldPolicy $WorkflowContext.ScaffoldPolicy + } + } finally { + Write-Progress -Activity $progressActivity -Completed + } + + Write-NovaAgenticCopilotScaffoldResult -WorkflowContext $WorkflowContext +} + +function Invoke-NovaAgenticCopilotScaffoldStep { + [CmdletBinding()] + param( + [Parameter(Mandatory)][string]$Activity, + [Parameter(Mandatory)][string]$Status, + [Parameter(Mandatory)][int]$PercentComplete, + [Parameter(Mandatory)][scriptblock]$Action + ) + + Write-Progress -Activity $Activity -Status $Status -PercentComplete $PercentComplete + & $Action +} + +function Write-NovaAgenticCopilotScaffoldResult { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext + ) + Write-Message "Agentic Copilot scaffold applied to $( $WorkflowContext.ProjectInfo.ProjectName )" -color Green + Write-Message "Project root: $( $WorkflowContext.ProjectRoot )" + + foreach ($line in (Get-NovaAgenticCopilotScaffoldNextStepLine)) { + Write-Message $line + } +} + +function Get-NovaAgenticCopilotScaffoldNextStepLine { + return @( + 'Next steps:' + 'Review AGENTS.md and CONTRIBUTING.md' + 'Test-NovaBuild' + ) } diff --git a/src/private/update/GetNovaModuleSelfUpdateWorkflowContext.ps1 b/src/private/update/GetNovaModuleSelfUpdateWorkflowContext.ps1 index 5134152..d036284 100644 --- a/src/private/update/GetNovaModuleSelfUpdateWorkflowContext.ps1 +++ b/src/private/update/GetNovaModuleSelfUpdateWorkflowContext.ps1 @@ -4,7 +4,7 @@ function Get-NovaModuleSelfUpdateWorkflowContext { [pscustomobject]$Preference, [pscustomobject]$InstalledModule, [pscustomobject]$LookupResult, - [int]$TimeoutMilliseconds = 10000 + [hashtable]$WorkflowParams = @{} ) $resolvedPreference = if ($null -ne $Preference) { @@ -20,7 +20,7 @@ function Get-NovaModuleSelfUpdateWorkflowContext { $resolvedLookupResult = if ($null -ne $LookupResult) { $LookupResult } else { - Invoke-NovaModuleUpdateLookup -AllowPrereleaseNotifications:$resolvedPreference.PrereleaseNotificationsEnabled -TimeoutMilliseconds $TimeoutMilliseconds + Invoke-NovaModuleUpdateLookup -AllowPrereleaseNotifications:$resolvedPreference.PrereleaseNotificationsEnabled -TimeoutMilliseconds 10000 } if ($null -eq $resolvedLookupResult) { @@ -38,6 +38,7 @@ function Get-NovaModuleSelfUpdateWorkflowContext { Preference = $resolvedPreference InstalledModule = $resolvedInstalledModule LookupResult = $resolvedLookupResult + WorkflowParams = $WorkflowParams Plan = $plan Action = $action } diff --git a/src/private/update/GetNovaUpdateNotificationPreferenceChangeContext.ps1 b/src/private/update/GetNovaUpdateNotificationPreferenceChangeContext.ps1 index 6bbbf5b..a992616 100644 --- a/src/private/update/GetNovaUpdateNotificationPreferenceChangeContext.ps1 +++ b/src/private/update/GetNovaUpdateNotificationPreferenceChangeContext.ps1 @@ -2,14 +2,16 @@ function Get-NovaUpdateNotificationPreferenceChangeContext { [CmdletBinding()] param( [switch]$EnablePrereleaseNotifications, - [switch]$DisablePrereleaseNotifications + [switch]$DisablePrereleaseNotifications, + [hashtable]$WorkflowParams = @{} ) if ($EnablePrereleaseNotifications.IsPresent) { return [pscustomobject]@{ PrereleaseNotificationsEnabled = $true Target = Get-NovaUpdateSettingsFilePath - Action = 'Enable prerelease update notifications' + Action = 'Enable prerelease self-update notifications' + WorkflowParams = $WorkflowParams } } @@ -17,9 +19,10 @@ function Get-NovaUpdateNotificationPreferenceChangeContext { return [pscustomobject]@{ PrereleaseNotificationsEnabled = $false Target = Get-NovaUpdateSettingsFilePath - Action = 'Disable prerelease update notifications' + Action = 'Disable prerelease self-update notifications' + WorkflowParams = $WorkflowParams } } - Stop-NovaOperation -Message 'Specify either -EnablePrereleaseNotifications or -DisablePrereleaseNotifications.' -ErrorId 'Nova.Validation.UpdateNotificationPreferenceChangeRequired' -Category InvalidArgument -TargetObject 'PrereleaseNotifications' + Stop-NovaOperation -Message 'Specify either -EnablePrereleaseNotifications or -DisablePrereleaseNotifications. Example: Set-NovaUpdateNotificationPreference -DisablePrereleaseNotifications' -ErrorId 'Nova.Validation.UpdateNotificationPreferenceChangeRequired' -Category InvalidArgument -TargetObject 'PrereleaseNotifications' } diff --git a/src/private/update/InvokeNovaModuleSelfUpdateWorkflow.ps1 b/src/private/update/InvokeNovaModuleSelfUpdateWorkflow.ps1 index cd0d26a..40b5266 100644 --- a/src/private/update/InvokeNovaModuleSelfUpdateWorkflow.ps1 +++ b/src/private/update/InvokeNovaModuleSelfUpdateWorkflow.ps1 @@ -7,7 +7,8 @@ function Invoke-NovaModuleSelfUpdateOrStop { try { $null = Invoke-NovaModuleSelfUpdate -ModuleName $Plan.ModuleName -AllowPrerelease:$Plan.UsedAllowPrerelease } catch { - Stop-NovaOperation -Message $_.Exception.Message -ErrorId 'Nova.Dependency.ModuleSelfUpdateFailed' -Category InvalidOperation -TargetObject $Plan.ModuleName + $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." + Stop-NovaOperation -Message $message -ErrorId 'Nova.Dependency.ModuleSelfUpdateFailed' -Category InvalidOperation -TargetObject $Plan.ModuleName } } @@ -30,16 +31,190 @@ function Complete-NovaModuleSelfUpdateResult { function Invoke-NovaModuleSelfUpdateWorkflow { [CmdletBinding()] param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext, + [switch]$ShouldRun + ) + + $progressActivity = 'Updating NovaModuleTools' + $result = $null + + if (Test-NovaModuleSelfUpdateWorkflowShouldSkipExecution -WorkflowContext $WorkflowContext -ShouldRun:$ShouldRun) { + $result = Complete-NovaModuleSelfUpdateResult -Plan $WorkflowContext.Plan -ReleaseNotesUri $null + Write-NovaModuleSelfUpdateWorkflowResult -Result $result -WorkflowContext $WorkflowContext + return $result + } + + try { + Invoke-NovaModuleSelfUpdateWorkflowStep -Activity $progressActivity -Status (Get-NovaModuleSelfUpdateWorkflowUpdateStatus -WorkflowContext $WorkflowContext) -PercentComplete 80 -Action { + Invoke-NovaModuleSelfUpdateOrStop -Plan $WorkflowContext.Plan + } + + $WorkflowContext.Plan.Updated = $true + $releaseNotesUri = Invoke-NovaModuleSelfUpdateWorkflowStep -Activity $progressActivity -Status 'Reading release notes from the updated module' -PercentComplete 95 -Action { + Get-NovaModuleReleaseNotesUri + } + + $result = Complete-NovaModuleSelfUpdateResult -Plan $WorkflowContext.Plan -ReleaseNotesUri $releaseNotesUri + } finally { + Write-Progress -Activity $progressActivity -Completed + } + + Write-NovaModuleSelfUpdateWorkflowResult -Result $result -WorkflowContext $WorkflowContext + return $result +} + +function Test-NovaModuleSelfUpdateWorkflowShouldSkipExecution { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext, + [switch]$ShouldRun + ) + + return (-not $WorkflowContext.Plan.UpdateAvailable) -or (-not $ShouldRun) +} + +function Invoke-NovaModuleSelfUpdateWorkflowStep { + [CmdletBinding()] + param( + [Parameter(Mandatory)][string]$Activity, + [Parameter(Mandatory)][string]$Status, + [Parameter(Mandatory)][int]$PercentComplete, + [Parameter(Mandatory)][scriptblock]$Action + ) + + Write-Progress -Activity $Activity -Status $Status -PercentComplete $PercentComplete + & $Action +} + +function Write-NovaModuleSelfUpdateWorkflowResult { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$Result, [Parameter(Mandatory)][pscustomobject]$WorkflowContext ) - $plan = $WorkflowContext.Plan - if (-not $plan.UpdateAvailable) { - return Complete-NovaModuleSelfUpdateResult -Plan $plan -ReleaseNotesUri $null + $whatIfEnabled = Test-NovaModuleSelfUpdateWorkflowWhatIfEnabled -WorkflowContext $WorkflowContext + Write-Message (Get-NovaModuleSelfUpdateWorkflowStatusMessage -Result $Result -WhatIfEnabled:$whatIfEnabled) -color (Get-NovaModuleSelfUpdateWorkflowStatusColor -Result $Result) + Write-Message "Current version: $( $Result.CurrentVersion )" + + $targetVersionLine = Get-NovaModuleSelfUpdateWorkflowTargetVersionLine -Result $Result + if (-not [string]::IsNullOrWhiteSpace($targetVersionLine)) { + Write-Message $targetVersionLine + } + + $repositoryLine = Get-NovaModuleSelfUpdateWorkflowRepositoryLine -Result $Result + if (-not [string]::IsNullOrWhiteSpace($repositoryLine)) { + Write-Message $repositoryLine + } + + foreach ($line in (Get-NovaModuleSelfUpdateWorkflowNextStepLine -Result $Result -WhatIfEnabled:$whatIfEnabled)) { + Write-Message $line } +} - Invoke-NovaModuleSelfUpdateOrStop -Plan $plan - $plan.Updated = $true - $releaseNotesUri = Get-NovaModuleReleaseNotesUri - return Complete-NovaModuleSelfUpdateResult -Plan $plan -ReleaseNotesUri $releaseNotesUri +function Test-NovaModuleSelfUpdateWorkflowWhatIfEnabled { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext + ) + + return $WorkflowContext.WorkflowParams.ContainsKey('WhatIf') -and $WorkflowContext.WorkflowParams.WhatIf +} + +function Get-NovaModuleSelfUpdateWorkflowUpdateStatus { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext + ) + + if ($WorkflowContext.Plan.IsPrereleaseTarget) { + return "Installing prerelease version $( $WorkflowContext.Plan.TargetVersion )" + } + + return "Installing version $( $WorkflowContext.Plan.TargetVersion )" +} + +function Get-NovaModuleSelfUpdateWorkflowStatusMessage { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$Result, + [switch]$WhatIfEnabled + ) + + if (-not $Result.UpdateAvailable) { + return 'NovaModuleTools is already up to date.' + } + + if ($WhatIfEnabled) { + return 'Self-update plan ready for NovaModuleTools' + } + + if ($Result.Cancelled) { + return 'Self-update cancelled for NovaModuleTools.' + } + + return "Updated NovaModuleTools to version $( $Result.TargetVersion )." +} + +function Get-NovaModuleSelfUpdateWorkflowStatusColor { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$Result + ) + + if ($Result.Cancelled) { + return 'Blue' + } + + return 'Green' +} + +function Get-NovaModuleSelfUpdateWorkflowTargetVersionLine { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$Result + ) + + if (-not $Result.UpdateAvailable) { + return $null + } + + return "Target version: $( $Result.TargetVersion )" +} + +function Get-NovaModuleSelfUpdateWorkflowRepositoryLine { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$Result + ) + + if ([string]::IsNullOrWhiteSpace($Result.LookupRepository)) { + return $null + } + + return "Repository: $( $Result.LookupRepository )" +} + +function Get-NovaModuleSelfUpdateWorkflowNextStepLine { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$Result, + [switch]$WhatIfEnabled + ) + + if (-not $Result.UpdateAvailable -or $Result.Cancelled) { + return @() + } + + if ($WhatIfEnabled) { + return @( + 'Next step:' + "Run Update-NovaModuleTool without -WhatIf when you are ready to install version $( $Result.TargetVersion )." + ) + } + + return @( + 'Next step:' + 'Get-NovaProjectInfo -Installed' + ) } diff --git a/src/private/update/InvokeNovaUpdateNotificationPreferenceChange.ps1 b/src/private/update/InvokeNovaUpdateNotificationPreferenceChange.ps1 index 7199b65..afe410e 100644 --- a/src/private/update/InvokeNovaUpdateNotificationPreferenceChange.ps1 +++ b/src/private/update/InvokeNovaUpdateNotificationPreferenceChange.ps1 @@ -1,9 +1,135 @@ function Invoke-NovaUpdateNotificationPreferenceChange { [CmdletBinding()] param( - [Parameter(Mandatory)][pscustomobject]$WorkflowContext + [Parameter(Mandatory)][pscustomobject]$WorkflowContext, + [switch]$ShouldRun ) + $whatIfEnabled = Test-NovaUpdateNotificationPreferenceChangeWhatIfEnabled -WorkflowContext $WorkflowContext + if (-not $ShouldRun) { + if ($whatIfEnabled) { + Write-NovaUpdateNotificationPreferenceChangeResult -WorkflowContext $WorkflowContext -WhatIfEnabled + } + + return + } + Write-NovaUpdateNotificationPreference -PrereleaseNotificationsEnabled:$WorkflowContext.PrereleaseNotificationsEnabled - return Get-NovaUpdateNotificationPreferenceStatus + $status = Get-NovaUpdateNotificationPreferenceStatus + Write-NovaUpdateNotificationPreferenceChangeResult -WorkflowContext $WorkflowContext -Status $status + return $status +} + +function Test-NovaUpdateNotificationPreferenceChangeWhatIfEnabled { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext + ) + + return $WorkflowContext.WorkflowParams.ContainsKey('WhatIf') -and $WorkflowContext.WorkflowParams.WhatIf +} + +function Write-NovaUpdateNotificationPreferenceChangeResult { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext, + [AllowNull()][pscustomobject]$Status, + [switch]$WhatIfEnabled + ) + + Write-Message (Get-NovaUpdateNotificationPreferenceChangeStatusMessage -WorkflowContext $WorkflowContext -WhatIfEnabled:$WhatIfEnabled) -color Green + Write-Message "Settings file: $( Get-NovaUpdateNotificationPreferenceChangeSettingsPath -WorkflowContext $WorkflowContext -Status $Status )" + Write-Message (Get-NovaUpdateNotificationPreferenceChangeAvailabilityMessage -WhatIfEnabled:$WhatIfEnabled) + + foreach ($line in (Get-NovaUpdateNotificationPreferenceChangeNextStepLine -WorkflowContext $WorkflowContext -WhatIfEnabled:$WhatIfEnabled)) { + Write-Message $line + } +} + +function Get-NovaUpdateNotificationPreferenceChangeStatusMessage { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext, + [switch]$WhatIfEnabled + ) + + $stateText = Get-NovaUpdateNotificationPreferenceChangeStateText -PrereleaseNotificationsEnabled:$WorkflowContext.PrereleaseNotificationsEnabled + if ($WhatIfEnabled) { + return "Notification preference plan ready: prerelease self-updates $stateText" + } + + return "Prerelease self-updates are now $stateText." +} + +function Get-NovaUpdateNotificationPreferenceChangeSettingsPath { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext, + [AllowNull()][pscustomobject]$Status + ) + + if ($null -ne $Status -and ($Status.PSObject.Properties.Name -contains 'SettingsPath')) { + return $Status.SettingsPath + } + + return $WorkflowContext.Target +} + +function Get-NovaUpdateNotificationPreferenceChangeAvailabilityMessage { + [CmdletBinding()] + param( + [switch]$WhatIfEnabled + ) + + if ($WhatIfEnabled) { + return 'Stable self-updates would remain available.' + } + + return 'Stable self-updates remain available.' +} + +function Get-NovaUpdateNotificationPreferenceChangeNextStepLine { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext, + [switch]$WhatIfEnabled + ) + + if ($WhatIfEnabled) { + return @( + 'Next step:' + "Run $( Get-NovaUpdateNotificationPreferenceChangeCommandLine -WorkflowContext $WorkflowContext ) without -WhatIf when you are ready to store the preference." + ) + } + + return @( + 'Next step:' + 'Get-NovaUpdateNotificationPreference' + ) +} + +function Get-NovaUpdateNotificationPreferenceChangeCommandLine { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject]$WorkflowContext + ) + + if ($WorkflowContext.PrereleaseNotificationsEnabled) { + return 'Set-NovaUpdateNotificationPreference -EnablePrereleaseNotifications' + } + + return 'Set-NovaUpdateNotificationPreference -DisablePrereleaseNotifications' +} + +function Get-NovaUpdateNotificationPreferenceChangeStateText { + [CmdletBinding()] + param( + [Parameter(Mandatory)][bool]$PrereleaseNotificationsEnabled + ) + + if ($PrereleaseNotificationsEnabled) { + return 'enabled' + } + + return 'disabled' } diff --git a/src/public/InvokeNovaCli.ps1 b/src/public/InvokeNovaCli.ps1 index 486cf08..64e5f83 100644 --- a/src/public/InvokeNovaCli.ps1 +++ b/src/public/InvokeNovaCli.ps1 @@ -8,8 +8,14 @@ function Invoke-NovaCli { [string[]]$Arguments ) + $commandName = if ([string]::IsNullOrWhiteSpace($Command)) { + '--help' + } else { + $Command + } + $invocationRequest = [pscustomobject]@{ - Command = $Command + Command = $commandName BoundParameters = $PSBoundParameters Arguments = $Arguments } diff --git a/src/public/SetNovaUpdateNotificationPreference.ps1 b/src/public/SetNovaUpdateNotificationPreference.ps1 index 6a178a0..998b40a 100644 --- a/src/public/SetNovaUpdateNotificationPreference.ps1 +++ b/src/public/SetNovaUpdateNotificationPreference.ps1 @@ -8,11 +8,12 @@ function Set-NovaUpdateNotificationPreference { [switch]$DisablePrereleaseNotifications ) - $workflowContext = Get-NovaUpdateNotificationPreferenceChangeContext -EnablePrereleaseNotifications:$EnablePrereleaseNotifications -DisablePrereleaseNotifications:$DisablePrereleaseNotifications + $workflowContext = Get-NovaUpdateNotificationPreferenceChangeContext -EnablePrereleaseNotifications:$EnablePrereleaseNotifications -DisablePrereleaseNotifications:$DisablePrereleaseNotifications -WorkflowParams @{WhatIf = [bool]$WhatIfPreference} - if (-not $PSCmdlet.ShouldProcess($workflowContext.Target, $workflowContext.Action)) { + $shouldRun = $PSCmdlet.ShouldProcess($workflowContext.Target, $workflowContext.Action) + if (-not $shouldRun -and -not $WhatIfPreference) { return } - return Invoke-NovaUpdateNotificationPreferenceChange -WorkflowContext $workflowContext + return Invoke-NovaUpdateNotificationPreferenceChange -WorkflowContext $workflowContext -ShouldRun:$shouldRun } diff --git a/src/public/UpdateNovaModuleTools.ps1 b/src/public/UpdateNovaModuleTools.ps1 index f54082f..08ed0a1 100644 --- a/src/public/UpdateNovaModuleTools.ps1 +++ b/src/public/UpdateNovaModuleTools.ps1 @@ -3,22 +3,22 @@ function Update-NovaModuleTool { [Alias('Update-NovaModuleTools')] param() - $workflowContext = Get-NovaModuleSelfUpdateWorkflowContext + $workflowContext = Get-NovaModuleSelfUpdateWorkflowContext -WorkflowParams @{WhatIf = [bool]$WhatIfPreference} $plan = $workflowContext.Plan - if (-not $plan.UpdateAvailable) { - return Complete-NovaModuleSelfUpdateResult -Plan $plan -ReleaseNotesUri $null + if ($plan.IsPrereleaseTarget -and -not $WhatIfPreference) { + $prereleaseConfirmed = Confirm-NovaPrereleaseModuleUpdate -Cmdlet $PSCmdlet -CurrentVersion $plan.CurrentVersion -TargetVersion $plan.TargetVersion + if (-not $prereleaseConfirmed) { + $plan.Cancelled = $true + return Invoke-NovaModuleSelfUpdateWorkflow -WorkflowContext $workflowContext -ShouldRun:$false + } } - if ($plan.IsPrereleaseTarget -and -not (Confirm-NovaPrereleaseModuleUpdate -Cmdlet $PSCmdlet -CurrentVersion $plan.CurrentVersion -TargetVersion $plan.TargetVersion)) { - $plan.Cancelled = $true - return Complete-NovaModuleSelfUpdateResult -Plan $plan -ReleaseNotesUri $null + $shouldRun = $plan.UpdateAvailable -and $PSCmdlet.ShouldProcess($plan.ModuleName, $workflowContext.Action) + if (-not $shouldRun) { + $plan.Cancelled = (-not $WhatIfPreference) -and $plan.UpdateAvailable } - if (-not $PSCmdlet.ShouldProcess($plan.ModuleName, $workflowContext.Action)) { - return Complete-NovaModuleSelfUpdateResult -Plan $plan -ReleaseNotesUri $null - } - - $result = Invoke-NovaModuleSelfUpdateWorkflow -WorkflowContext $workflowContext + $result = Invoke-NovaModuleSelfUpdateWorkflow -WorkflowContext $workflowContext -ShouldRun:$shouldRun Write-NovaModuleReleaseNotesLink -ReleaseNotesUri $result.ReleaseNotesUri return $result } diff --git a/tests/ArchitectureGuardrails.Tests.ps1 b/tests/ArchitectureGuardrails.Tests.ps1 index dc07e3d..e9cf619 100644 --- a/tests/ArchitectureGuardrails.Tests.ps1 +++ b/tests/ArchitectureGuardrails.Tests.ps1 @@ -95,7 +95,7 @@ Describe 'Architecture guardrails' { [pscustomobject]@{Path = 'src/public/PublishNovaModule.ps1'; ExpectedHelpers = @('Get-NovaDynamicDeliveryParameterDictionary', 'Get-NovaProjectInfo', 'Get-NovaPublishWorkflowContext', 'Get-NovaShouldProcessForwardingParameter', 'Invoke-NovaPublishWorkflow', 'Write-NovaPublishWorkflowContext')} [pscustomobject]@{Path = 'src/public/SetNovaUpdateNotificationPreference.ps1'; ExpectedHelpers = @('Get-NovaUpdateNotificationPreferenceChangeContext', 'Invoke-NovaUpdateNotificationPreferenceChange')} [pscustomobject]@{Path = 'src/public/TestNovaBuild.ps1'; ExpectedHelpers = @('Get-NovaTestWorkflowContext', 'Invoke-NovaTestWorkflow', 'New-NovaTestDynamicParameterDictionary')} - [pscustomobject]@{Path = 'src/public/UpdateNovaModuleTools.ps1'; ExpectedHelpers = @('Complete-NovaModuleSelfUpdateResult', 'Confirm-NovaPrereleaseModuleUpdate', 'Get-NovaModuleSelfUpdateWorkflowContext', 'Invoke-NovaModuleSelfUpdateWorkflow', 'Write-NovaModuleReleaseNotesLink')} + [pscustomobject]@{Path = 'src/public/UpdateNovaModuleTools.ps1'; ExpectedHelpers = @('Confirm-NovaPrereleaseModuleUpdate', 'Get-NovaModuleSelfUpdateWorkflowContext', 'Invoke-NovaModuleSelfUpdateWorkflow', 'Write-NovaModuleReleaseNotesLink')} [pscustomobject]@{Path = 'src/public/UpdateNovaModuleVersion.ps1'; ExpectedHelpers = @('Get-NovaVersionUpdateWorkflowContext', 'Invoke-NovaVersionUpdateCiActivation', 'Invoke-NovaVersionUpdateWorkflow', 'Write-NovaVersionUpdateResultOutput')} ) $expectedPaths = @($testCases | ForEach-Object Path | Sort-Object) diff --git a/tests/private/build/InvokeNovaBuildWorkflow.TestSupport.ps1 b/tests/private/build/InvokeNovaBuildWorkflow.TestSupport.ps1 index c8fc538..732b91a 100644 --- a/tests/private/build/InvokeNovaBuildWorkflow.TestSupport.ps1 +++ b/tests/private/build/InvokeNovaBuildWorkflow.TestSupport.ps1 @@ -7,3 +7,4 @@ function Build-Help {param($ProjectInfo)} function Copy-ProjectResource {param($ProjectInfo)} function Invoke-NovaModuleUpdateNotificationSafely {} function Import-NovaBuiltModuleForCi {param($ProjectInfo)} +function Write-Message {param([string]$Text, [string]$color)} diff --git a/tests/private/build/InvokeNovaBuildWorkflow.Tests.ps1 b/tests/private/build/InvokeNovaBuildWorkflow.Tests.ps1 index 30cf48e..6949da5 100644 --- a/tests/private/build/InvokeNovaBuildWorkflow.Tests.ps1 +++ b/tests/private/build/InvokeNovaBuildWorkflow.Tests.ps1 @@ -6,7 +6,49 @@ BeforeAll { } Describe 'Invoke-NovaBuildWorkflow' { - It 'uses the shared module update notification before the optional CI import step' { + It 'reports progress, runs the build steps in order, and prints the next suggested validation step' { + $global:steps = @() + try { + $workflowContext = [pscustomobject]@{ + ProjectInfo = [pscustomobject]@{ + ProjectName = 'NovaModuleTools' + OutputModuleDir = '/tmp/dist/NovaModuleTools' + FailOnDuplicateFunctionNames = $true + } + ContinuousIntegrationRequested = $false + } + + Mock Assert-NovaPublicFunctionFileLayout {$global:steps += 'public-layout'} + Mock Reset-ProjectDist {$global:steps += 'reset'} + Mock Build-Module {$global:steps += 'module'} + Mock Assert-BuiltModuleHasNoDuplicateFunctionName {$global:steps += 'duplicates'} + Mock Build-Manifest {$global:steps += 'manifest'} + Mock Build-Help {$global:steps += 'help'} + Mock Copy-ProjectResource {$global:steps += 'resources'} + Mock Invoke-NovaModuleUpdateNotificationSafely {$global:steps += 'notification'} + Mock Import-NovaBuiltModuleForCi {$global:steps += 'ci'} + Mock Write-Message {} + Mock Write-Progress {} + + Invoke-NovaBuildWorkflow -WorkflowContext $workflowContext + + $global:steps -join ',' | Should -Be 'public-layout,reset,module,duplicates,manifest,help,resources,notification' + Assert-MockCalled Invoke-NovaModuleUpdateNotificationSafely -Times 1 + Assert-MockCalled Import-NovaBuiltModuleForCi -Times 0 + Assert-MockCalled Write-Progress -Times 9 + Assert-MockCalled Write-Message -Times 3 + Assert-MockCalled Write-Message -Times 1 -ParameterFilter { + $Text -eq 'Built Nova module: NovaModuleTools' -and $color -eq 'Green' + } + Assert-MockCalled Write-Message -Times 1 -ParameterFilter { + $Text -eq 'Next step: Test-NovaBuild' + } + } finally { + Remove-Variable -Name steps -Scope Global -ErrorAction SilentlyContinue + } + } + + It 'uses the shared module update notification before the optional CI import step and reports the refreshed session' { $global:steps = @() try { $workflowContext = [pscustomobject]@{ @@ -27,12 +69,18 @@ Describe 'Invoke-NovaBuildWorkflow' { Mock Copy-ProjectResource {$global:steps += 'resources'} Mock Invoke-NovaModuleUpdateNotificationSafely {$global:steps += 'notification'} Mock Import-NovaBuiltModuleForCi {$global:steps += 'ci'} + Mock Write-Message {} + Mock Write-Progress {} Invoke-NovaBuildWorkflow -WorkflowContext $workflowContext $global:steps -join ',' | Should -Be 'public-layout,reset,module,duplicates,manifest,help,resources,notification,ci' - Assert-MockCalled Invoke-NovaModuleUpdateNotificationSafely -Times 1 Assert-MockCalled Import-NovaBuiltModuleForCi -Times 1 + Assert-MockCalled Write-Progress -Times 10 + Assert-MockCalled Write-Message -Times 4 + Assert-MockCalled Write-Message -Times 1 -ParameterFilter { + $Text -eq 'The freshly built dist module is loaded for later commands in this session.' + } } finally { Remove-Variable -Name steps -Scope Global -ErrorAction SilentlyContinue } diff --git a/tests/private/cli/InvokeNovaCliCommandRoute.Tests.ps1 b/tests/private/cli/InvokeNovaCliCommandRoute.Tests.ps1 index 20d7986..c3c260a 100644 --- a/tests/private/cli/InvokeNovaCliCommandRoute.Tests.ps1 +++ b/tests/private/cli/InvokeNovaCliCommandRoute.Tests.ps1 @@ -231,6 +231,16 @@ Describe 'Invoke-NovaCliCommandRoute' { It 'throws on unknown command' { Mock Confirm-NovaCliRoutedCommand {} - {Invoke-NovaCliCommandRoute -InvocationContext (New-TestContext -Command 'bogus')} | Should -Throw -ErrorId 'Nova.Validation.UnknownCliCommand' + $record = $null + + try { + Invoke-NovaCliCommandRoute -InvocationContext (New-TestContext -Command 'bogus') + } catch { + $record = $_ + } + + $record | Should -Not -BeNullOrEmpty + $record.FullyQualifiedErrorId | Should -Be 'Nova.Validation.UnknownCliCommand' + $record.Exception.Message | Should -Be "Unknown nova command: bogus. Run 'nova --help' to list available commands, or 'nova --help ' for command-specific help." } } diff --git a/tests/private/cli/InvokeNovaCliInstallWorkflow.Tests.ps1 b/tests/private/cli/InvokeNovaCliInstallWorkflow.Tests.ps1 index 99e2981..f5862eb 100644 --- a/tests/private/cli/InvokeNovaCliInstallWorkflow.Tests.ps1 +++ b/tests/private/cli/InvokeNovaCliInstallWorkflow.Tests.ps1 @@ -9,6 +9,8 @@ BeforeAll { Describe 'Invoke-NovaCliInstallWorkflow' { It 'returns workflow result when directory is on PATH' { + Mock Write-Host {} + $ctx = [pscustomobject]@{ SourcePath = '/tmp/src/nova' TargetPath = '/usr/local/bin/nova' @@ -20,12 +22,25 @@ Describe 'Invoke-NovaCliInstallWorkflow' { $result.InstalledPath | Should -Be '/usr/local/bin/nova' $result.DirectoryOnPath | Should -BeTrue $result.ReleaseNotesUri | Should -Be 'https://example.test/notes' + Assert-MockCalled Write-Host -Times 3 + Assert-MockCalled Write-Host -Times 1 -ParameterFilter { + $Object -eq 'Installed nova launcher: /usr/local/bin/nova' -or $args -contains 'Installed nova launcher: /usr/local/bin/nova' + } + Assert-MockCalled Write-Host -Times 1 -ParameterFilter { + $Object -eq 'nova --help' -or $args -contains 'nova --help' + } } It 'warns when target directory is not on PATH' { + Mock Write-Host {} Mock Test-NovaCliDirectoryOnPath {return $false} $ctx = [pscustomobject]@{SourcePath = '/s'; TargetPath = '/t/nova'; TargetDirectory = '/t'; Force = $false} Invoke-NovaCliInstallWorkflow -WorkflowContext $ctx -WarningVariable warning -WarningAction SilentlyContinue | Out-Null $warning.Count | Should -BeGreaterThan 0 + $warning[0].Message | Should -Be "Installed nova to /t, but that directory is not currently in PATH. Add it to your shell profile, start a new shell, and then run 'nova --help'." + Assert-MockCalled Write-Host -Times 4 + Assert-MockCalled Write-Host -Times 1 -ParameterFilter { + $Object -eq 'Add /t to your PATH' -or $args -contains 'Add /t to your PATH' + } } } diff --git a/tests/private/package/InvokeNovaPackageWorkflow.TestSupport.ps1 b/tests/private/package/InvokeNovaPackageWorkflow.TestSupport.ps1 new file mode 100644 index 0000000..4c9549a --- /dev/null +++ b/tests/private/package/InvokeNovaPackageWorkflow.TestSupport.ps1 @@ -0,0 +1,18 @@ +function Invoke-NovaBuildValidation { + param($WorkflowContext) + + $script:validated = $true +} + +function Invoke-NovaPackageArtifactCreation { + param($WorkflowContext) + + return @([pscustomobject]@{PackagePath = '/p'}) +} + +function Write-Message { + param( + [string]$Text, + [string]$color + ) +} diff --git a/tests/private/package/InvokeNovaPackageWorkflow.Tests.ps1 b/tests/private/package/InvokeNovaPackageWorkflow.Tests.ps1 index acbdfa8..5dcad36 100644 --- a/tests/private/package/InvokeNovaPackageWorkflow.Tests.ps1 +++ b/tests/private/package/InvokeNovaPackageWorkflow.Tests.ps1 @@ -1,29 +1,90 @@ BeforeAll { $projectRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $PSScriptRoot)) . (Join-Path $projectRoot 'src/private/package/InvokeNovaPackageWorkflow.ps1') - - function Invoke-NovaBuildValidation {param($WorkflowContext) $script:validated = $true} - function Invoke-NovaPackageArtifactCreation {param($WorkflowContext) return @([pscustomobject]@{PackagePath='/p'})} + . (Join-Path $PSScriptRoot 'InvokeNovaPackageWorkflow.TestSupport.ps1') } Describe 'Invoke-NovaPackageWorkflow' { BeforeEach { $script:validated = $false + Mock Write-Message {} + Mock Write-Progress {} } It 'validates the build and returns without creating artifacts when ShouldRun is false' { Mock Invoke-NovaPackageArtifactCreation {} - $result = Invoke-NovaPackageWorkflow -WorkflowContext ([pscustomobject]@{}) + $result = Invoke-NovaPackageWorkflow -WorkflowContext ([pscustomobject]@{ + WorkflowParams = @{} + SkipTestsRequested = $false + ProjectInfo = [pscustomobject]@{ProjectName = 'Demo'} + Target = '/p/Demo.1.0.0.nupkg' + }) $script:validated | Should -BeTrue Should -Invoke Invoke-NovaPackageArtifactCreation -Times 0 + Assert-MockCalled Write-Progress -Times 2 $result | Should -BeNullOrEmpty } - It 'creates artifacts when ShouldRun is set' { - $artifacts = @([pscustomobject]@{PackagePath='/p'}) + It 'creates artifacts, reports progress, and prints the next suggested step when ShouldRun is set' { + $artifacts = @([pscustomobject]@{PackagePath='/p/Demo.1.0.0.nupkg'; OutputDirectory='/p'}) Mock Invoke-NovaPackageArtifactCreation {return $artifacts} - $result = Invoke-NovaPackageWorkflow -WorkflowContext ([pscustomobject]@{}) -ShouldRun + $result = Invoke-NovaPackageWorkflow -WorkflowContext ([pscustomobject]@{ + WorkflowParams = @{} + SkipTestsRequested = $false + ProjectInfo = [pscustomobject]@{ProjectName = 'Demo'} + Target = '/p/Demo.1.0.0.nupkg' + }) -ShouldRun Should -Invoke Invoke-NovaPackageArtifactCreation -Times 1 + Assert-MockCalled Write-Progress -Times 3 + Assert-MockCalled Write-Message -Times 3 + Assert-MockCalled Write-Message -Times 1 -ParameterFilter { + $Text -eq 'Created 1 package artifact for Demo' -and $color -eq 'Green' + } + Assert-MockCalled Write-Message -Times 1 -ParameterFilter { + $Text -eq 'Deploy-NovaPackage' + } @($result).Count | Should -Be 1 } + + It 'writes a package plan summary in WhatIf mode' { + $result = Invoke-NovaPackageWorkflow -WorkflowContext ([pscustomobject]@{ + WorkflowParams = @{WhatIf = $true} + SkipTestsRequested = $true + ProjectInfo = [pscustomobject]@{ProjectName = 'Demo'} + Target = '/p/Demo.1.0.0.nupkg' + }) + + $script:validated | Should -BeTrue + Assert-MockCalled Write-Message -Times 3 + Assert-MockCalled Write-Message -Times 1 -ParameterFilter { + $Text -eq 'Package plan ready for Demo' -and $color -eq 'Green' + } + Assert-MockCalled Write-Message -Times 1 -ParameterFilter { + $Text -eq 'Run New-NovaModulePackage without -WhatIf when you are ready to create the package artifacts.' + } + $result | Should -BeNullOrEmpty + } +} + +Describe 'Get-NovaPackageWorkflowStatusMessage' { + It 'reports the created artifact count when more than one artifact was produced' { + $workflowContext = [pscustomobject]@{ + ProjectInfo = [pscustomobject]@{ + ProjectName = 'Demo' + } + } + + Get-NovaPackageWorkflowStatusMessage -WorkflowContext $workflowContext -ArtifactCount 2 | Should -Be 'Created 2 package artifacts for Demo' + } +} + +Describe 'Get-NovaPackageWorkflowResultTarget' { + It 'falls back to the workflow target when artifacts do not expose output directories' { + $workflowContext = [pscustomobject]@{ + Target = '/p/Demo.1.0.0.nupkg' + } + + $artifacts = @([pscustomobject]@{PackagePath = '/p/Demo.1.0.0.nupkg'; OutputDirectory = $null}) + Get-NovaPackageWorkflowResultTarget -WorkflowContext $workflowContext -Artifacts $artifacts | Should -Be '/p/Demo.1.0.0.nupkg' + } } diff --git a/tests/private/quality/InvokeNovaTestWorkflow.TestSupport.ps1 b/tests/private/quality/InvokeNovaTestWorkflow.TestSupport.ps1 index adb9f25..10366f0 100644 --- a/tests/private/quality/InvokeNovaTestWorkflow.TestSupport.ps1 +++ b/tests/private/quality/InvokeNovaTestWorkflow.TestSupport.ps1 @@ -10,6 +10,8 @@ function Stop-NovaOperation { function Invoke-NovaBuild {} function Invoke-NovaPester {param($Configuration)} function Get-NovaBuildCommandParameterMap {param($WorkflowParams, [switch]$OverrideWarningRequested) return @{}} +function Write-Message {param([string]$Text, [string]$color)} +function Write-Progress {param([string]$Activity, [string]$Status, [int]$PercentComplete, [switch]$Completed)} function New-NovaInvokeNovaTestWorkflowContext { param( [hashtable]$PesterSettings = @{}, @@ -21,7 +23,7 @@ function New-NovaInvokeNovaTestWorkflowContext { return [pscustomobject]@{ BuildRequested = $BuildRequested WorkflowParams = $WorkflowParams - ProjectInfo = [pscustomobject]@{Pester = $PesterSettings} + ProjectInfo = [pscustomobject]@{ProjectName = 'NovaModuleTools'; Pester = $PesterSettings} TestResultDirectory = $TestResultDirectory TestResultPath = Join-Path $TestResultDirectory 'TestResults.xml' PesterConfig = [pscustomobject]@{TestResult = [pscustomobject]@{OutputPath = $null}} diff --git a/tests/private/quality/InvokeNovaTestWorkflow.Tests.ps1 b/tests/private/quality/InvokeNovaTestWorkflow.Tests.ps1 index 8814588..c10bc18 100644 --- a/tests/private/quality/InvokeNovaTestWorkflow.Tests.ps1 +++ b/tests/private/quality/InvokeNovaTestWorkflow.Tests.ps1 @@ -6,10 +6,15 @@ BeforeAll { } Describe 'Invoke-NovaTestWorkflow' { + BeforeEach { + Mock Write-Message {} + Mock Write-Progress {} + } + It 'uses the pre-resolved coverage assertion after the Pester run' { $global:coverageAssertionRan = $false $workflowContext = [pscustomobject]@{ - ProjectInfo = [pscustomobject]@{Pester = [ordered]@{}} + ProjectInfo = [pscustomobject]@{ProjectName = 'NovaModuleTools'; Pester = [ordered]@{}} TestResultDirectory = '/tmp/nova-project/artifacts' TestResultPath = '/tmp/nova-project/artifacts/TestResults.xml' PesterConfig = [pscustomobject]@{TestResult = [pscustomobject]@{OutputPath = $null}} @@ -26,6 +31,14 @@ Describe 'Invoke-NovaTestWorkflow' { {Invoke-NovaTestWorkflow -WorkflowContext $workflowContext} | Should -Not -Throw $global:coverageAssertionRan | Should -BeTrue + Assert-MockCalled Write-Message -Times 4 + Assert-MockCalled Write-Progress -Times 5 + Assert-MockCalled Write-Message -Times 1 -ParameterFilter { + $Text -eq 'Pester tests passed for NovaModuleTools' -and $color -eq 'Green' + } + Assert-MockCalled Write-Message -Times 1 -ParameterFilter { + $Text -eq 'Publish-NovaModule -Local' + } } finally { Remove-Variable -Name coverageAssertionRan -Scope Global -ErrorAction SilentlyContinue } @@ -35,20 +48,21 @@ Describe 'Invoke-NovaTestWorkflow' { @{ Name = 'coverage below target' PesterResult = [pscustomobject]@{Result = 'Passed'; CodeCoverage = [pscustomobject]@{CoveragePercent = 66.67}} - ExpectedMessage = 'Code coverage 66.67% did not meet the configured target 90%.' + ExpectedMessage = 'Code coverage 66.67% did not meet the configured target 90%. Review the failing tests or coverage settings, then rerun Test-NovaBuild.' ExpectedErrorId = 'Nova.Workflow.CodeCoverageTargetNotMet' ExpectedCategory = [System.Management.Automation.ErrorCategory]::InvalidOperation } @{ Name = 'missing measured coverage' PesterResult = [pscustomobject]@{Result = 'Passed'} - ExpectedMessage = 'Code coverage target 90% is configured, but the Pester result did not include a coverage percentage.' + ExpectedMessage = 'Code coverage target 90% is configured, but the Pester result did not include a coverage percentage. Review the coverage settings in project.json and the test result file at /tmp/nova-project/artifacts/TestResults.xml.' ExpectedErrorId = 'Nova.Workflow.CodeCoveragePercentMissing' ExpectedCategory = [System.Management.Automation.ErrorCategory]::InvalidData } ) { $workflowContext = [pscustomobject]@{ ProjectInfo = [pscustomobject]@{ + ProjectName = 'NovaModuleTools' Pester = [ordered]@{CodeCoverage = [ordered]@{Enabled = $true; CoveragePercentTarget = 90}} } TestResultDirectory = '/tmp/nova-project/artifacts' @@ -73,7 +87,7 @@ Describe 'Invoke-NovaTestWorkflow' { It 'does not enforce a coverage threshold when project.json does not configure one' { $workflowContext = [pscustomobject]@{ - ProjectInfo = [pscustomobject]@{Pester = [ordered]@{}} + ProjectInfo = [pscustomobject]@{ProjectName = 'NovaModuleTools'; Pester = [ordered]@{}} TestResultDirectory = '/tmp/nova-project/artifacts' TestResultPath = '/tmp/nova-project/artifacts/TestResults.xml' PesterConfig = [pscustomobject]@{TestResult = [pscustomobject]@{OutputPath = $null}} @@ -88,6 +102,10 @@ Describe 'Invoke-NovaTestWorkflow' { {Invoke-NovaTestWorkflow -WorkflowContext $workflowContext} | Should -Not -Throw $workflowContext.PesterConfig.TestResult.OutputPath | Should -Be '/tmp/nova-project/artifacts/TestResults.xml' + Assert-MockCalled Write-Message -Times 5 + Assert-MockCalled Write-Message -Times 1 -ParameterFilter { + $Text -eq 'Measured code coverage: 10%' + } } It 'falls back to CodeCoverage as the target object when the workflow context has no test result path' { @@ -123,7 +141,7 @@ Describe 'Invoke-NovaTestWorkflow' { It 'suppresses global progress output around the Pester run and restores the previous preference' { $workflowContext = [pscustomobject]@{ - ProjectInfo = [pscustomobject]@{Pester = [ordered]@{}} + ProjectInfo = [pscustomobject]@{ProjectName = 'NovaModuleTools'; Pester = [ordered]@{}} TestResultDirectory = '/tmp/nova-project/artifacts' TestResultPath = '/tmp/nova-project/artifacts/TestResults.xml' PesterConfig = [pscustomobject]@{TestResult = [pscustomobject]@{OutputPath = $null}} @@ -153,7 +171,7 @@ Describe 'Invoke-NovaTestWorkflow' { It 'restores the previous progress preference even when Pester throws' { $workflowContext = [pscustomobject]@{ - ProjectInfo = [pscustomobject]@{Pester = [ordered]@{}} + ProjectInfo = [pscustomobject]@{ProjectName = 'NovaModuleTools'; Pester = [ordered]@{}} TestResultDirectory = '/tmp/nova-project/artifacts' TestResultPath = '/tmp/nova-project/artifacts/TestResults.xml' PesterConfig = [pscustomobject]@{TestResult = [pscustomobject]@{OutputPath = $null}} @@ -185,6 +203,7 @@ Describe 'Invoke-NovaTestWorkflow' { ExpectedBuildCalls = 1 ExpectedPesterCalls = 1 ExpectedErrorId = $null + ExpectedMessageCount = 4 } @{ Name = 'an explicit ShouldRun=false' @@ -195,6 +214,7 @@ Describe 'Invoke-NovaTestWorkflow' { ExpectedBuildCalls = 0 ExpectedPesterCalls = 0 ExpectedErrorId = $null + ExpectedMessageCount = 0 } @{ Name = 'a WhatIf workflow parameter' @@ -205,6 +225,7 @@ Describe 'Invoke-NovaTestWorkflow' { ExpectedBuildCalls = 0 ExpectedPesterCalls = 0 ExpectedErrorId = $null + ExpectedMessageCount = 4 } @{ Name = 'a failed Pester result' @@ -215,6 +236,7 @@ Describe 'Invoke-NovaTestWorkflow' { ExpectedBuildCalls = 0 ExpectedPesterCalls = 1 ExpectedErrorId = 'Nova.Workflow.TestRunFailed' + ExpectedMessageCount = 0 } ) { $workflowContext = New-NovaInvokeNovaTestWorkflowContext -BuildRequested $BuildRequested -WorkflowParams $WorkflowParams @@ -237,6 +259,9 @@ Describe 'Invoke-NovaTestWorkflow' { if ($ExpectedErrorId) { $thrown | Should -Not -BeNullOrEmpty $thrown.FullyQualifiedErrorId | Should -Be $ExpectedErrorId + if ($ExpectedErrorId -eq 'Nova.Workflow.TestRunFailed') { + $thrown.Exception.Message | Should -Be 'Pester reported one or more failing tests. Review the output above and the test result file at /tmp/nova-project/artifacts/TestResults.xml, then rerun Test-NovaBuild.' + } } else { $thrown | Should -BeNullOrEmpty @@ -244,12 +269,13 @@ Describe 'Invoke-NovaTestWorkflow' { Should -Invoke Invoke-NovaBuild -Times $ExpectedBuildCalls Should -Invoke Invoke-NovaPester -Times $ExpectedPesterCalls + Assert-MockCalled Write-Message -Times $ExpectedMessageCount } It 'creates the artifact directory when it does not exist' { $tempDir = Join-Path ([System.IO.Path]::GetTempPath()) ([guid]::NewGuid()) $workflowContext = [pscustomobject]@{ - ProjectInfo = [pscustomobject]@{Pester = [ordered]@{}} + ProjectInfo = [pscustomobject]@{ProjectName = 'NovaModuleTools'; Pester = [ordered]@{}} TestResultDirectory = $tempDir TestResultPath = (Join-Path $tempDir 'TestResults.xml') PesterConfig = [pscustomobject]@{TestResult = [pscustomobject]@{OutputPath = $null}} @@ -265,4 +291,57 @@ Describe 'Invoke-NovaTestWorkflow' { } } + It 'writes a test plan summary in WhatIf mode after previewing the nested build step' { + $workflowContext = New-NovaInvokeNovaTestWorkflowContext -BuildRequested $true -WorkflowParams @{WhatIf = $true} -PesterSettings @{ + CodeCoverage = [ordered]@{ + Enabled = $true + CoveragePercentTarget = 99 + } + } + Mock Invoke-NovaBuild {} + Mock Invoke-NovaPester {} + + Invoke-NovaTestWorkflow -WorkflowContext $workflowContext + + Should -Invoke Invoke-NovaBuild -Times 1 + Should -Invoke Invoke-NovaPester -Times 0 + Assert-MockCalled Write-Message -Times 5 + Assert-MockCalled Write-Progress -Times 2 + Assert-MockCalled Write-Progress -Times 1 -ParameterFilter { + $Status -eq 'Previewing the build-before-test workflow' -and $PercentComplete -eq 20 + } + Assert-MockCalled Write-Message -Times 1 -ParameterFilter { + $Text -eq 'Test plan ready for NovaModuleTools' -and $color -eq 'Green' + } + Assert-MockCalled Write-Message -Times 1 -ParameterFilter { + $Text -eq 'Configured coverage target: 99%' + } + Assert-MockCalled Write-Message -Times 1 -ParameterFilter { + $Text -eq 'Run Test-NovaBuild without -WhatIf when you are ready to execute the test workflow.' + } + } + +} + +Describe 'Get-NovaTestWorkflowCoverageMessage' { + It 'includes the measured and configured coverage values when both are available' { + $workflowContext = [pscustomobject]@{ + ProjectInfo = [pscustomobject]@{ + Pester = [ordered]@{ + CodeCoverage = [ordered]@{ + Enabled = $true + CoveragePercentTarget = 90 + } + } + } + } + + $testResult = [pscustomobject]@{ + CodeCoverage = [pscustomobject]@{ + CoveragePercent = 66.67 + } + } + + Get-NovaTestWorkflowCoverageMessage -WorkflowContext $workflowContext -TestResult $testResult | Should -Be 'Measured code coverage: 66.67% (target: 90%)' + } } diff --git a/tests/private/release/InvokeNovaPublishWorkflow.TestSupport.ps1 b/tests/private/release/InvokeNovaPublishWorkflow.TestSupport.ps1 new file mode 100644 index 0000000..a7d9db1 --- /dev/null +++ b/tests/private/release/InvokeNovaPublishWorkflow.TestSupport.ps1 @@ -0,0 +1,27 @@ +function Import-NovaBuiltModuleForCi { + param($ProjectInfo) + + $script:ciImportCalls += 1 +} + +function Invoke-NovaBuildValidation { + param($WorkflowContext) + + $script:validationCalls += 1 +} + +function Write-Message { + param( + [string]$Text, + [string]$color + ) +} + +function Write-Progress { + param( + [string]$Activity, + [string]$Status, + [int]$PercentComplete, + [switch]$Completed + ) +} diff --git a/tests/private/release/InvokeNovaPublishWorkflow.Tests.ps1 b/tests/private/release/InvokeNovaPublishWorkflow.Tests.ps1 index 5957439..59281df 100644 --- a/tests/private/release/InvokeNovaPublishWorkflow.Tests.ps1 +++ b/tests/private/release/InvokeNovaPublishWorkflow.Tests.ps1 @@ -1,8 +1,7 @@ BeforeAll { $projectRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $PSScriptRoot)) . (Join-Path $projectRoot 'src/private/release/InvokeNovaPublishWorkflow.ps1') - function Import-NovaBuiltModuleForCi {param($ProjectInfo) $script:ciImportCalls += 1} - function Invoke-NovaBuildValidation {param($WorkflowContext) $script:validationCalls += 1} + . (Join-Path $PSScriptRoot 'InvokeNovaPublishWorkflow.TestSupport.ps1') } Describe 'Test-NovaPublishWorkflowShouldImportLocalModule' { @@ -20,6 +19,18 @@ Describe 'Test-NovaPublishWorkflowShouldImportLocalModule' { } } +Describe 'Test-NovaPublishWorkflowWhatIfEnabled' { + It 'returns true when WorkflowParams.WhatIf is enabled' { + $ctx = [pscustomobject]@{WorkflowParams = @{WhatIf = $true}} + Test-NovaPublishWorkflowWhatIfEnabled -WorkflowContext $ctx | Should -BeTrue + } + + It 'returns false when WorkflowParams.WhatIf is not enabled' { + $ctx = [pscustomobject]@{WorkflowParams = @{}} + Test-NovaPublishWorkflowWhatIfEnabled -WorkflowContext $ctx | Should -BeFalse + } +} + Describe 'Invoke-NovaPublishWorkflowCiRestore' { It 'invokes the importer when ShouldRun and CI requested' { $script:calls = 0 @@ -41,39 +52,99 @@ Describe 'Invoke-NovaPublishWorkflow' { $script:publishCalls = 0 $script:localImportCalls = 0 $script:ciImportCalls = 0 + Mock Write-Message {} + Mock Write-Progress {} } - It 'runs validation and publish; skips local import when activation is null' { + It 'runs validation and publish, reports progress, and suggests how to verify repository publish' { Mock Invoke-NovaBuildValidation {$script:validationCalls += 1} $ctx = [pscustomobject]@{ PublishParams = @{} - PublishInvocation = [pscustomobject]@{Action = {param() $script:publishCalls += 1}} + PublishInvocation = [pscustomobject]@{Action = {param() $script:publishCalls += 1}; Target = 'PSGallery'; IsLocal = $false} LocalPublishActivation = $null - ProjectInfo = [pscustomobject]@{} + ProjectInfo = [pscustomobject]@{ProjectName = 'NovaModuleTools'} + WorkflowParams = @{} + SkipTestsRequested = $false ContinuousIntegrationRequested = $false } Invoke-NovaPublishWorkflow -WorkflowContext $ctx -ShouldRun $script:validationCalls | Should -Be 1 $script:publishCalls | Should -Be 1 $script:localImportCalls | Should -Be 0 + Assert-MockCalled Write-Progress -Times 3 + Assert-MockCalled Write-Message -Times 4 + Assert-MockCalled Write-Message -Times 1 -ParameterFilter { + $Text -eq 'Published Nova module: NovaModuleTools' -and $color -eq 'Green' + } + Assert-MockCalled Write-Message -Times 1 -ParameterFilter { + $Text -eq 'Find-Module NovaModuleTools -Repository PSGallery' + } } - It 'imports local published module when LocalPublishActivation is set' { + It 'imports the local published module, restores the built module in CI, and reports the result' { Mock Invoke-NovaBuildValidation {$script:validationCalls += 1} $ctx = [pscustomobject]@{ PublishParams = @{} PublishInvocation = [pscustomobject]@{ Action = {param() $script:publishCalls += 1} + IsLocal = $true + Target = '/modules' Parameters = [pscustomobject]@{ProjectInfo = [pscustomobject]@{ProjectName='Mod'}} } LocalPublishActivation = [pscustomobject]@{ ManifestPath='/m/Mod.psd1' ImportAction = {param($ProjectName,$ManifestPath) $script:localImportCalls += 1} } - ProjectInfo = [pscustomobject]@{} - ContinuousIntegrationRequested = $false + ProjectInfo = [pscustomobject]@{ProjectName = 'Mod'} + WorkflowParams = @{} + SkipTestsRequested = $true + ContinuousIntegrationRequested = $true } Invoke-NovaPublishWorkflow -WorkflowContext $ctx -ShouldRun $script:localImportCalls | Should -Be 1 + $script:ciImportCalls | Should -Be 1 + Assert-MockCalled Write-Progress -Times 5 + Assert-MockCalled Write-Message -Times 6 + Assert-MockCalled Write-Message -Times 1 -ParameterFilter { + $Text -eq 'Pre-publish tests were skipped for this run.' + } + Assert-MockCalled Write-Message -Times 1 -ParameterFilter { + $Text -eq 'The published local module is loaded from /m/Mod.psd1.' + } + Assert-MockCalled Write-Message -Times 1 -ParameterFilter { + $Text -eq 'The freshly built dist module is loaded again for later commands in this session.' + } + Assert-MockCalled Write-Message -Times 1 -ParameterFilter { + $Text -eq 'Get-NovaProjectInfo -Installed' + } + } + + It 'writes a publish plan summary in WhatIf mode without importing or restoring modules' { + Mock Invoke-NovaBuildValidation {$script:validationCalls += 1} + $ctx = [pscustomobject]@{ + PublishParams = @{WhatIf = $true} + PublishInvocation = [pscustomobject]@{Action = {param() $script:publishCalls += 1}; Target = 'PSGallery'; IsLocal = $false} + LocalPublishActivation = [pscustomobject]@{ + ManifestPath='/m/Mod.psd1' + ImportAction = {param($ProjectName,$ManifestPath) $script:localImportCalls += 1} + } + ProjectInfo = [pscustomobject]@{ProjectName = 'NovaModuleTools'} + WorkflowParams = @{WhatIf = $true} + SkipTestsRequested = $false + ContinuousIntegrationRequested = $true + } + + Invoke-NovaPublishWorkflow -WorkflowContext $ctx + + $script:publishCalls | Should -Be 1 + $script:localImportCalls | Should -Be 0 + $script:ciImportCalls | Should -Be 0 + Assert-MockCalled Write-Message -Times 4 + Assert-MockCalled Write-Message -Times 1 -ParameterFilter { + $Text -eq 'Publish plan ready for NovaModuleTools' -and $color -eq 'Green' + } + Assert-MockCalled Write-Message -Times 1 -ParameterFilter { + $Text -eq 'Run Publish-NovaModule without -WhatIf when you are ready to publish the module.' + } } } diff --git a/tests/private/release/InvokeNovaReleaseWorkflow.TestSupport.ps1 b/tests/private/release/InvokeNovaReleaseWorkflow.TestSupport.ps1 new file mode 100644 index 0000000..c9b2a69 --- /dev/null +++ b/tests/private/release/InvokeNovaReleaseWorkflow.TestSupport.ps1 @@ -0,0 +1,54 @@ +function Get-NovaBuildCommandParameterMap { + param( + [hashtable]$WorkflowParams, + [switch]$OverrideWarningRequested + ) + + $map = @{} + $WorkflowParams + if ($OverrideWarningRequested) { + $map.OverrideWarning = $true + } + + return $map +} + +function Invoke-NovaBuild { + param() + + $script:buildCalls += 1 +} + +function Test-NovaBuild { + param() + + $script:testCalls += 1 +} + +function Update-NovaModuleVersion { + param() + + $script:versionCalls += 1 + return [pscustomobject]@{Version = '1.0.0'} +} + +function Import-NovaBuiltModuleForCi { + param($ProjectInfo) + + $script:restoreCalls += 1 +} + +function Write-Message { + param( + [string]$Text, + [string]$color + ) +} + +function Write-Progress { + param( + $Activity, + $Status, + $PercentComplete, + [switch]$Completed + ) +} diff --git a/tests/private/release/InvokeNovaReleaseWorkflow.Tests.ps1 b/tests/private/release/InvokeNovaReleaseWorkflow.Tests.ps1 index ad1060d..2bd26fc 100644 --- a/tests/private/release/InvokeNovaReleaseWorkflow.Tests.ps1 +++ b/tests/private/release/InvokeNovaReleaseWorkflow.Tests.ps1 @@ -1,11 +1,7 @@ BeforeAll { $projectRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $PSScriptRoot)) . (Join-Path $projectRoot 'src/private/release/InvokeNovaReleaseWorkflow.ps1') - function Get-NovaBuildCommandParameterMap {param([hashtable]$WorkflowParams,[switch]$OverrideWarningRequested) $map=@{}+$WorkflowParams; if($OverrideWarningRequested){$map.OverrideWarning=$true}; return $map} - function Invoke-NovaBuild {param() $script:buildCalls += 1} - function Test-NovaBuild {param() $script:testCalls += 1} - function Update-NovaModuleVersion {param() $script:versionCalls += 1; return [pscustomobject]@{Version='1.0.0'}} - function Import-NovaBuiltModuleForCi {param($ProjectInfo) $script:restoreCalls += 1} + . (Join-Path $PSScriptRoot 'InvokeNovaReleaseWorkflow.TestSupport.ps1') } Describe 'Get-NovaReleaseNestedWorkflowParameterMap' { @@ -27,6 +23,34 @@ Describe 'Get-NovaReleaseBuildWorkflowParameterMap' { } } +Describe 'Get-NovaReleaseWorkflowResultVersion' { + It 'returns null when no version result is available' { + Get-NovaReleaseWorkflowResultVersion -VersionResult $null | Should -BeNullOrEmpty + } + + It 'prefers NewVersion when the version result exposes it' { + Get-NovaReleaseWorkflowResultVersion -VersionResult ([pscustomobject]@{NewVersion = '1.2.3'; Version = '1.2.2'}) | Should -Be '1.2.3' + } + + It 'falls back to Version when NewVersion is not present' { + Get-NovaReleaseWorkflowResultVersion -VersionResult ([pscustomobject]@{Version = '1.2.3'}) | Should -Be '1.2.3' + } + + It 'returns null when the version result has no version-like properties' { + Get-NovaReleaseWorkflowResultVersion -VersionResult ([pscustomobject]@{Other = 'x'}) | Should -BeNullOrEmpty + } +} + +Describe 'Get-NovaReleaseWorkflowStatusMessage' { + It 'returns a plan-ready message without a version in WhatIf mode' { + Get-NovaReleaseWorkflowStatusMessage -ProjectInfo ([pscustomobject]@{ProjectName = 'NovaModuleTools'}) -WhatIfEnabled | Should -Be 'Release plan ready for NovaModuleTools' + } + + It 'returns a released message without a version when no version was resolved' { + Get-NovaReleaseWorkflowStatusMessage -ProjectInfo ([pscustomobject]@{ProjectName = 'NovaModuleTools'}) | Should -Be 'Released Nova module: NovaModuleTools' + } +} + Describe 'Test-NovaReleaseWorkflowShouldRestoreBuiltModule' { It 'returns true when CI is on and WhatIf is not set' { Test-NovaReleaseWorkflowShouldRestoreBuiltModule -WorkflowParams @{} -ContinuousIntegrationRequested | Should -BeTrue @@ -46,14 +70,16 @@ Describe 'Invoke-NovaReleaseWorkflow' { $script:versionCalls = 0 $script:restoreCalls = 0 $script:publishCalls = 0 + Mock Write-Message {} + Mock Write-Progress {} } - It 'builds, tests, updates version, builds again, publishes, and restores in CI' { + It 'builds, tests, updates version, builds again, publishes, restores in CI, and reports the result' { $ctx = [pscustomobject]@{ WorkflowParams = @{} PublishParams = @{} - PublishInvocation = [pscustomobject]@{Action = {param() $script:publishCalls += 1}} - ProjectInfo = [pscustomobject]@{} + PublishInvocation = [pscustomobject]@{Action = {param() $script:publishCalls += 1}; Target = 'PSGallery'; IsLocal = $false} + ProjectInfo = [pscustomobject]@{ProjectName = 'NovaModuleTools'} ContinuousIntegrationRequested = $true SkipTestsRequested = $false OverrideWarningRequested = $false @@ -65,18 +91,53 @@ Describe 'Invoke-NovaReleaseWorkflow' { $script:versionCalls | Should -Be 1 $script:publishCalls | Should -Be 1 $script:restoreCalls | Should -Be 1 + Assert-MockCalled Write-Progress -Times 6 + Assert-MockCalled Write-Message -Times 5 + Assert-MockCalled Write-Message -Times 1 -ParameterFilter { + $Text -eq 'Released Nova module: NovaModuleTools 1.0.0' -and $color -eq 'Green' + } + Assert-MockCalled Write-Message -Times 1 -ParameterFilter { + $Text -eq 'Get-NovaProjectInfo -Version' + } } It 'skips tests when SkipTestsRequested and skips restore when not CI' { $ctx = [pscustomobject]@{ WorkflowParams = @{} PublishParams = @{} - PublishInvocation = [pscustomobject]@{Action = {param() $script:publishCalls += 1}} - ProjectInfo = [pscustomobject]@{} + PublishInvocation = [pscustomobject]@{Action = {param() $script:publishCalls += 1}; Target = '/modules'; IsLocal = $true} + ProjectInfo = [pscustomobject]@{ProjectName = 'NovaModuleTools'} SkipTestsRequested = $true } $null = Invoke-NovaReleaseWorkflow -WorkflowContext $ctx $script:testCalls | Should -Be 0 $script:restoreCalls | Should -Be 0 + Assert-MockCalled Write-Message -Times 1 -ParameterFilter { + $Text -eq 'Pre-release tests were skipped for this run.' + } + Assert-MockCalled Write-Message -Times 1 -ParameterFilter { + $Text -eq 'Get-NovaProjectInfo -Installed' + } + } + + It 'writes a release plan summary in WhatIf mode without restoring the built module' { + $ctx = [pscustomobject]@{ + WorkflowParams = @{WhatIf = $true} + PublishParams = @{} + PublishInvocation = [pscustomobject]@{Action = {param() $script:publishCalls += 1}; Target = 'PSGallery'; IsLocal = $false} + ProjectInfo = [pscustomobject]@{ProjectName = 'NovaModuleTools'} + ContinuousIntegrationRequested = $true + SkipTestsRequested = $false + } + + $null = Invoke-NovaReleaseWorkflow -WorkflowContext $ctx + + $script:restoreCalls | Should -Be 0 + Assert-MockCalled Write-Message -Times 1 -ParameterFilter { + $Text -eq 'Release plan ready for NovaModuleTools -> 1.0.0' -and $color -eq 'Green' + } + Assert-MockCalled Write-Message -Times 1 -ParameterFilter { + $Text -eq 'Run Invoke-NovaRelease without -WhatIf when you are ready to apply the release.' + } } } diff --git a/tests/private/release/InvokeNovaVersionUpdateWorkflow.Tests.ps1 b/tests/private/release/InvokeNovaVersionUpdateWorkflow.Tests.ps1 index 164ffce..63aeb18 100644 --- a/tests/private/release/InvokeNovaVersionUpdateWorkflow.Tests.ps1 +++ b/tests/private/release/InvokeNovaVersionUpdateWorkflow.Tests.ps1 @@ -5,17 +5,12 @@ BeforeAll { function Set-NovaModuleVersion {param($ProjectInfo, $Label, [switch]$PreviewRelease, [switch]$Confirm) return [pscustomobject]@{Applied=$true}} } -Describe 'Test-NovaVersionUpdateResultRequired' { - It 'returns true when ShouldRun is set' { - Test-NovaVersionUpdateResultRequired -ShouldRun | Should -BeTrue - } - - It 'returns true when WhatIfEnabled is set' { - Test-NovaVersionUpdateResultRequired -WhatIfEnabled | Should -BeTrue - } - - It 'returns false when neither is set' { - Test-NovaVersionUpdateResultRequired | Should -BeFalse +Describe 'Invoke-NovaVersionUpdateWorkflowStep' { + It 'reports progress before invoking the action' { + Mock Write-Progress {} + $result = Invoke-NovaVersionUpdateWorkflowStep -Activity 'Updating' -Status 'Writing version' -PercentComplete 80 -Action { 'done' } + $result | Should -Be 'done' + Should -Invoke Write-Progress -Times 1 -ParameterFilter { $Activity -eq 'Updating' -and $Status -eq 'Writing version' -and $PercentComplete -eq 80 } } } @@ -41,32 +36,49 @@ Describe 'Get-NovaVersionUpdateAdvisoryMessage' { Describe 'Get-NovaVersionUpdateResult' { It 'assembles a structured result' { - $ctx = [pscustomobject]@{PreviousVersion='1.0.0'; NewVersion='1.0.1'; Label='Patch'; CommitCount=2} - $result = Get-NovaVersionUpdateResult -WorkflowContext $ctx -Applied + $ctx = [pscustomobject]@{ + PreviousVersion = '1.0.0' + NewVersion = '1.0.1' + Label = 'Patch' + CommitCount = 2 + ProjectInfo = [pscustomobject]@{ProjectJSON='/p/project.json'} + } + $result = Get-NovaVersionUpdateResult -WorkflowContext $ctx -Applied -Previewed $result.PreviousVersion | Should -Be '1.0.0' $result.NewVersion | Should -Be '1.0.1' $result.Label | Should -Be 'Patch' $result.EffectiveLabel | Should -Be 'Patch' $result.CommitCount | Should -Be 2 + $result.ProjectFile | Should -Be '/p/project.json' + $result.Target | Should -Be 'project.json' $result.Applied | Should -BeTrue + $result.Previewed | Should -BeTrue } } Describe 'Invoke-NovaVersionUpdateWorkflow' { - It 'returns null when neither ShouldRun nor WhatIfEnabled is set' { + It 'returns a cancelled result when confirmation is declined before the write step' { $ctx = [pscustomobject]@{ProjectInfo=[pscustomobject]@{}; PreviewRelease=$false; Label='Patch'; EffectiveLabel='Patch'} - Invoke-NovaVersionUpdateWorkflow -WorkflowContext $ctx | Should -BeNullOrEmpty + $result = Invoke-NovaVersionUpdateWorkflow -WorkflowContext $ctx + $result.Applied | Should -BeFalse + $result.Cancelled | Should -BeTrue } + It 'returns a result with Applied=true when ShouldRun and the write succeeds' { Mock Set-NovaModuleVersion {[pscustomobject]@{Applied=$true}} - $ctx = [pscustomobject]@{ProjectInfo=[pscustomobject]@{}; PreviewRelease=$false; Label='Patch'; EffectiveLabel='Patch'; PreviousVersion='1.0.0'; NewVersion='1.0.1'; CommitCount=1} + Mock Write-Progress {} + $ctx = [pscustomobject]@{ProjectInfo=[pscustomobject]@{ProjectJSON='/p/project.json'}; PreviewRelease=$false; Label='Patch'; EffectiveLabel='Patch'; PreviousVersion='1.0.0'; NewVersion='1.0.1'; CommitCount=1} $r = Invoke-NovaVersionUpdateWorkflow -WorkflowContext $ctx -ShouldRun $r.Applied | Should -BeTrue + Should -Invoke Write-Progress -Times 1 -ParameterFilter { $Status -eq 'Writing version 1.0.1 to project.json' } + Should -Invoke Write-Progress -Times 1 -ParameterFilter { $Completed } } + It 'returns a result with Applied=false in WhatIf mode without invoking the writer' { Mock Set-NovaModuleVersion {throw 'should not be called'} $ctx = [pscustomobject]@{ProjectInfo=[pscustomobject]@{}; PreviewRelease=$false; Label='Patch'; EffectiveLabel='Patch'; PreviousVersion='1.0.0'; NewVersion='1.0.1'; CommitCount=0} $r = Invoke-NovaVersionUpdateWorkflow -WorkflowContext $ctx -WhatIfEnabled $r.Applied | Should -BeFalse + $r.Previewed | Should -BeTrue } } diff --git a/tests/private/release/WriteNovaVersionUpdateResultMessages.Tests.ps1 b/tests/private/release/WriteNovaVersionUpdateResultMessages.Tests.ps1 index 6759997..1a37b43 100644 --- a/tests/private/release/WriteNovaVersionUpdateResultMessages.Tests.ps1 +++ b/tests/private/release/WriteNovaVersionUpdateResultMessages.Tests.ps1 @@ -3,6 +3,7 @@ BeforeAll { . (Join-Path $projectRoot 'src/private/release/WriteNovaVersionUpdateResultMessages.ps1') function Get-NovaVersionUpdateCiActivatedCommand {param($ProjectRoot) return $null} + function Write-Message {param($Message, $color) $script:messages += [pscustomobject]@{Text=$Message; Color=$color}} } Describe 'Invoke-NovaVersionUpdateCiActivation' { @@ -43,15 +44,76 @@ Describe 'Get-NovaVersionUpdateResultAdvisoryMessage' { } Describe 'Write-NovaVersionUpdateResultOutput' { + BeforeEach { + $script:messages = @() + } + It 'writes a warning when an advisory message is present' { - $r = [pscustomobject]@{AdvisoryMessage='watch out'; Applied=$false} + $r = [pscustomobject]@{AdvisoryMessage='watch out'; Applied=$false; Previewed=$true; NewVersion='1.2.3'; PreviousVersion='1.2.2'; Label='Patch'; EffectiveLabel='Patch'; CommitCount=2} Write-NovaVersionUpdateResultOutput -Result $r -WarningVariable w -WarningAction SilentlyContinue 6> $null $w[0].Message | Should -Be 'watch out' } - It 'writes the bumped version host message when Applied is true' { - $r = [pscustomobject]@{AdvisoryMessage=''; Applied=$true; NewVersion='1.2.3'} - $output = Write-NovaVersionUpdateResultOutput -Result $r 6>&1 | Out-String - $output | Should -Match 'Version bumped to : 1.2.3' + It 'writes a completion summary, details, and next step when Applied is true' { + $r = [pscustomobject]@{ + AdvisoryMessage = '' + Applied = $true + Previewed = $false + Cancelled = $false + NewVersion = '1.2.3' + PreviousVersion = '1.2.2' + Label = 'Patch' + EffectiveLabel = 'Patch' + CommitCount = 2 + ProjectFile = '/p/project.json' + } + Write-NovaVersionUpdateResultOutput -Result $r + $script:messages[0].Text | Should -Be 'Updated project version to 1.2.3' + $script:messages[0].Color | Should -Be 'Green' + $script:messages.Text | Should -Contain 'Version file: /p/project.json' + $script:messages.Text | Should -Contain 'Previous version: 1.2.2' + $script:messages.Text | Should -Contain 'New version: 1.2.3' + $script:messages.Text | Should -Contain 'Release label: Patch' + $script:messages.Text | Should -Contain 'Commits considered: 2' + $script:messages.Text | Should -Contain 'Invoke-NovaBuild' + } + + It 'writes a preview summary and next step when WhatIf created the result' { + $r = [pscustomobject]@{ + AdvisoryMessage = '' + Applied = $false + Previewed = $true + Cancelled = $false + NewVersion = '0.2.0' + PreviousVersion = '0.1.0' + Label = 'Major' + EffectiveLabel = 'Minor' + CommitCount = 34 + Target = 'project.json' + } + Write-NovaVersionUpdateResultOutput -Result $r + $script:messages[0].Text | Should -Be 'Version update plan ready -> 0.2.0' + $script:messages.Text | Should -Contain 'Version file: project.json' + $script:messages.Text | Should -Contain 'Detected release label: Major' + $script:messages.Text | Should -Contain 'Applied release label: Minor' + $script:messages.Text | Should -Contain 'Run Update-NovaModuleVersion without -WhatIf when you are ready to apply the version change.' + } + + It 'writes a cancellation summary when the command does not proceed past confirmation' { + $r = [pscustomobject]@{ + AdvisoryMessage = '' + Applied = $false + Previewed = $false + Cancelled = $true + NewVersion = '1.2.3' + PreviousVersion = '1.2.2' + Label = 'Patch' + EffectiveLabel = 'Patch' + CommitCount = 2 + } + Write-NovaVersionUpdateResultOutput -Result $r + $script:messages[0].Text | Should -Be 'Version update cancelled before changing project.json.' + $script:messages[0].Color | Should -Be 'Blue' + $script:messages.Text | Should -Contain 'Run Update-NovaModuleVersion again when you are ready to write the new version to project.json.' } } diff --git a/tests/private/scaffold/InvokeNovaAgenticCopilotScaffoldWorkflow.TestSupport.ps1 b/tests/private/scaffold/InvokeNovaAgenticCopilotScaffoldWorkflow.TestSupport.ps1 index 88c5efc..f2f4364 100644 --- a/tests/private/scaffold/InvokeNovaAgenticCopilotScaffoldWorkflow.TestSupport.ps1 +++ b/tests/private/scaffold/InvokeNovaAgenticCopilotScaffoldWorkflow.TestSupport.ps1 @@ -7,6 +7,10 @@ function Initialize-NovaModuleAgenticCopilotScaffold { ) } +function Write-Message { + param([string]$Message, [string]$color) +} + function Stop-NovaOperation { param([string]$Message, [string]$ErrorId, [System.Management.Automation.ErrorCategory]$Category, $TargetObject) $exception = [System.Exception]::new($Message) diff --git a/tests/private/scaffold/InvokeNovaAgenticCopilotScaffoldWorkflow.Tests.ps1 b/tests/private/scaffold/InvokeNovaAgenticCopilotScaffoldWorkflow.Tests.ps1 index d175902..8b83b75 100644 --- a/tests/private/scaffold/InvokeNovaAgenticCopilotScaffoldWorkflow.Tests.ps1 +++ b/tests/private/scaffold/InvokeNovaAgenticCopilotScaffoldWorkflow.Tests.ps1 @@ -71,7 +71,7 @@ Describe 'Confirm-NovaAgenticCopilotScaffoldWarning' { ManagedOverwritePathList = @('.github/agents/') AddOnlyPathList = @('README.md') }) - } | Should -Throw -ErrorId 'Nova.Workflow.AgenticCopilotScaffoldCancelled' + } | Should -Throw '*Agentic Copilot scaffold apply cancelled for /repo.*choose Yes when you are ready to overwrite the managed scaffold paths.*' } } @@ -80,6 +80,7 @@ Describe 'Invoke-NovaAgenticCopilotScaffoldWorkflow' { Mock Confirm-NovaAgenticCopilotScaffoldWarning {} Mock Initialize-NovaModuleAgenticCopilotScaffold {} Mock Write-Message {} + Mock Write-Progress {} } It 'returns without applying when ShouldRun is false' { @@ -108,6 +109,13 @@ Describe 'Invoke-NovaAgenticCopilotScaffoldWorkflow' { Assert-MockCalled Confirm-NovaAgenticCopilotScaffoldWarning -Times 1 Assert-MockCalled Initialize-NovaModuleAgenticCopilotScaffold -Times 1 - Assert-MockCalled Write-Message -Times 1 + Assert-MockCalled Write-Progress -Times 3 + Assert-MockCalled Write-Message -Times 5 + Assert-MockCalled Write-Message -Times 1 -ParameterFilter { + $Message -eq 'Agentic Copilot scaffold applied to Demo' -and $color -eq 'Green' + } + Assert-MockCalled Write-Message -Times 1 -ParameterFilter { + $Message -eq 'Test-NovaBuild' + } } } diff --git a/tests/private/update/GetNovaModuleSelfUpdateWorkflowContext.Tests.ps1 b/tests/private/update/GetNovaModuleSelfUpdateWorkflowContext.Tests.ps1 index bdd790e..fe4e647 100644 --- a/tests/private/update/GetNovaModuleSelfUpdateWorkflowContext.Tests.ps1 +++ b/tests/private/update/GetNovaModuleSelfUpdateWorkflowContext.Tests.ps1 @@ -28,6 +28,17 @@ Describe 'Get-NovaModuleSelfUpdateWorkflowContext' { $ctx.Action | Should -Be 'Update NovaModuleTools to prerelease version 3.0.0-rc1' } + It 'carries forwarded workflow parameters into the context' { + Mock Read-NovaUpdateNotificationPreference {[pscustomobject]@{PrereleaseNotificationsEnabled=$false}} + Mock Get-NovaInstalledModuleVersionInfo {[pscustomobject]@{Version='1.0.0'}} + Mock Invoke-NovaModuleUpdateLookup {[pscustomobject]@{Version='2.0.0'}} + Mock Get-NovaModuleSelfUpdatePlan {[pscustomobject]@{TargetVersion='2.0.0'; IsPrereleaseTarget=$false}} + + $ctx = Get-NovaModuleSelfUpdateWorkflowContext -WorkflowParams @{WhatIf = $true} + + $ctx.WorkflowParams.WhatIf | Should -BeTrue + } + It 'throws when no lookup result is available' { Mock Read-NovaUpdateNotificationPreference {[pscustomobject]@{PrereleaseNotificationsEnabled=$false}} Mock Get-NovaInstalledModuleVersionInfo {[pscustomobject]@{Version='1.0.0'}} diff --git a/tests/private/update/GetNovaUpdateNotificationPreferenceChangeContext.Tests.ps1 b/tests/private/update/GetNovaUpdateNotificationPreferenceChangeContext.Tests.ps1 index 4657f2c..2a3d4dd 100644 --- a/tests/private/update/GetNovaUpdateNotificationPreferenceChangeContext.Tests.ps1 +++ b/tests/private/update/GetNovaUpdateNotificationPreferenceChangeContext.Tests.ps1 @@ -16,21 +16,23 @@ Describe 'Get-NovaUpdateNotificationPreferenceChangeContext' { } It 'returns an enable context when -EnablePrereleaseNotifications is set' { - $context = Get-NovaUpdateNotificationPreferenceChangeContext -EnablePrereleaseNotifications + $context = Get-NovaUpdateNotificationPreferenceChangeContext -EnablePrereleaseNotifications -WorkflowParams @{WhatIf = $true} $context.PrereleaseNotificationsEnabled | Should -BeTrue - $context.Action | Should -Be 'Enable prerelease update notifications' + $context.Action | Should -Be 'Enable prerelease self-update notifications' $context.Target | Should -Not -BeNullOrEmpty + $context.WorkflowParams | Should -BeOfType Hashtable + $context.WorkflowParams.WhatIf | Should -BeTrue } It 'returns a disable context when -DisablePrereleaseNotifications is set' { $context = Get-NovaUpdateNotificationPreferenceChangeContext -DisablePrereleaseNotifications $context.PrereleaseNotificationsEnabled | Should -BeFalse - $context.Action | Should -Be 'Disable prerelease update notifications' + $context.Action | Should -Be 'Disable prerelease self-update notifications' } It 'throws via Stop-NovaOperation when neither switch is provided' { - {Get-NovaUpdateNotificationPreferenceChangeContext} | Should -Throw '*Specify either*' + {Get-NovaUpdateNotificationPreferenceChangeContext} | Should -Throw '*Example: Set-NovaUpdateNotificationPreference -DisablePrereleaseNotifications*' } } diff --git a/tests/private/update/InvokeNovaModuleSelfUpdateWorkflow.TestSupport.ps1 b/tests/private/update/InvokeNovaModuleSelfUpdateWorkflow.TestSupport.ps1 index a48d5a1..f9e1943 100644 --- a/tests/private/update/InvokeNovaModuleSelfUpdateWorkflow.TestSupport.ps1 +++ b/tests/private/update/InvokeNovaModuleSelfUpdateWorkflow.TestSupport.ps1 @@ -1,3 +1,5 @@ function Invoke-NovaModuleSelfUpdate {param([string]$ModuleName,[switch]$AllowPrerelease)} function Get-NovaModuleReleaseNotesUri {'https://example.com/notes'} function Stop-NovaOperation {param([string]$Message,[string]$ErrorId,$Category,$TargetObject) throw [System.Management.Automation.ErrorRecord]::new([System.Exception]::new($Message),$ErrorId,$Category,$TargetObject)} +function Write-Message {param([string]$Text, [string]$color)} +function Write-Progress {param([string]$Activity, [string]$Status, [int]$PercentComplete, [switch]$Completed)} diff --git a/tests/private/update/InvokeNovaModuleSelfUpdateWorkflow.Tests.ps1 b/tests/private/update/InvokeNovaModuleSelfUpdateWorkflow.Tests.ps1 index a6581e0..8cf42bc 100644 --- a/tests/private/update/InvokeNovaModuleSelfUpdateWorkflow.Tests.ps1 +++ b/tests/private/update/InvokeNovaModuleSelfUpdateWorkflow.Tests.ps1 @@ -30,26 +30,126 @@ Describe 'Invoke-NovaModuleSelfUpdateOrStop' { } It 'wraps update failures in a Nova.Dependency.ModuleSelfUpdateFailed error' { Mock Invoke-NovaModuleSelfUpdate {throw 'gallery offline'} - { Invoke-NovaModuleSelfUpdateOrStop -Plan ([pscustomobject]@{ModuleName='Nova'; UsedAllowPrerelease=$false}) } | Should -Throw -ErrorId 'Nova.Dependency.ModuleSelfUpdateFailed' + $thrown = $null + try { + Invoke-NovaModuleSelfUpdateOrStop -Plan ([pscustomobject]@{ModuleName='Nova'; UsedAllowPrerelease=$false}) + } catch { + $thrown = $_ + } + + $thrown | Should -Not -BeNullOrEmpty + $thrown.FullyQualifiedErrorId | Should -Be 'Nova.Dependency.ModuleSelfUpdateFailed' + $thrown.Exception.Message | Should -Be 'NovaModuleTools self-update failed: gallery offline Confirm that the PowerShell Gallery is reachable and that this session can update installed modules, then rerun Update-NovaModuleTool.' + } +} + +Describe 'Get-NovaModuleSelfUpdateWorkflowUpdateStatus' { + It 'describes prerelease installs explicitly' { + $workflowContext = [pscustomobject]@{ + Plan = [pscustomobject]@{ + IsPrereleaseTarget = $true + TargetVersion = '1.2.0-preview1' + } + } + + Get-NovaModuleSelfUpdateWorkflowUpdateStatus -WorkflowContext $workflowContext | Should -Be 'Installing prerelease version 1.2.0-preview1' + } +} + +Describe 'Get-NovaModuleSelfUpdateWorkflowRepositoryLine' { + It 'returns null when the repository name is blank' { + Get-NovaModuleSelfUpdateWorkflowRepositoryLine -Result ([pscustomobject]@{LookupRepository = ''}) | Should -BeNullOrEmpty } } Describe 'Invoke-NovaModuleSelfUpdateWorkflow' { + BeforeEach { + Mock Write-Message {} + Mock Write-Progress {} + } + It 'returns plan with null release notes when no update is available' { Mock Invoke-NovaModuleSelfUpdate {} Mock Get-NovaModuleReleaseNotesUri {throw 'should not run'} - $plan = [pscustomobject]@{UpdateAvailable=$false; ModuleName='Nova'; UsedAllowPrerelease=$false; Updated=$false; ReleaseNotesUri=$null} - $result = Invoke-NovaModuleSelfUpdateWorkflow -WorkflowContext ([pscustomobject]@{Plan=$plan}) + $plan = [pscustomobject]@{UpdateAvailable=$false; ModuleName='Nova'; CurrentVersion='1.0.0'; UsedAllowPrerelease=$false; Updated=$false; Cancelled=$false; LookupRepository='PSGallery'; ReleaseNotesUri=$null} + $result = Invoke-NovaModuleSelfUpdateWorkflow -WorkflowContext ([pscustomobject]@{Plan=$plan; WorkflowParams=@{}}) -ShouldRun:$false $result.Updated | Should -BeFalse $result.ReleaseNotesUri | Should -BeNull + Assert-MockCalled Write-Message -Times 1 -ParameterFilter {$Text -eq 'NovaModuleTools is already up to date.' -and $color -eq 'Green'} + Assert-MockCalled Write-Message -Times 1 -ParameterFilter {$Text -eq 'Current version: 1.0.0'} + Should -Invoke Invoke-NovaModuleSelfUpdate -Times 0 + } + + It 'writes a preview summary in WhatIf mode without updating the module' { + Mock Invoke-NovaModuleSelfUpdate {} + Mock Get-NovaModuleReleaseNotesUri {throw 'should not run'} + $plan = [pscustomobject]@{ + UpdateAvailable = $true + IsPrereleaseTarget = $false + ModuleName = 'NovaModuleTools' + CurrentVersion = '1.0.0' + TargetVersion = '1.1.0' + UsedAllowPrerelease = $false + Updated = $false + Cancelled = $false + LookupRepository = 'PSGallery' + ReleaseNotesUri = $null + } + + $result = Invoke-NovaModuleSelfUpdateWorkflow -WorkflowContext ([pscustomobject]@{Plan=$plan; WorkflowParams=@{WhatIf=$true}}) -ShouldRun:$false + + $result.ReleaseNotesUri | Should -BeNull + Assert-MockCalled Write-Message -Times 1 -ParameterFilter {$Text -eq 'Self-update plan ready for NovaModuleTools' -and $color -eq 'Green'} + Assert-MockCalled Write-Message -Times 1 -ParameterFilter {$Text -eq 'Target version: 1.1.0'} + Assert-MockCalled Write-Message -Times 1 -ParameterFilter {$Text -eq 'Repository: PSGallery'} + Assert-MockCalled Write-Message -Times 1 -ParameterFilter {$Text -eq 'Run Update-NovaModuleTool without -WhatIf when you are ready to install version 1.1.0.'} + Should -Invoke Invoke-NovaModuleSelfUpdate -Times 0 + } + + It 'writes a cancellation summary when the update is cancelled before execution' { + Mock Invoke-NovaModuleSelfUpdate {} + $plan = [pscustomobject]@{ + UpdateAvailable = $true + IsPrereleaseTarget = $false + ModuleName = 'NovaModuleTools' + CurrentVersion = '1.0.0' + TargetVersion = '1.1.0' + UsedAllowPrerelease = $false + Updated = $false + Cancelled = $true + LookupRepository = 'PSGallery' + ReleaseNotesUri = $null + } + + $result = Invoke-NovaModuleSelfUpdateWorkflow -WorkflowContext ([pscustomobject]@{Plan=$plan; WorkflowParams=@{}}) -ShouldRun:$false + + $result.Cancelled | Should -BeTrue + Assert-MockCalled Write-Message -Times 1 -ParameterFilter {$Text -eq 'Self-update cancelled for NovaModuleTools.' -and $color -eq 'Blue'} + Should -Invoke Invoke-NovaModuleSelfUpdate -Times 0 } - It 'performs update, sets Updated, and resolves release notes when update is available' { + It 'performs update, sets Updated, resolves release notes, and prints the next step when update is available' { Mock Invoke-NovaModuleSelfUpdate {} Mock Get-NovaModuleReleaseNotesUri {'https://example.com/n'} - $plan = [pscustomobject]@{UpdateAvailable=$true; ModuleName='Nova'; UsedAllowPrerelease=$false; Updated=$false; ReleaseNotesUri=$null} - $result = Invoke-NovaModuleSelfUpdateWorkflow -WorkflowContext ([pscustomobject]@{Plan=$plan}) + $plan = [pscustomobject]@{ + UpdateAvailable = $true + IsPrereleaseTarget = $false + ModuleName = 'NovaModuleTools' + CurrentVersion = '1.0.0' + TargetVersion = '1.1.0' + UsedAllowPrerelease = $false + Updated = $false + Cancelled = $false + LookupRepository = 'PSGallery' + ReleaseNotesUri = $null + } + + $result = Invoke-NovaModuleSelfUpdateWorkflow -WorkflowContext ([pscustomobject]@{Plan=$plan; WorkflowParams=@{}}) -ShouldRun + $result.Updated | Should -BeTrue $result.ReleaseNotesUri | Should -Be 'https://example.com/n' + Assert-MockCalled Write-Message -Times 1 -ParameterFilter {$Text -eq 'Updated NovaModuleTools to version 1.1.0.' -and $color -eq 'Green'} + Assert-MockCalled Write-Message -Times 1 -ParameterFilter {$Text -eq 'Get-NovaProjectInfo -Installed'} + Assert-MockCalled Write-Progress -Times 3 } } diff --git a/tests/private/update/InvokeNovaUpdateNotificationPreferenceChange.Tests.ps1 b/tests/private/update/InvokeNovaUpdateNotificationPreferenceChange.Tests.ps1 index e56a966..be4a808 100644 --- a/tests/private/update/InvokeNovaUpdateNotificationPreferenceChange.Tests.ps1 +++ b/tests/private/update/InvokeNovaUpdateNotificationPreferenceChange.Tests.ps1 @@ -4,20 +4,81 @@ BeforeAll { function Write-NovaUpdateNotificationPreference {param([bool]$PrereleaseNotificationsEnabled)} function Get-NovaUpdateNotificationPreferenceStatus {return [pscustomobject]@{PrereleaseNotificationsEnabled = $true; SettingsPath = '/tmp/x.json'}} + function Write-Message {param([string]$Text, [string]$color)} +} + +Describe 'Test-NovaUpdateNotificationPreferenceChangeWhatIfEnabled' { + It 'returns true when WorkflowParams.WhatIf is enabled' { + $workflowContext = [pscustomobject]@{WorkflowParams = @{WhatIf = $true}} + Test-NovaUpdateNotificationPreferenceChangeWhatIfEnabled -WorkflowContext $workflowContext | Should -BeTrue + } + + It 'returns false when WorkflowParams.WhatIf is not enabled' { + $workflowContext = [pscustomobject]@{WorkflowParams = @{}} + Test-NovaUpdateNotificationPreferenceChangeWhatIfEnabled -WorkflowContext $workflowContext | Should -BeFalse + } +} + +Describe 'Get-NovaUpdateNotificationPreferenceChangeCommandLine' { + It 'returns the disable command line when prerelease notifications are being turned off' { + $workflowContext = [pscustomobject]@{PrereleaseNotificationsEnabled = $false} + Get-NovaUpdateNotificationPreferenceChangeCommandLine -WorkflowContext $workflowContext | Should -Be 'Set-NovaUpdateNotificationPreference -DisablePrereleaseNotifications' + } } Describe 'Invoke-NovaUpdateNotificationPreferenceChange' { + BeforeEach { + Mock Write-Message {} + } + It 'writes the requested preference and returns the resulting status' { Mock Write-NovaUpdateNotificationPreference {} Mock Get-NovaUpdateNotificationPreferenceStatus {return [pscustomobject]@{PrereleaseNotificationsEnabled = $false; SettingsPath = '/tmp/x.json'}} - $workflowContext = [pscustomobject]@{PrereleaseNotificationsEnabled = $false; Action = 'Disable prerelease update notifications'; Target = '/tmp/x.json'} - $status = Invoke-NovaUpdateNotificationPreferenceChange -WorkflowContext $workflowContext + $workflowContext = [pscustomobject]@{ + PrereleaseNotificationsEnabled = $false + Action = 'Disable prerelease self-update notifications' + Target = '/tmp/x.json' + WorkflowParams = @{} + } + $status = Invoke-NovaUpdateNotificationPreferenceChange -WorkflowContext $workflowContext -ShouldRun Assert-MockCalled Write-NovaUpdateNotificationPreference -Times 1 -ParameterFilter { $PrereleaseNotificationsEnabled -eq $false } Assert-MockCalled Get-NovaUpdateNotificationPreferenceStatus -Times 1 + Assert-MockCalled Write-Message -Times 4 + Assert-MockCalled Write-Message -Times 1 -ParameterFilter { + $Text -eq 'Prerelease self-updates are now disabled.' -and $color -eq 'Green' + } + Assert-MockCalled Write-Message -Times 1 -ParameterFilter { + $Text -eq 'Get-NovaUpdateNotificationPreference' + } $status.PrereleaseNotificationsEnabled | Should -BeFalse } + + It 'writes a preview summary in WhatIf mode without storing the preference' { + Mock Write-NovaUpdateNotificationPreference {} + Mock Get-NovaUpdateNotificationPreferenceStatus {} + + $workflowContext = [pscustomobject]@{ + PrereleaseNotificationsEnabled = $true + Action = 'Enable prerelease self-update notifications' + Target = '/tmp/x.json' + WorkflowParams = @{WhatIf = $true} + } + + $status = Invoke-NovaUpdateNotificationPreferenceChange -WorkflowContext $workflowContext + + Assert-MockCalled Write-NovaUpdateNotificationPreference -Times 0 + Assert-MockCalled Get-NovaUpdateNotificationPreferenceStatus -Times 0 + Assert-MockCalled Write-Message -Times 4 + Assert-MockCalled Write-Message -Times 1 -ParameterFilter { + $Text -eq 'Notification preference plan ready: prerelease self-updates enabled' -and $color -eq 'Green' + } + Assert-MockCalled Write-Message -Times 1 -ParameterFilter { + $Text -eq 'Run Set-NovaUpdateNotificationPreference -EnablePrereleaseNotifications without -WhatIf when you are ready to store the preference.' + } + $status | Should -BeNullOrEmpty + } } diff --git a/tests/public/InvokeNovaCli.Tests.ps1 b/tests/public/InvokeNovaCli.Tests.ps1 index 3f1258a..fa46895 100644 --- a/tests/public/InvokeNovaCli.Tests.ps1 +++ b/tests/public/InvokeNovaCli.Tests.ps1 @@ -14,6 +14,10 @@ Describe 'Invoke-NovaCli' { (Invoke-NovaCli).Command | Should -Be '--help' } + It 'defaults blank command input to --help' { + (Invoke-NovaCli '').Command | Should -Be '--help' + } + It 'forwards command and remaining arguments to the invocation context' { Invoke-NovaCli 'build' '--ci' '--override-warning' | Out-Null $script:request.Command | Should -Be 'build' diff --git a/tests/public/SetNovaUpdateNotificationPreference.TestSupport.ps1 b/tests/public/SetNovaUpdateNotificationPreference.TestSupport.ps1 new file mode 100644 index 0000000..75c295a --- /dev/null +++ b/tests/public/SetNovaUpdateNotificationPreference.TestSupport.ps1 @@ -0,0 +1,30 @@ +function Get-NovaUpdateNotificationPreferenceChangeContext { + param( + [switch]$EnablePrereleaseNotifications, + [switch]$DisablePrereleaseNotifications, + [hashtable]$WorkflowParams + ) + + $script:ctxArgs = @{ + Enable = [bool]$EnablePrereleaseNotifications + Disable = [bool]$DisablePrereleaseNotifications + WhatIf = $WorkflowParams.WhatIf + } + + return [pscustomobject]@{ + Target = 'nm' + Action = 'Set' + WorkflowParams = $WorkflowParams + } +} + +function Invoke-NovaUpdateNotificationPreferenceChange { + param( + $WorkflowContext, + [switch]$ShouldRun + ) + + $script:invoked = $true + $script:shouldRun = [bool]$ShouldRun + return [pscustomobject]@{Changed = $true} +} diff --git a/tests/public/SetNovaUpdateNotificationPreference.Tests.ps1 b/tests/public/SetNovaUpdateNotificationPreference.Tests.ps1 index fc1c324..3257847 100644 --- a/tests/public/SetNovaUpdateNotificationPreference.Tests.ps1 +++ b/tests/public/SetNovaUpdateNotificationPreference.Tests.ps1 @@ -1,24 +1,17 @@ BeforeAll { $projectRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) . (Join-Path $projectRoot 'src/public/SetNovaUpdateNotificationPreference.ps1') - - function Get-NovaUpdateNotificationPreferenceChangeContext {param([switch]$EnablePrereleaseNotifications, [switch]$DisablePrereleaseNotifications) - $script:ctxArgs = @{Enable=[bool]$EnablePrereleaseNotifications; Disable=[bool]$DisablePrereleaseNotifications} - return [pscustomobject]@{Target='nm'; Action='Set'} - } - function Invoke-NovaUpdateNotificationPreferenceChange {param($WorkflowContext) - $script:invoked = $true - return [pscustomobject]@{Changed=$true} - } + . (Join-Path $PSScriptRoot 'SetNovaUpdateNotificationPreference.TestSupport.ps1') } Describe 'Set-NovaUpdateNotificationPreference' { - BeforeEach {$script:ctxArgs = $null; $script:invoked = $false} + BeforeEach {$script:ctxArgs = $null; $script:invoked = $false; $script:shouldRun = $null} It 'forwards -EnablePrereleaseNotifications to the context and invokes the workflow' { $result = Set-NovaUpdateNotificationPreference -EnablePrereleaseNotifications $script:ctxArgs.Enable | Should -BeTrue $script:invoked | Should -BeTrue + $script:shouldRun | Should -BeTrue $result.Changed | Should -BeTrue } @@ -27,8 +20,10 @@ Describe 'Set-NovaUpdateNotificationPreference' { $script:ctxArgs.Disable | Should -BeTrue } - It 'returns without invoking the workflow when -WhatIf is set' { + It 'invokes the workflow with ShouldRun=$false when -WhatIf is set' { Set-NovaUpdateNotificationPreference -EnablePrereleaseNotifications -WhatIf - $script:invoked | Should -BeFalse + $script:invoked | Should -BeTrue + $script:shouldRun | Should -BeFalse + $script:ctxArgs.WhatIf | Should -BeTrue } } diff --git a/tests/public/UpdateNovaModuleTools.TestSupport.ps1 b/tests/public/UpdateNovaModuleTools.TestSupport.ps1 index eed9dcb..a72ffaf 100644 --- a/tests/public/UpdateNovaModuleTools.TestSupport.ps1 +++ b/tests/public/UpdateNovaModuleTools.TestSupport.ps1 @@ -1,8 +1,13 @@ -function Get-NovaModuleSelfUpdateWorkflowContext {return [pscustomobject]@{Plan=$script:plan; Action='Update'}} -function Complete-NovaModuleSelfUpdateResult {param($Plan, $ReleaseNotesUri) return [pscustomobject]@{Plan=$Plan; ReleaseNotesUri=$ReleaseNotesUri; Completed=$true}} -function Confirm-NovaPrereleaseModuleUpdate {param($Cmdlet, $CurrentVersion, $TargetVersion) return $script:confirmResult} -function Invoke-NovaModuleSelfUpdateWorkflow {param($WorkflowContext) +function Get-NovaShouldProcessForwardingParameter {param([switch]$WhatIfEnabled) return @{WhatIf = [bool]$WhatIfEnabled}} +function Get-NovaModuleSelfUpdateWorkflowContext {param([hashtable]$WorkflowParams) $script:workflowParams = $WorkflowParams; return [pscustomobject]@{Plan=$script:plan; Action='Update'; WorkflowParams=$WorkflowParams}} +function Confirm-NovaPrereleaseModuleUpdate {param($Cmdlet, $CurrentVersion, $TargetVersion) $script:confirmCallCount += 1; return $script:confirmResult} +function Invoke-NovaModuleSelfUpdateWorkflow {param($WorkflowContext, [switch]$ShouldRun) $script:invoked = $true - return [pscustomobject]@{ReleaseNotesUri='https://x/rel'} + $script:workflowContext = $WorkflowContext + $script:shouldRun = [bool]$ShouldRun + $releaseNotesUri = if ($ShouldRun) {'https://x/rel'} else {$null} + $WorkflowContext.Plan | Add-Member -NotePropertyName 'ReleaseNotesUri' -NotePropertyValue $releaseNotesUri -Force + + return $WorkflowContext.Plan } function Write-NovaModuleReleaseNotesLink {param($ReleaseNotesUri) $script:notesUri = $ReleaseNotesUri} diff --git a/tests/public/UpdateNovaModuleTools.Tests.ps1 b/tests/public/UpdateNovaModuleTools.Tests.ps1 index 9d0b5ae..4311cf3 100644 --- a/tests/public/UpdateNovaModuleTools.Tests.ps1 +++ b/tests/public/UpdateNovaModuleTools.Tests.ps1 @@ -7,30 +7,67 @@ BeforeAll { Describe 'Update-NovaModuleTool' { BeforeEach { - $script:plan = [pscustomobject]@{UpdateAvailable=$true; IsPrereleaseTarget=$false; ModuleName='NovaModuleTools'; CurrentVersion='1.0.0'; TargetVersion='1.1.0'; Cancelled=$false} + $script:plan = [pscustomobject]@{ + UpdateAvailable = $true + IsPrereleaseTarget = $false + ModuleName = 'NovaModuleTools' + CurrentVersion = '1.0.0' + TargetVersion = '1.1.0' + Cancelled = $false + LookupRepository = 'PSGallery' + } $script:confirmResult = $true - $script:invoked = $false; $script:notesUri = $null + $script:confirmCallCount = 0 + $script:invoked = $false + $script:notesUri = $null + $script:shouldRun = $null + $script:workflowContext = $null + $script:workflowParams = $null } - It 'short-circuits with a completion result when no update is available' { + It 'invokes the workflow with ShouldRun=$false when no update is available' { $script:plan.UpdateAvailable = $false + $result = Update-NovaModuleTool - $script:invoked | Should -BeFalse - $result.Completed | Should -BeTrue + + $script:invoked | Should -BeTrue + $script:shouldRun | Should -BeFalse + $result.UpdateAvailable | Should -BeFalse + $script:notesUri | Should -BeNull } - It 'cancels when a prerelease target is not confirmed' { + It 'cancels when a prerelease target is not confirmed and still invokes the workflow for the summary' { $script:plan.IsPrereleaseTarget = $true $script:confirmResult = $false + $result = Update-NovaModuleTool - $script:invoked | Should -BeFalse - $result.Plan.Cancelled | Should -BeTrue + + $script:invoked | Should -BeTrue + $script:confirmCallCount | Should -Be 1 + $script:shouldRun | Should -BeFalse + $result.Cancelled | Should -BeTrue + $script:notesUri | Should -BeNull } It 'runs the self-update workflow and writes the release notes link' { $result = Update-NovaModuleTool + $script:invoked | Should -BeTrue + $script:shouldRun | Should -BeTrue + $script:workflowParams.WhatIf | Should -BeFalse $script:notesUri | Should -Be 'https://x/rel' $result.ReleaseNotesUri | Should -Be 'https://x/rel' } + + It 'invokes the workflow with ShouldRun=$false in WhatIf mode without prompting for prerelease confirmation' { + $script:plan.IsPrereleaseTarget = $true + + Update-NovaModuleTool -WhatIf | Out-Null + + $script:invoked | Should -BeTrue + $script:shouldRun | Should -BeFalse + $script:workflowContext.WorkflowParams.WhatIf | Should -BeTrue + $script:confirmCallCount | Should -Be 0 + $script:notesUri | Should -BeNull + } } diff --git a/tests/public/UpdateNovaModuleVersion.TestSupport.ps1 b/tests/public/UpdateNovaModuleVersion.TestSupport.ps1 index f134232..7ee5488 100644 --- a/tests/public/UpdateNovaModuleVersion.TestSupport.ps1 +++ b/tests/public/UpdateNovaModuleVersion.TestSupport.ps1 @@ -7,6 +7,7 @@ function Get-NovaVersionUpdateWorkflowContext {param($ProjectRoot, [switch]$Prev } function Invoke-NovaVersionUpdateWorkflow {param($WorkflowContext, [switch]$ShouldRun, [switch]$WhatIfEnabled) $script:invoked = $true + $script:workflowArgs = @{ShouldRun=[bool]$ShouldRun; WhatIfEnabled=[bool]$WhatIfEnabled} return $script:workflowResult } function Write-NovaVersionUpdateResultOutput {param($Result) $script:outputResult = $Result} diff --git a/tests/public/UpdateNovaModuleVersion.Tests.ps1 b/tests/public/UpdateNovaModuleVersion.Tests.ps1 index 8879a2d..aeec709 100644 --- a/tests/public/UpdateNovaModuleVersion.Tests.ps1 +++ b/tests/public/UpdateNovaModuleVersion.Tests.ps1 @@ -9,6 +9,7 @@ Describe 'Update-NovaModuleVersion' { BeforeEach { $script:ciActivation = [pscustomobject]@{ShouldReturn=$false; Result=$null} $script:ctxArgs = $null; $script:invoked = $false + $script:workflowArgs = $null $script:workflowResult = [pscustomobject]@{NewVersion='1.1.0'} $script:outputResult = $null } @@ -27,6 +28,12 @@ Describe 'Update-NovaModuleVersion' { $result.NewVersion | Should -Be '1.1.0' } + It 'forwards the WhatIf preview flags to the workflow' { + Update-NovaModuleVersion -Path . -WhatIf | Out-Null + $script:workflowArgs.ShouldRun | Should -BeFalse + $script:workflowArgs.WhatIfEnabled | Should -BeTrue + } + It 'returns nothing when the workflow returns null' { $script:workflowResult = $null Update-NovaModuleVersion -Path . | Should -BeNullOrEmpty