diff --git a/.vscode/launch.json b/.vscode/launch.json index c1b568a..b0e497f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,6 +4,30 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { + "name": "Run Build and Debug (Temp Console)", + "type": "PowerShell", + "request": "launch", + "script": "${cwd}/build.ps1", + "args": [ + "-Task Test", + "-Verbose" + ], + "cwd": "${workspaceFolder}", + "createTemporaryIntegratedConsole": true + }, + { + "name": "Run Build and Debug", + "type": "PowerShell", + "request": "launch", + "script": "${cwd}/build.ps1", + "args": [ + "-Task Test", + "-Verbose" + ], + "cwd": "${workspaceFolder}", + "createTemporaryIntegratedConsole": true + }, { "name": "PowerShell: Interactive Session", "type": "PowerShell", @@ -11,4 +35,4 @@ "cwd": "" } ] -} \ No newline at end of file +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 227163e..472eaa5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,5 +6,6 @@ "powershell.codeFormatting.addWhitespaceAroundPipe": true, "powershell.codeFormatting.useCorrectCasing": true, "powershell.codeFormatting.newLineAfterOpenBrace": true, - "powershell.codeFormatting.alignPropertyValuePairs": true + "powershell.codeFormatting.alignPropertyValuePairs": true, + "powershell.scriptAnalysis.settingsPath": "tests/ScriptAnalyzerSettings.psd1" } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 106a76c..1b1505a 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -2,13 +2,17 @@ // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format "version": "2.0.0", - // Start PowerShell (pwsh on *nix) "windows": { "options": { "shell": { - "executable": "powershell.exe", - "args": [ "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command" ] + "executable": "pwsh.exe", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command" + ] } } }, @@ -16,7 +20,10 @@ "options": { "shell": { "executable": "/usr/bin/pwsh", - "args": [ "-NoProfile", "-Command" ] + "args": [ + "-NoProfile", + "-Command" + ] } } }, @@ -24,11 +31,13 @@ "options": { "shell": { "executable": "/usr/local/bin/pwsh", - "args": [ "-NoProfile", "-Command" ] + "args": [ + "-NoProfile", + "-Command" + ] } } }, - "tasks": [ { "label": "Clean", diff --git a/CHANGELOG.md b/CHANGELOG.md index 79e8374..354abdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [0.7.1] 2025-03-31 Bugfix + +### Fixed + +- Remove extra item from `New-MarkdownHelp` splat that would result in a failure + when using `$PSBPreference.Docs.Overwrite = $true` +- Clean up some failing Script Analyzer settings, including moving the file. + ## [0.7.0] 2025-03-31 ### Changed diff --git a/PowerShellBuild/PowerShellBuild.psd1 b/PowerShellBuild/PowerShellBuild.psd1 index 2f59838..d9fcae9 100644 --- a/PowerShellBuild/PowerShellBuild.psd1 +++ b/PowerShellBuild/PowerShellBuild.psd1 @@ -1,6 +1,6 @@ @{ RootModule = 'PowerShellBuild.psm1' - ModuleVersion = '0.7.0' + ModuleVersion = '0.7.1' GUID = '15431eb8-be2d-4154-b8ad-4cb68a488e3d' Author = 'Brandon Olin' CompanyName = 'Community' diff --git a/PowerShellBuild/Public/Build-PSBuildMarkdown.ps1 b/PowerShellBuild/Public/Build-PSBuildMarkdown.ps1 index 36a71d8..034fb47 100644 --- a/PowerShellBuild/Public/Build-PSBuildMarkdown.ps1 +++ b/PowerShellBuild/Public/Build-PSBuildMarkdown.ps1 @@ -19,7 +19,7 @@ function Build-PSBuildMarkdown { Analysis the comment-based help of the MyModule module and create markdown documents under ./docs/en-US. #> - [cmdletbinding()] + [CmdletBinding()] param( [parameter(Mandatory)] [string]$ModulePath, @@ -57,12 +57,11 @@ function Build-PSBuildMarkdown { # ErrorAction set to SilentlyContinue so this command will not overwrite an existing MD file. $newMDParams = @{ - Module = $ModuleName - Locale = $Locale + Module = $ModuleName + Locale = $Locale OutputFolder = [IO.Path]::Combine($DocsPath, $Locale) - ErrorAction = 'SilentlyContinue' - Verbose = $VerbosePreference - Force = $Overwrite + ErrorAction = 'SilentlyContinue' + Verbose = $VerbosePreference } if ($Overwrite) { $newMDParams.Add('Force', $true) diff --git a/PowerShellBuild/Public/Publish-PSBuildModule.ps1 b/PowerShellBuild/Public/Publish-PSBuildModule.ps1 index 3603c29..20a3b35 100644 --- a/PowerShellBuild/Public/Publish-PSBuildModule.ps1 +++ b/PowerShellBuild/Public/Publish-PSBuildModule.ps1 @@ -27,18 +27,23 @@ function Publish-PSBuildModule { Publish version 0.1.0 of the module at path .\Output\0.1.0\MyModule to the PSGallery repository using an API key and a PowerShell credential. #> - [cmdletbinding(DefaultParameterSetName = 'ApiKey')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSReviewUnusedParameter', + '', + Justification = 'Both Credential and NuGetApiKey are used just not via explicit variable call.' + )] + [CmdletBinding(DefaultParameterSetName = 'ApiKey')] param( [parameter(Mandatory)] [ValidateScript({ - if (-not (Test-Path -Path $_ )) { - throw 'Folder does not exist' - } - if (-not (Test-Path -Path $_ -PathType Container)) { - throw 'The Path argument must be a folder. File paths are not allowed.' - } - $true - })] + if (-not (Test-Path -Path $_ )) { + throw 'Folder does not exist' + } + if (-not (Test-Path -Path $_ -PathType Container)) { + throw 'The Path argument must be a folder. File paths are not allowed.' + } + $true + })] [System.IO.FileInfo]$Path, [parameter(Mandatory)] diff --git a/PowerShellBuild/Public/Test-PSBuildPester.ps1 b/PowerShellBuild/Public/Test-PSBuildPester.ps1 index 58ebfa4..20ab01a 100644 --- a/PowerShellBuild/Public/Test-PSBuildPester.ps1 +++ b/PowerShellBuild/Public/Test-PSBuildPester.ps1 @@ -27,11 +27,16 @@ function Test-PSBuildPester { .PARAMETER ImportModule Import module from OutDir prior to running Pester tests. .EXAMPLE - PS> Test-PSBuildPester -Path ./tests -ModuleName Mymodule -OutputPath ./out/testResults.xml + PS> Test-PSBuildPester -Path ./tests -ModuleName MyModule -OutputPath ./out/testResults.xml Run Pester tests in ./tests and save results to ./out/testResults.xml #> - [cmdletbinding()] + [CmdletBinding()] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSReviewUnusedParameter', + 'CodeCoverageThreshold', + Justification = 'Used inside a foreach method call.' + )] param( [parameter(Mandatory)] [string]$Path, @@ -76,10 +81,10 @@ function Test-PSBuildPester { Import-Module Pester -MinimumVersion 5.0.0 $configuration = [PesterConfiguration]::Default - $configuration.Output.Verbosity = 'Detailed' - $configuration.Run.PassThru = $true - $configuration.TestResult.Enabled = -not [string]::IsNullOrEmpty($OutputPath) - $configuration.TestResult.OutputPath = $OutputPath + $configuration.Output.Verbosity = 'Detailed' + $configuration.Run.PassThru = $true + $configuration.TestResult.Enabled = -not [string]::IsNullOrEmpty($OutputPath) + $configuration.TestResult.OutputPath = $OutputPath $configuration.TestResult.OutputFormat = $OutputFormat if ($CodeCoverage.IsPresent) { @@ -87,7 +92,7 @@ function Test-PSBuildPester { if ($CodeCoverageFiles.Count -gt 0) { $configuration.CodeCoverage.Path = $CodeCoverageFiles } - $configuration.CodeCoverage.OutputPath = $CodeCoverageOutputFile + $configuration.CodeCoverage.OutputPath = $CodeCoverageOutputFile $configuration.CodeCoverage.OutputFormat = $CodeCoverageOutputFileFormat } @@ -103,25 +108,25 @@ function Test-PSBuildPester { $textInfo = (Get-Culture).TextInfo [xml]$testCoverage = Get-Content $CodeCoverageOutputFile $ccReport = $testCoverage.report.counter.ForEach({ - $total = [int]$_.missed + [int]$_.covered - $perc = [Math]::Truncate([int]$_.covered / $total) - [pscustomobject]@{ - name = $textInfo.ToTitleCase($_.Type.ToLower()) - percent = $perc - } - }) + $total = [int]$_.missed + [int]$_.covered + $percent = [Math]::Truncate([int]$_.covered / $total) + [PSCustomObject]@{ + name = $textInfo.ToTitleCase($_.Type.ToLower()) + percent = $percent + } + }) $ccFailMsgs = @() $ccReport.ForEach({ - 'Type: [{0}]: {1:p}' -f $_.name, $_.percent - if ($_.percent -lt $CodeCoverageThreshold) { - $ccFailMsgs += ('Code coverage: [{0}] is [{1:p}], which is less than the threshold of [{2:p}]' -f $_.name, $_.percent, $CodeCoverageThreshold) - } - }) + 'Type: [{0}]: {1:p}' -f $_.name, $_.percent + if ($_.percent -lt $CodeCoverageThreshold) { + $ccFailMsgs += ('Code coverage: [{0}] is [{1:p}], which is less than the threshold of [{2:p}]' -f $_.name, $_.percent, $CodeCoverageThreshold) + } + }) Write-Host "`n" $ccFailMsgs.Foreach({ - Write-Error $_ - }) + Write-Error $_ + }) } else { Write-Error "Code coverage file [$CodeCoverageOutputFile] not found." } diff --git a/PowerShellBuild/ScriptAnalyzerSettings.psd1 b/PowerShellBuild/ScriptAnalyzerSettings.psd1 deleted file mode 100644 index f794cdb..0000000 --- a/PowerShellBuild/ScriptAnalyzerSettings.psd1 +++ /dev/null @@ -1,13 +0,0 @@ -@{ - ExcludeRules = @( - 'PSAvoidUsingWriteHost', - 'PSUseToExportFieldsInManifest' - 'PSUseDeclaredVarsMoreThanAssignments' - ) - Rules = @{ - # Don't trip on the task alias. It's by design - PSAvoidUsingCmdletAliases = @{ - Whitelist = @('task') - } - } -} diff --git a/README.md b/README.md index bd992f5..c55ee1f 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,11 @@ Install-Module -Name psake -RequiredVersion 4.8.0 -Repository PSGallery > [how to dot source tasks using PowerShell aliases](https://github.com/nightroman/Invoke-Build/blob/master/Tasks/Import/README.md#example-2-import-from-a-module-with-tasks) > example. +

Logo

+ ## Status - Work in progress @@ -195,3 +197,5 @@ $PSBPreference.Test.CodeCoverage.Enabled = $false [psgallery]: https://www.powershellgallery.com/packages/PowerShellBuild [license-badge]: https://img.shields.io/github/license/psake/PowerShellBuild.svg [license]: https://raw.githubusercontent.com/psake/PowerShellBuild/main/LICENSE + + diff --git a/cspell.json b/cspell.json index a8959d9..23d56cb 100644 --- a/cspell.json +++ b/cspell.json @@ -11,7 +11,9 @@ ], "words": [], "ignoreWords": [ + "BHPS", "psake", + "pscredential", "MAML" ], "import": [] diff --git a/psakeFile.ps1 b/psakeFile.ps1 index 22a24c4..4fd5b41 100644 --- a/psakeFile.ps1 +++ b/psakeFile.ps1 @@ -1,24 +1,24 @@ -properties { +Properties { $settings = . ([IO.Path]::Combine($PSScriptRoot, 'build.settings.ps1')) if ($galleryApiKey) { $settings.PSGalleryApiKey = $galleryApiKey.GetNetworkCredential().password } } -task default -depends Test +Task default -depends Test -task Init { +Task Init { "STATUS: Testing with PowerShell $($settings.PSVersion)" 'Build System Details:' Get-Item ENV:BH* } -description 'Initialize build environment' -task Test -Depends Init, Analyze, Pester -description 'Run test suite' +Task Test -depends Init, Analyze, Pester -description 'Run test suite' -task Analyze -depends Build { - $analysis = Invoke-ScriptAnalyzer -Path $settings.ModuleOutDir -Recurse -Verbose:$false -Settings ([IO.Path]::Combine($env:BHModulePath, 'ScriptAnalyzerSettings.psd1')) - $errors = $analysis | Where-Object {$_.Severity -eq 'Error'} - $warnings = $analysis | Where-Object {$_.Severity -eq 'Warning'} +Task Analyze -depends Build { + $analysis = Invoke-ScriptAnalyzer -Path $settings.ModuleOutDir -Recurse -Verbose:$false -Settings './tests/ScriptAnalyzerSettings.psd1' + $errors = $analysis | Where-Object { $_.Severity -eq 'Error' } + $warnings = $analysis | Where-Object { $_.Severity -eq 'Warning' } if (@($errors).Count -gt 0) { Write-Error -Message 'One or more Script Analyzer errors were found. Build cannot continue!' $errors | Format-Table -AutoSize @@ -30,11 +30,11 @@ task Analyze -depends Build { } } -description 'Run PSScriptAnalyzer' -task Pester -depends Build { +Task Pester -depends Build { Remove-Module $settings.ProjectName -ErrorAction SilentlyContinue -Verbose:$false $testResultsXml = [IO.Path]::Combine($settings.OutputDir, 'testResults.xml') - $testResults = Invoke-Pester -Path $settings.Tests -Output Detailed -PassThru + $testResults = Invoke-Pester -Path $settings.Tests -Output Detailed -PassThru if ($testResults.FailedCount -gt 0) { $testResults | Format-List @@ -42,13 +42,13 @@ task Pester -depends Build { } } -description 'Run Pester tests' -task Clean -depends Init { +Task Clean -depends Init { if (Test-Path -Path $settings.ModuleOutDir) { Remove-Item -Path $settings.ModuleOutDir -Recurse -Force -Verbose:$false } } -task Build -depends Init, Clean { +Task Build -depends Init, Clean { New-Item -Path $settings.ModuleOutDir -ItemType Directory -Force > $null Copy-Item -Path "$($settings.SUT)/*" -Destination $settings.ModuleOutDir -Recurse @@ -59,7 +59,7 @@ task Build -depends Init, Clean { # & .\Build\Convert-PSAke.ps1 $psakePath | Out-File -Encoding UTF8 $ibPath } -task Publish -depends Test { +Task Publish -depends Test { " Publishing version [$($settings.Manifest.ModuleVersion)] to PSGallery..." if ($settings.PSGalleryApiKey) { Publish-Module -Path $settings.ModuleOutDir -NuGetApiKey $settings.PSGalleryApiKey diff --git a/requirements.psd1 b/requirements.psd1 index 5e22bd3..190478b 100755 --- a/requirements.psd1 +++ b/requirements.psd1 @@ -4,7 +4,7 @@ } BuildHelpers = '2.0.16' Pester = @{ - MinimumVersion = '5.6.1' + MinimumVersion = '5.7.1' Parameters = @{ SkipPublisherCheck = $true } diff --git a/tests/Help.tests.ps1 b/tests/Help.tests.ps1 index 974e60a..2719468 100755 --- a/tests/Help.tests.ps1 +++ b/tests/Help.tests.ps1 @@ -1,17 +1,17 @@ # Taken with love from @juneb_get_help (https://raw.githubusercontent.com/juneb/PesterTDD/master/Module.Help.Tests.ps1) BeforeDiscovery { + function global:FilterOutCommonParams { param ($Params) - $commonParams = [System.Management.Automation.PSCmdlet]::OptionalCommonParameters + - [System.Management.Automation.PSCmdlet]::CommonParameters - $params | Where-Object { $_.Name -notin $commonParams } | Sort-Object -Property Name -Unique + $commonParameters = [System.Management.Automation.PSCmdlet]::CommonParameters + [System.Management.Automation.PSCmdlet]::OptionalCommonParameters + $params | Where-Object { $_.Name -notin $commonParameters } | Sort-Object -Property Name -Unique } - $manifest = Import-PowerShellDataFile -Path $env:BHPSModuleManifest - $outputDir = Join-Path -Path $env:BHProjectPath -ChildPath 'Output' - $outputModDir = Join-Path -Path $outputDir -ChildPath $env:BHProjectName - $outputModVerDir = Join-Path -Path $outputModDir -ChildPath $manifest.ModuleVersion + $manifest = Import-PowerShellDataFile -Path $env:BHPSModuleManifest + $outputDir = Join-Path -Path $env:BHProjectPath -ChildPath 'Output' + $outputModDir = Join-Path -Path $outputDir -ChildPath $env:BHProjectName + $outputModVerDir = Join-Path -Path $outputModDir -ChildPath $manifest.ModuleVersion $outputModVerManifest = Join-Path -Path $outputModVerDir -ChildPath "$($env:BHProjectName).psd1" # Get module commands @@ -25,36 +25,32 @@ BeforeDiscovery { if ($PSVersionTable.PSVersion.Major -lt 6) { $params.CommandType[0] += 'Workflow' } - $commands = Get-Command @params + $script:commands = Get-Command @params ## When testing help, remember that help is cached at the beginning of each session. ## To test, restart session. } -AfterAll { - Remove-Item Function:/FilterOutCommonParams -} - Describe "Test help for <_.Name>" -ForEach $commands { BeforeDiscovery { # Get command help, parameters, and links - $command = $_ - $commandHelp = Get-Help $command.Name -ErrorAction SilentlyContinue - $commandParameters = global:FilterOutCommonParams -Params $command.ParameterSets.Parameters - $commandParameterNames = $commandParameters.Name - $helpLinks = $commandHelp.relatedLinks.navigationLink.uri + $command = $_ + $commandHelp = Get-Help $command.Name -ErrorAction SilentlyContinue + $commandParameters = global:FilterOutCommonParams -Params $command.ParameterSets.Parameters + $script:commandParameterNames = $commandParameters.Name + $script:helpLinks = $commandHelp.relatedLinks.navigationLink.uri } BeforeAll { # These vars are needed in both discovery and test phases so we need to duplicate them here - $command = $_ - $commandName = $_.Name - $commandHelp = Get-Help $command.Name -ErrorAction SilentlyContinue - $commandParameters = global:FilterOutCommonParams -Params $command.ParameterSets.Parameters - $commandParameterNames = $commandParameters.Name - $helpParameters = global:FilterOutCommonParams -Params $commandHelp.Parameters.Parameter - $helpParameterNames = $helpParameters.Name + $command = $_ + $script:commandName = $_.Name + $commandHelp = Get-Help $command.Name -ErrorAction SilentlyContinue + $commandParameters = global:FilterOutCommonParams -Params $command.ParameterSets.Parameters + $script:commandParameterNames = $commandParameters.Name + $helpParameters = global:FilterOutCommonParams -Params $commandHelp.Parameters.Parameter + $script:helpParameterNames = $helpParameters.Name } # If help is not found, synopsis in auto-generated help is the syntax diagram @@ -81,13 +77,13 @@ Describe "Test help for <_.Name>" -ForEach $commands { (Invoke-WebRequest -Uri $_ -UseBasicParsing).StatusCode | Should -Be '200' } - Context "Parameter <_.Name>" -Foreach $commandParameters { + Context "Parameter <_.Name>" -ForEach $commandParameters { BeforeAll { - $parameter = $_ - $parameterName = $parameter.Name - $parameterHelp = $commandHelp.parameters.parameter | Where-Object Name -eq $parameterName - $parameterHelpType = if ($parameterHelp.ParameterValue) { $parameterHelp.ParameterValue.Trim() } + $parameter = $_ + $parameterName = $parameter.Name + $parameterHelp = $commandHelp.parameters.parameter | Where-Object Name -EQ $parameterName + $script:parameterHelpType = if ($parameterHelp.ParameterValue) { $parameterHelp.ParameterValue.Trim() } } # Should be a description for every parameter @@ -107,7 +103,7 @@ Describe "Test help for <_.Name>" -ForEach $commands { } } - Context "Test <_> help parameter help for " -Foreach $helpParameterNames { + Context "Test <_> help parameter help for " -ForEach $helpParameterNames { # Shouldn't find extra parameters in help. It "finds help parameter in code: <_>" { diff --git a/tests/Manifest.tests.ps1 b/tests/Manifest.tests.ps1 index b659654..4dcc18d 100644 --- a/tests/Manifest.tests.ps1 +++ b/tests/Manifest.tests.ps1 @@ -1,14 +1,15 @@ BeforeAll { + Set-Location "$PSScriptRoot/.." Set-BuildEnvironment -Force - $moduleName = $env:BHProjectName - $manifest = Import-PowerShellDataFile -Path $env:BHPSModuleManifest - $outputDir = Join-Path -Path $ENV:BHProjectPath -ChildPath 'Output' - $outputModDir = Join-Path -Path $outputDir -ChildPath $env:BHProjectName - $outputModVerDir = Join-Path -Path $outputModDir -ChildPath $manifest.ModuleVersion + $moduleName = $env:BHProjectName + $manifest = Import-PowerShellDataFile -Path $env:BHPSModuleManifest + $outputDir = Join-Path -Path $ENV:BHProjectPath -ChildPath 'Output' + $outputModDir = Join-Path -Path $outputDir -ChildPath $env:BHProjectName + $outputModVerDir = Join-Path -Path $outputModDir -ChildPath $manifest.ModuleVersion $outputManifestPath = Join-Path -Path $outputModVerDir -Child "$($moduleName).psd1" - $manifestData = Test-ModuleManifest -Path $outputManifestPath -Verbose:$false -ErrorAction Stop -WarningAction SilentlyContinue + $manifestData = Test-ModuleManifest -Path $outputManifestPath -Verbose:$false -ErrorAction Stop -WarningAction SilentlyContinue - $changelogPath = Join-Path -Path $env:BHProjectPath -Child 'CHANGELOG.md' + $changelogPath = Join-Path -Path $env:BHProjectPath -Child 'CHANGELOG.md' $changelogVersion = Get-Content $changelogPath | ForEach-Object { if ($_ -match "^##\s\[(?(\d+\.){1,3}\d+)\]") { $changelogVersion = $matches.Version @@ -16,7 +17,7 @@ BeforeAll { } } - $script:manifest = $null + $script:manifest = $null } Describe 'Module manifest' { @@ -47,7 +48,7 @@ Describe 'Module manifest' { } It 'Has a valid guid' { - {[guid]::Parse($manifestData.Guid)} | Should -Not -Throw + { [guid]::Parse($manifestData.Guid) } | Should -Not -Throw } It 'Has a valid copyright' { @@ -55,7 +56,7 @@ Describe 'Module manifest' { } It 'Has a valid version in the changelog' { - $changelogVersion | Should -Not -BeNullOrEmpty + $changelogVersion | Should -Not -BeNullOrEmpty $changelogVersion -as [Version] | Should -Not -BeNullOrEmpty } @@ -76,7 +77,7 @@ Describe 'Git tagging' -Skip { } It 'Is tagged with a valid version' { - $gitTagVersion | Should -Not -BeNullOrEmpty + $gitTagVersion | Should -Not -BeNullOrEmpty $gitTagVersion -as [Version] | Should -Not -BeNullOrEmpty } diff --git a/tests/ScriptAnalyzerSettings.psd1 b/tests/ScriptAnalyzerSettings.psd1 index 8fe45b9..2c11de5 100644 --- a/tests/ScriptAnalyzerSettings.psd1 +++ b/tests/ScriptAnalyzerSettings.psd1 @@ -1,3 +1,15 @@ @{ - + ExcludeRules = @( + 'PSAvoidUsingWriteHost', + 'PSUseToExportFieldsInManifest', + 'PSUseDeclaredVarsMoreThanAssignments', + # This throws a warning on Build verb, which is valid as of PSv6 + 'PSUseApprovedVerbs' + ) + Rules = @{ + # Don't trip on the task alias. It's by design + PSAvoidUsingCmdletAliases = @{ + Whitelist = @('task') + } + } } diff --git a/tests/TestModule/Tests/Help.tests.ps1 b/tests/TestModule/Tests/Help.tests.ps1 deleted file mode 100644 index 7467ad8..0000000 --- a/tests/TestModule/Tests/Help.tests.ps1 +++ /dev/null @@ -1,93 +0,0 @@ -# Taken with love from @juneb_get_help (https://raw.githubusercontent.com/juneb/PesterTDD/master/Module.Help.Tests.ps1) - -Describe 'Help' { - $testCases = Get-Command -Module $env:BHProjectName -CommandType Cmdlet, Function | ForEach-Object { - @{ - Name = $_.Name - Command = $_ - } - } - - BeforeAll { - $commonParameters = 'Debug', 'ErrorAction', 'ErrorVariable', 'InformationAction', 'InformationVariable', 'OutBuffer', - 'OutVariable', 'PipelineVariable', 'Verbose', 'WarningAction', 'WarningVariable', 'Confirm', 'Whatif' - } - - # No auto-generated help - Context 'Auto-generation' { - it 'Help for [] should not be auto-generated' -TestCases $testCases { - param($Name, $Command) - - $help = Get-Help $Name -ErrorAction SilentlyContinue - $help.Synopsis | Should -Not -BeLike '*`[``]*' - } - } - - - # Should have a description for every function - Context 'Help description' { - It 'Help for [] has a description' -TestCases $testCases { - param($Name, $Command) - - $help = Get-Help $Name -ErrorAction SilentlyContinue - $help.Description | Should -Not -BeNullOrEmpty - } - } - - # Should be at least one example per command - Context 'Examples' { - It 'Help for [] has example code' -TestCases $testCases { - param($Name, $Command) - - $help = Get-Help $Name -ErrorAction SilentlyContinue - ($help.Examples.Example | Select-Object -First 1).Code | Should -Not -BeNullOrEmpty - } - } - - # Parameter help - Context 'Parameter help' { - It '[] has help for every parameter' -TestCases $testCases { - param($Name, $Command) - - $help = Get-Help $Name -ErrorAction SilentlyContinue - $parameters = $Command.ParameterSets.Parameters | - Sort-Object -Property Name -Unique | - Where-Object { $_.Name -notin $commonParameters } - $parameterNames = $parameters.Name - - # Without the filter, WhatIf and Confirm parameters are still flagged in "finds help parameter in code" test - $helpParameters = $help.Parameters.Parameter | - Where-Object { $_.Name -notin $commonParameters } | - Sort-Object -Property Name -Unique - $helpParameterNames = $helpParameters.Name - - foreach ($parameter in $parameters) { - $parameterName = $parameter.Name - $parameterHelp = $help.parameters.parameter | Where-Object Name -eq $parameterName - $parameterHelp.Description.Text | Should -Not -BeNullOrEmpty - - $codeMandatory = $parameter.IsMandatory.toString() - $parameterHelp.Required | Should -Be $codeMandatory - - $codeType = $parameter.ParameterType.Name - # To avoid calling Trim method on a null object. - $helpType = if ($parameterHelp.parameterValue) { $parameterHelp.parameterValue.Trim() } - $helpType | Should -Be $codeType - } - } - } - - # Links are valid - Context 'Links' { - It 'Help for [] has valid links' -TestCases $testCases { - param($Name, $Command) - - $help = Get-Help $Name -ErrorAction SilentlyContinue - $link = $help.relatedLinks.navigationLink.uri - foreach ($link in $links) { - $Results = Invoke-WebRequest -Uri $link -UseBasicParsing - $Results.StatusCode | Should -Be '200' - } - } - } -} diff --git a/tests/build.tests.ps1 b/tests/build.tests.ps1 index 9bbbdc3..d918bfd 100644 --- a/tests/build.tests.ps1 +++ b/tests/build.tests.ps1 @@ -2,25 +2,31 @@ Describe 'Build' { BeforeAll { - # Hack for GH Actions - # For some reason, the TestModule build process create the output in the project root - # and not relative to it's own build file. - if ($env:GITHUB_ACTION) { - $script:testModuleOutputPath = [IO.Path]::Combine($env:BHProjectPath, 'Output', 'TestModule', '0.1.0') - } else { - $script:testModuleOutputPath = [IO.Path]::Combine($env:BHProjectPath, 'tests', 'TestModule', 'Output', 'TestModule', '0.1.0') - } + $tempDir = Join-Path $TestDrive 'TestModule' + Copy-Item $PSScriptRoot/fixtures/TestModule $tempDir -Recurse + Push-Location $tempDir + + # Capture any of the jobs for cleanup later + [array]$script:jobs = @() + + $path = 'Output/TestModule/0.1.0' + $script:testModuleOutputPath = Join-Path $tempDir $path + } + + AfterAll { + Pop-Location + $jobs | Stop-Job -ErrorAction Ignore + $jobs | Remove-Job -ErrorAction Ignore } Context 'Compile module' { BeforeAll { - - Write-Host "PSScriptRoot: $PSScriptRoot" + Write-Host "PSScriptRoot: $tempDir" Write-Host "OutputPath: $script:testModuleOutputPath" # build is PS job so psake doesn't freak out because it's nested - Start-Job -ScriptBlock { - Set-Location $using:PSScriptRoot/TestModule + $script:jobs += Start-Job -Scriptblock { + Set-Location $using:tempDir $global:PSBuildCompile = $true ./build.ps1 -Task Build } | Wait-Job @@ -71,8 +77,8 @@ Describe 'Build' { Context 'Dot-sourced module' { BeforeAll { # build is PS job so psake doesn't freak out because it's nested - Start-Job -ScriptBlock { - Set-Location $using:PSScriptRoot/TestModule + $script:jobs += Start-Job -Scriptblock { + Set-Location $using:tempDir $global:PSBuildCompile = $false ./build.ps1 -Task Build } | Wait-Job @@ -102,4 +108,38 @@ Describe 'Build' { "$script:testModuleOutputPath/en-US/TestModule-help.xml" | Should -Exist } } + Context 'Overwrite Docs' { + BeforeAll { + + Write-Host "PSScriptRoot: $tempDir" + Write-Host "OutputPath: $script:testModuleOutputPath" + + # Replace with a different string to test the overwrite + $script:docPath = "$tempDir/docs/en-US/Get-HelloWorld.md" + $script:original = Get-Content $docPath -Raw + $new = $original -replace 'Hello World', 'Hello Universe' + Set-Content $docPath -Value $new -Force + + # Update the psake file + $psakeFile = "$tempDir/psakeFile.ps1" + $psakeFileContent = Get-Content $psakeFile -Raw + $psakeFileContent = $psakeFileContent -replace '\$PSBPreference.Docs.Overwrite = \$false', '$PSBPreference.Docs.Overwrite = $true' + Set-Content $psakeFile -Value $psakeFileContent -Force + + # build is PS job so psake doesn't freak out because it's nested + $script:jobs += Start-Job -Scriptblock { + Set-Location $using:tempDir + $global:PSBuildCompile = $true + ./build.ps1 -Task Build + } | Wait-Job + } + + AfterAll { + Remove-Item $script:testModuleOutputPath -Recurse -Force + } + It 'Can Overwrite the Docs' { + # Test that the file reset as expected + Get-Content $script:docPath -Raw | Should -BeExactly $script:original + } + } } diff --git a/tests/TestModule/.build.ps1 b/tests/fixtures/TestModule/.build.ps1 similarity index 100% rename from tests/TestModule/.build.ps1 rename to tests/fixtures/TestModule/.build.ps1 diff --git a/tests/TestModule/.gitattributes b/tests/fixtures/TestModule/.gitattributes similarity index 100% rename from tests/TestModule/.gitattributes rename to tests/fixtures/TestModule/.gitattributes diff --git a/tests/TestModule/.github/CONTRIBUTING.md b/tests/fixtures/TestModule/.github/CONTRIBUTING.md similarity index 100% rename from tests/TestModule/.github/CONTRIBUTING.md rename to tests/fixtures/TestModule/.github/CONTRIBUTING.md diff --git a/tests/TestModule/.github/ISSUE_TEMPLATE.md b/tests/fixtures/TestModule/.github/ISSUE_TEMPLATE.md similarity index 100% rename from tests/TestModule/.github/ISSUE_TEMPLATE.md rename to tests/fixtures/TestModule/.github/ISSUE_TEMPLATE.md diff --git a/tests/TestModule/.github/PULL_REQUEST_TEMPLATE.md b/tests/fixtures/TestModule/.github/PULL_REQUEST_TEMPLATE.md similarity index 100% rename from tests/TestModule/.github/PULL_REQUEST_TEMPLATE.md rename to tests/fixtures/TestModule/.github/PULL_REQUEST_TEMPLATE.md diff --git a/tests/TestModule/.gitignore b/tests/fixtures/TestModule/.gitignore similarity index 100% rename from tests/TestModule/.gitignore rename to tests/fixtures/TestModule/.gitignore diff --git a/tests/TestModule/.vscode/extensions.json b/tests/fixtures/TestModule/.vscode/extensions.json similarity index 100% rename from tests/TestModule/.vscode/extensions.json rename to tests/fixtures/TestModule/.vscode/extensions.json diff --git a/tests/TestModule/.vscode/settings.json b/tests/fixtures/TestModule/.vscode/settings.json similarity index 100% rename from tests/TestModule/.vscode/settings.json rename to tests/fixtures/TestModule/.vscode/settings.json diff --git a/tests/TestModule/.vscode/tasks.json b/tests/fixtures/TestModule/.vscode/tasks.json similarity index 100% rename from tests/TestModule/.vscode/tasks.json rename to tests/fixtures/TestModule/.vscode/tasks.json diff --git a/tests/TestModule/CHANGELOG.md b/tests/fixtures/TestModule/CHANGELOG.md similarity index 100% rename from tests/TestModule/CHANGELOG.md rename to tests/fixtures/TestModule/CHANGELOG.md diff --git a/tests/TestModule/CODE_OF_CONDUCT.md b/tests/fixtures/TestModule/CODE_OF_CONDUCT.md similarity index 100% rename from tests/TestModule/CODE_OF_CONDUCT.md rename to tests/fixtures/TestModule/CODE_OF_CONDUCT.md diff --git a/tests/TestModule/LICENSE b/tests/fixtures/TestModule/LICENSE similarity index 100% rename from tests/TestModule/LICENSE rename to tests/fixtures/TestModule/LICENSE diff --git a/tests/TestModule/README.md b/tests/fixtures/TestModule/README.md similarity index 100% rename from tests/TestModule/README.md rename to tests/fixtures/TestModule/README.md diff --git a/tests/TestModule/TestModule/Private/GetHelloWorld.ps1 b/tests/fixtures/TestModule/TestModule/Private/GetHelloWorld.ps1 similarity index 100% rename from tests/TestModule/TestModule/Private/GetHelloWorld.ps1 rename to tests/fixtures/TestModule/TestModule/Private/GetHelloWorld.ps1 diff --git a/tests/TestModule/TestModule/Private/excludemealso.ps1 b/tests/fixtures/TestModule/TestModule/Private/excludemealso.ps1 similarity index 100% rename from tests/TestModule/TestModule/Private/excludemealso.ps1 rename to tests/fixtures/TestModule/TestModule/Private/excludemealso.ps1 diff --git a/tests/TestModule/TestModule/Public/Get-HelloWorld.ps1 b/tests/fixtures/TestModule/TestModule/Public/Get-HelloWorld.ps1 similarity index 100% rename from tests/TestModule/TestModule/Public/Get-HelloWorld.ps1 rename to tests/fixtures/TestModule/TestModule/Public/Get-HelloWorld.ps1 diff --git a/tests/TestModule/TestModule/TestModule.psd1 b/tests/fixtures/TestModule/TestModule/TestModule.psd1 similarity index 100% rename from tests/TestModule/TestModule/TestModule.psd1 rename to tests/fixtures/TestModule/TestModule/TestModule.psd1 diff --git a/tests/TestModule/TestModule/TestModule.psm1 b/tests/fixtures/TestModule/TestModule/TestModule.psm1 similarity index 100% rename from tests/TestModule/TestModule/TestModule.psm1 rename to tests/fixtures/TestModule/TestModule/TestModule.psm1 diff --git a/tests/TestModule/TestModule/dontcopy/garbage.txt b/tests/fixtures/TestModule/TestModule/dontcopy/garbage.txt similarity index 100% rename from tests/TestModule/TestModule/dontcopy/garbage.txt rename to tests/fixtures/TestModule/TestModule/dontcopy/garbage.txt diff --git a/tests/TestModule/TestModule/excludeme.txt b/tests/fixtures/TestModule/TestModule/excludeme.txt similarity index 100% rename from tests/TestModule/TestModule/excludeme.txt rename to tests/fixtures/TestModule/TestModule/excludeme.txt diff --git a/tests/TestModule/TestModule/stuff/copymealways.txt b/tests/fixtures/TestModule/TestModule/stuff/copymealways.txt similarity index 100% rename from tests/TestModule/TestModule/stuff/copymealways.txt rename to tests/fixtures/TestModule/TestModule/stuff/copymealways.txt diff --git a/tests/fixtures/TestModule/Tests/Help.tests.ps1 b/tests/fixtures/TestModule/Tests/Help.tests.ps1 new file mode 100644 index 0000000..857eb63 --- /dev/null +++ b/tests/fixtures/TestModule/Tests/Help.tests.ps1 @@ -0,0 +1,117 @@ +# Taken with love from @juneb_get_help (https://raw.githubusercontent.com/juneb/PesterTDD/master/Module.Help.Tests.ps1) + +BeforeDiscovery { + function global:FilterOutCommonParams { + param ($Params) + $commonParams = [System.Management.Automation.PSCmdlet]::OptionalCommonParameters + + [System.Management.Automation.PSCmdlet]::CommonParameters + $params | Where-Object { $_.Name -notin $commonParams } | Sort-Object -Property Name -Unique + } + + $manifest = Import-PowerShellDataFile -Path $env:BHPSModuleManifest + $outputDir = Join-Path -Path $env:BHProjectPath -ChildPath 'Output' + $outputModDir = Join-Path -Path $outputDir -ChildPath $env:BHProjectName + $outputModVerDir = Join-Path -Path $outputModDir -ChildPath $manifest.ModuleVersion + $outputModVerManifest = Join-Path -Path $outputModVerDir -ChildPath "$($env:BHProjectName).psd1" + + # Get module commands + # Remove all versions of the module from the session. Pester can't handle multiple versions. + Get-Module $env:BHProjectName | Remove-Module -Force -ErrorAction Ignore + Import-Module -Name $outputModVerManifest -Verbose:$false -ErrorAction Stop + $params = @{ + Module = (Get-Module $env:BHProjectName) + CommandType = [System.Management.Automation.CommandTypes[]]'Cmdlet, Function' # Not alias + } + if ($PSVersionTable.PSVersion.Major -lt 6) { + $params.CommandType[0] += 'Workflow' + } + $commands = Get-Command @params + + ## When testing help, remember that help is cached at the beginning of each session. + ## To test, restart session. +} + +AfterAll { + Remove-Item Function:/FilterOutCommonParams +} + +Describe "Test help for <_.Name>" -ForEach $commands { + + BeforeDiscovery { + # Get command help, parameters, and links + $command = $_ + $commandHelp = Get-Help $command.Name -ErrorAction SilentlyContinue + $commandParameters = global:FilterOutCommonParams -Params $command.ParameterSets.Parameters + $commandParameterNames = $commandParameters.Name + $helpLinks = $commandHelp.relatedLinks.navigationLink.uri + } + + BeforeAll { + # These vars are needed in both discovery and test phases so we need to duplicate them here + $command = $_ + $commandName = $_.Name + $commandHelp = Get-Help $command.Name -ErrorAction SilentlyContinue + $commandParameters = global:FilterOutCommonParams -Params $command.ParameterSets.Parameters + $commandParameterNames = $commandParameters.Name + $helpParameters = global:FilterOutCommonParams -Params $commandHelp.Parameters.Parameter + $helpParameterNames = $helpParameters.Name + } + + # If help is not found, synopsis in auto-generated help is the syntax diagram + It 'Help is not auto-generated' { + $commandHelp.Synopsis | Should -Not -BeLike '*`[``]*' + } + + # Should be a description for every function + It "Has description" { + $commandHelp.Description | Should -Not -BeNullOrEmpty + } + + # Should be at least one example + It "Has example code" { + ($commandHelp.Examples.Example | Select-Object -First 1).Code | Should -Not -BeNullOrEmpty + } + + # Should be at least one example description + It "Has example help" { + ($commandHelp.Examples.Example.Remarks | Select-Object -First 1).Text | Should -Not -BeNullOrEmpty + } + + It "Help link <_> is valid" -ForEach $helpLinks { + (Invoke-WebRequest -Uri $_ -UseBasicParsing).StatusCode | Should -Be '200' + } + + Context "Parameter <_.Name>" -ForEach $commandParameters { + + BeforeAll { + $parameter = $_ + $parameterName = $parameter.Name + $parameterHelp = $commandHelp.parameters.parameter | Where-Object Name -EQ $parameterName + $parameterHelpType = if ($parameterHelp.ParameterValue) { $parameterHelp.ParameterValue.Trim() } + } + + # Should be a description for every parameter + It "Has description" { + $parameterHelp.Description.Text | Should -Not -BeNullOrEmpty + } + + # Required value in Help should match IsMandatory property of parameter + It "Has correct [mandatory] value" { + $codeMandatory = $_.IsMandatory.toString() + $parameterHelp.Required | Should -Be $codeMandatory + } + + # Parameter type in help should match code + It "Has correct parameter type" { + $parameterHelpType | Should -Be $parameter.ParameterType.Name + } + } + + Context "Test <_> help parameter help for " -ForEach $helpParameterNames { + + # Shouldn't find extra parameters in help. + It "finds help parameter in code: <_>" { + $_ -in $parameterNames | Should -Be $true + } + } +} diff --git a/tests/TestModule/Tests/Manifest.tests.ps1 b/tests/fixtures/TestModule/Tests/Manifest.tests.ps1 similarity index 100% rename from tests/TestModule/Tests/Manifest.tests.ps1 rename to tests/fixtures/TestModule/Tests/Manifest.tests.ps1 diff --git a/tests/TestModule/Tests/Meta.tests.ps1 b/tests/fixtures/TestModule/Tests/Meta.tests.ps1 similarity index 100% rename from tests/TestModule/Tests/Meta.tests.ps1 rename to tests/fixtures/TestModule/Tests/Meta.tests.ps1 diff --git a/tests/TestModule/Tests/MetaFixers.psm1 b/tests/fixtures/TestModule/Tests/MetaFixers.psm1 similarity index 100% rename from tests/TestModule/Tests/MetaFixers.psm1 rename to tests/fixtures/TestModule/Tests/MetaFixers.psm1 diff --git a/tests/TestModule/Tests/ScriptAnalyzerSettings.psd1 b/tests/fixtures/TestModule/Tests/ScriptAnalyzerSettings.psd1 similarity index 100% rename from tests/TestModule/Tests/ScriptAnalyzerSettings.psd1 rename to tests/fixtures/TestModule/Tests/ScriptAnalyzerSettings.psd1 diff --git a/tests/TestModule/Tests/a_InModuleScope.tests.ps1 b/tests/fixtures/TestModule/Tests/a_InModuleScope.tests.ps1 similarity index 100% rename from tests/TestModule/Tests/a_InModuleScope.tests.ps1 rename to tests/fixtures/TestModule/Tests/a_InModuleScope.tests.ps1 diff --git a/tests/TestModule/azure-pipelines.yml b/tests/fixtures/TestModule/azure-pipelines.yml similarity index 100% rename from tests/TestModule/azure-pipelines.yml rename to tests/fixtures/TestModule/azure-pipelines.yml diff --git a/tests/TestModule/build.ps1 b/tests/fixtures/TestModule/build.ps1 similarity index 100% rename from tests/TestModule/build.ps1 rename to tests/fixtures/TestModule/build.ps1 diff --git a/tests/TestModule/mkdocs.yml b/tests/fixtures/TestModule/mkdocs.yml similarity index 100% rename from tests/TestModule/mkdocs.yml rename to tests/fixtures/TestModule/mkdocs.yml diff --git a/tests/TestModule/psakeFile.ps1 b/tests/fixtures/TestModule/psakeFile.ps1 similarity index 69% rename from tests/TestModule/psakeFile.ps1 rename to tests/fixtures/TestModule/psakeFile.ps1 index 68e4dbb..5007a1e 100644 --- a/tests/TestModule/psakeFile.ps1 +++ b/tests/fixtures/TestModule/psakeFile.ps1 @@ -1,6 +1,6 @@ Import-Module ../../Output/PowerShellBuild -Force -properties { +Properties { # Pester can build the module using both scenarios if (Test-Path -Path 'Variable:\PSBuildCompile') { $PSBPreference.Build.CompileModule = $global:PSBuildCompile @@ -15,21 +15,35 @@ properties { $PSBPreference.Build.Exclude = ('excludeme.txt', 'excludemealso*', 'dontcopy') # If compiling, insert headers/footers for entire PSM1 and for each inserted function - $PSBPreference.Build.CompileHeader = '# Module Header' + [Environment]::NewLine - $PSBPreference.Build.CompileFooter = '# Module Footer' + $PSBPreference.Build.CompileHeader = '# Module Header' + [Environment]::NewLine + $PSBPreference.Build.CompileFooter = '# Module Footer' $PSBPreference.Build.CompileScriptHeader = '# Function header' $PSBPreference.Build.CompileScriptFooter = '# Function footer' + [Environment]::NewLine # So Pester InModuleScope works - $PSBPreference.Test.ImportModule = $true - $PSBPreference.Test.CodeCoverage.Enabled = $true + $PSBPreference.Test.ImportModule = $true + $PSBPreference.Test.CodeCoverage.Enabled = $true $PSBPreference.Test.CodeCoverage.Threshold = 0.0 $PSBPreference.Test.CodeCoverage.OutputFile = 'cc.xml' # Override the default output directory $PSBPreference.Build.OutDir = 'Output' + + # Don't overwrite the docs + $PSBPreference.Docs.Overwrite = $false } -task default -depends Build +Task default -depends Build + +Task Build -FromModule PowerShellBuild -minimumVersion 0.5.0 + + + + + + + + + + -task Build -FromModule PowerShellBuild -minimumVersion 0.5.0 diff --git a/tests/TestModule/requirements.psd1 b/tests/fixtures/TestModule/requirements.psd1 similarity index 100% rename from tests/TestModule/requirements.psd1 rename to tests/fixtures/TestModule/requirements.psd1