diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ee4ecfb..d4dd83b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,4 +16,10 @@ jobs: - uses: actions/checkout@v4 - name: Test shell: pwsh - run: ./build.ps1 -Task Test -Bootstrap + env: + DEBUG: ${{ runner.debug == '1' }} + run: | + if($env:DEBUG -eq 'true' -or $env:DEBUG -eq '1') { + $DebugPreference = 'Continue' + } + ./build.ps1 -Task Test -Bootstrap 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..b8375aa 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,5 +6,14 @@ "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", + "search.useIgnoreFiles": true, + "search.exclude": { + "**/node_modules": true, + "**/bower_components": true, + "**/*.code-search": true, + "**/.ruby-lsp": true, + "Output/**": true + } } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 106a76c..69ccd58 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", @@ -69,6 +78,12 @@ "label": "Publish", "type": "shell", "command": "${cwd}/build.ps1 -Task Publish -Verbose" + }, + { + "label": "Bootstrap", + "type": "shell", + "command": "${cwd}/build.ps1 -Task Init -Bootstrap -Verbose", + "problemMatcher": [] } ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 92685cf..267d22f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## Unreleased +### Added + - Add new dependencies variables to allow end user to modify which tasks are run. +- Add localization support. + +### Fixes + +- Fix a bug in `Build-PSBuildMarkdown` where a hashtable item was added twice. ## [0.7.2] 2025-05-21 @@ -26,11 +33,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - `$PSBPreference.Test.OutputVerbosity` can be set to **None**, **Normal**, **Detailed**, and **Diagnostic**. The default value is **Detailed**. -## [0.7.1] 2025-04-01 +## [0.7.1] 2025-03-31 Bugfix -### Fixes +### Fixed -- Fix a bug in `Build-PSBuildMarkdown` where a hashtable item was added twice. +- 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 diff --git a/PowerShellBuild/PowerShellBuild.psm1 b/PowerShellBuild/PowerShellBuild.psm1 index 1726b41..87a2200 100644 --- a/PowerShellBuild/PowerShellBuild.psm1 +++ b/PowerShellBuild/PowerShellBuild.psm1 @@ -1,6 +1,6 @@ # Dot source public functions $private = @(Get-ChildItem -Path ([IO.Path]::Combine($PSScriptRoot, 'Private/*.ps1')) -Recurse) -$public = @(Get-ChildItem -Path ([IO.Path]::Combine($PSScriptRoot, 'Public/*.ps1')) -Recurse) +$public = @(Get-ChildItem -Path ([IO.Path]::Combine($PSScriptRoot, 'Public/*.ps1')) -Recurse) foreach ($import in $public + $private) { try { . $import.FullName @@ -9,6 +9,43 @@ foreach ($import in $public + $private) { } } +data LocalizedData { + # Load here in case Import-LocalizedData is not available + ConvertFrom-StringData @' +NoCommandsExported=No commands have been exported. Skipping markdown generation. +FailedToGenerateMarkdownHelp=Failed to generate markdown help. : {0} +AddingFileToPsm1=Adding [{0}] to PSM1 +MakeCabNotAvailable=MakeCab.exe is not available. Cannot create help cab. +DirectoryAlreadyExists=Directory already exists [{0}]. +PathLongerThan3Chars=`$Path [{0}] must be longer than 3 characters. +BuildSystemDetails=Build System Details: +BuildModule=Build Module: {0}`:{1} +PowerShellVersion=PowerShell Version: {0} +EnvironmentVariables={0}`Environment variables: +PublishingVersionToRepository=Publishing version [{0}] to repository [{1}]... +FolderDoesNotExist=Folder does not exist: {0} +PathArgumentMustBeAFolder=The Path argument must be a folder. File paths are not allowed. +UnableToFindModuleManifest=Unable to find module manifest [{0}]. Can't import module +PesterTestsFailed=One or more Pester tests failed +CodeCoverage=Code Coverage +Type=Type +CodeCoverageLessThanThreshold=Code coverage: [{0}] is [{1:p}], which is less than the threshold of [{2:p}] +CodeCoverageCodeCoverageFileNotFound=Code coverage file [{0}] not found. +SeverityThresholdSetTo=SeverityThreshold set to: {0} +PSScriptAnalyzerResults=PSScriptAnalyzer results: +ScriptAnalyzerErrors=One or more ScriptAnalyzer errors were found! +ScriptAnalyzerWarnings=One or more ScriptAnalyzer warnings were found! +ScriptAnalyzerIssues=One or more ScriptAnalyzer issues were found! +'@ +} +$importLocalizedDataSplat = @{ + BindingVariable = 'LocalizedData' + FileName = 'Messages.psd1' + ErrorAction = 'SilentlyContinue' +} +Import-LocalizedData @importLocalizedDataSplat + + Export-ModuleMember -Function $public.Basename # $psakeTaskAlias = 'PowerShellBuild.psake.tasks' diff --git a/PowerShellBuild/Public/Build-PSBuildMAMLHelp.ps1 b/PowerShellBuild/Public/Build-PSBuildMAMLHelp.ps1 index d1fd5b1..20d42a6 100644 --- a/PowerShellBuild/Public/Build-PSBuildMAMLHelp.ps1 +++ b/PowerShellBuild/Public/Build-PSBuildMAMLHelp.ps1 @@ -14,7 +14,7 @@ function Build-PSBuildMAMLHelp { Uses PlatyPS to generate MAML XML help from markdown files in ./docs and saves the XML file to a directory under ./output/MyModule #> - [cmdletbinding()] + [CmdletBinding()] param( [parameter(Mandatory)] [string]$Path, diff --git a/PowerShellBuild/Public/Build-PSBuildMarkdown.ps1 b/PowerShellBuild/Public/Build-PSBuildMarkdown.ps1 index ec2a90a..9010543 100644 --- a/PowerShellBuild/Public/Build-PSBuildMarkdown.ps1 +++ b/PowerShellBuild/Public/Build-PSBuildMarkdown.ps1 @@ -25,7 +25,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, @@ -56,7 +56,7 @@ function Build-PSBuildMarkdown { try { if ($moduleInfo.ExportedCommands.Count -eq 0) { - Write-Warning 'No commands have been exported. Skipping markdown generation.' + Write-Warning $LocalizedData.NoCommandsExported return } @@ -93,7 +93,7 @@ function Build-PSBuildMarkdown { } New-MarkdownHelp @newMDParams > $null } catch { - Write-Error "Failed to generate markdown help. : $_" + Write-Error ($LocalizedData.FailedToGenerateMarkdownHelp -f $_) } finally { Remove-Module $moduleName } diff --git a/PowerShellBuild/Public/Build-PSBuildModule.ps1 b/PowerShellBuild/Public/Build-PSBuildModule.ps1 index e4d99e6..e8373d1 100644 --- a/PowerShellBuild/Public/Build-PSBuildModule.ps1 +++ b/PowerShellBuild/Public/Build-PSBuildModule.ps1 @@ -1,10 +1,12 @@ +# spell-checker:ignore modulename function Build-PSBuildModule { <# .SYNOPSIS Builds a PowerShell module based on source directory .DESCRIPTION - Builds a PowerShell module based on source directory and optionally concatenates - public/private functions from separete files into monolithic .PSM1 file. + Builds a PowerShell module based on source directory and optionally + concatenates public/private functions from separate files into + monolithic .PSM1 file. .PARAMETER Path The source module path. .PARAMETER DestinationPath @@ -12,7 +14,8 @@ function Build-PSBuildModule { .PARAMETER ModuleName The name of the module. .PARAMETER Compile - Switch to indicate if separete function files should be concatenated into monolithic .PSM1 file. + Switch to indicate if separate function files should be concatenated + into monolithic .PSM1 file. .PARAMETER CompileHeader String that will be at the top of your PSM1 file. .PARAMETER CompileFooter @@ -20,19 +23,23 @@ function Build-PSBuildModule { .PARAMETER CompileScriptHeader String that will be added to your PSM1 file before each script file. .PARAMETER CompileScriptFooter - String that will be added to your PSM1 file beforeafter each script file. + String that will be added to your PSM1 file after each script file. .PARAMETER ReadMePath - Path to project README. If present, this will become the "about_.help.txt" file in the build module. + Path to project README. If present, this will become the + "about_.help.txt" file in the build module. .PARAMETER CompileDirectories - List of directories containing .ps1 files that will also be compiled into the PSM1. + List of directories containing .ps1 files that will also be compiled + into the PSM1. .PARAMETER CopyDirectories List of directories that will copying "as-is" into the build module. .PARAMETER Exclude - Array of files (regular expressions) to exclude from copying into built module. + Array of files (regular expressions) to exclude from copying into built + module. .PARAMETER Culture - UI Culture. This is used to determine what culture directory to store "about_.help.txt" in. + UI Culture. This is used to determine what culture directory to store + "about_.help.txt" in. .EXAMPLE - PS> $buildParams = @{ + $buildParams = @{ Path = ./MyModule DestinationPath = ./Output/MoModule/0.1.0 ModuleName = MyModule @@ -40,11 +47,12 @@ function Build-PSBuildModule { Compile = $false Culture = (Get-UICulture).Name } - PS> Build-PSBuildModule @buildParams + Build-PSBuildModule @buildParams - Build module from source directory './MyModule' and save to '/Output/MoModule/0.1.0' + Build module from source directory './MyModule' and save to + '/Output/MoModule/0.1.0' #> - [cmdletbinding()] + [CmdletBinding()] param( [parameter(Mandatory)] [string]$Path, @@ -77,11 +85,22 @@ function Build-PSBuildModule { ) if (-not (Test-Path -LiteralPath $DestinationPath)) { - New-Item -Path $DestinationPath -ItemType Directory -Verbose:$VerbosePreference > $null + $newItemSplat = @{ + Path = $DestinationPath + ItemType = 'Directory' + Verbose = $VerbosePreference + } + New-Item @newItemSplat > $null } # Copy "non-processed files" - Get-ChildItem -Path $Path -Include '*.psm1', '*.psd1', '*.ps1xml' -Depth 1 | Copy-Item -Destination $DestinationPath -Force + $getChildItemSplat = @{ + Path = $Path + Include = '*.psm1', '*.psd1', '*.ps1xml' + Depth = 1 + } + Get-ChildItem @getChildItemSplat | + Copy-Item -Destination $DestinationPath -Force foreach ($dir in $CopyDirectories) { $copyPath = [IO.Path]::Combine($Path, $dir) Copy-Item -Path $copyPath -Destination $DestinationPath -Recurse -Force @@ -89,37 +108,57 @@ function Build-PSBuildModule { # Copy README as about_.help.txt if (-not [string]::IsNullOrEmpty($ReadMePath)) { - $culturePath = [IO.Path]::Combine($DestinationPath, $Culture) - $aboutModulePath = [IO.Path]::Combine($culturePath, "about_$($ModuleName).help.txt") - if(-not (Test-Path $culturePath -PathType Container)) { + $culturePath = [IO.Path]::Combine($DestinationPath, $Culture) + $aboutModulePath = [IO.Path]::Combine( + $culturePath, + "about_$($ModuleName).help.txt" + ) + if (-not (Test-Path $culturePath -PathType Container)) { New-Item $culturePath -Type Directory -Force > $null - Copy-Item -LiteralPath $ReadMePath -Destination $aboutModulePath -Force + $copyItemSplat = @{ + LiteralPath = $ReadMePath + Destination = $aboutModulePath + Force = $true + } + Copy-Item @copyItemSplat } } - # Copy source files to destination and optionally combine *.ps1 files into the PSM1 + # Copy source files to destination and optionally combine *.ps1 files + # into the PSM1 if ($Compile.IsPresent) { $rootModule = [IO.Path]::Combine($DestinationPath, "$ModuleName.psm1") # Grab the contents of the copied over PSM1 # This will be appended to the end of the finished PSM1 $psm1Contents = Get-Content -Path $rootModule -Raw - '' | Out-File -FilePath $rootModule -Encoding utf8 + '' | Out-File -FilePath $rootModule -Encoding 'utf8' if ($CompileHeader) { - $CompileHeader | Add-Content -Path $rootModule -Encoding utf8 + $CompileHeader | Add-Content -Path $rootModule -Encoding 'utf8' } $resolvedCompileDirectories = $CompileDirectories | ForEach-Object { [IO.Path]::Combine($Path, $_) } - $allScripts = Get-ChildItem -Path $resolvedCompileDirectories -Filter '*.ps1' -File -Recurse -ErrorAction SilentlyContinue + $getChildItemSplat = @{ + Path = $resolvedCompileDirectories + Filter = '*.ps1' + File = $true + Recurse = $true + ErrorAction = 'SilentlyContinue' + } + $allScripts = Get-ChildItem @getChildItemSplat $allScripts = $allScripts | Remove-ExcludedItem -Exclude $Exclude + $addContentSplat = @{ + Path = $rootModule + Encoding = 'utf8' + } $allScripts | ForEach-Object { $srcFile = Resolve-Path $_.FullName -Relative - Write-Verbose "Adding [$srcFile] to PSM1" + Write-Verbose ($LocalizedData.AddingFileToPsm1 -f $srcFile) if ($CompileScriptHeader) { Write-Output $CompileScriptHeader @@ -130,15 +169,14 @@ function Build-PSBuildModule { if ($CompileScriptFooter) { Write-Output $CompileScriptFooter } + } | Add-Content @addContentSplat - } | Add-Content -Path $rootModule -Encoding utf8 - - $psm1Contents | Add-Content -Path $rootModule -Encoding utf8 + $psm1Contents | Add-Content @addContentSplat if ($CompileFooter) { - $CompileFooter | Add-Content -Path $rootModule -Encoding utf8 + $CompileFooter | Add-Content @addContentSplat } - } else{ + } else { # Copy everything over, then remove stuff that should have been excluded # It's just easier this way $copyParams = @{ @@ -157,13 +195,26 @@ function Build-PSBuildModule { } } } - $toRemove | Remove-Item -Recurse -Force -ErrorAction Ignore + $toRemove | Remove-Item -Recurse -Force -ErrorAction 'Ignore' } # Export public functions in manifest if there are any public functions - $publicFunctions = Get-ChildItem $Path/Public/*.ps1 -Recurse -ErrorAction SilentlyContinue + $getChildItemSplat = @{ + Recurse = $true + ErrorAction = 'SilentlyContinue' + Path = "$Path/Public/*.ps1" + } + $publicFunctions = Get-ChildItem @getChildItemSplat if ($publicFunctions) { - $outputManifest = [IO.Path]::Combine($DestinationPath, "$ModuleName.psd1") - Update-Metadata -Path $OutputManifest -PropertyName FunctionsToExport -Value $publicFunctions.BaseName + $outputManifest = [IO.Path]::Combine( + $DestinationPath, + "$ModuleName.psd1" + ) + $updateMetadataSplat = @{ + Path = $OutputManifest + PropertyName = 'FunctionsToExport' + Value = $publicFunctions.BaseName + } + Update-Metadata @updateMetadataSplat } } diff --git a/PowerShellBuild/Public/Build-PSBuildUpdatableHelp.ps1 b/PowerShellBuild/Public/Build-PSBuildUpdatableHelp.ps1 index 9b8a404..de784eb 100644 --- a/PowerShellBuild/Public/Build-PSBuildUpdatableHelp.ps1 +++ b/PowerShellBuild/Public/Build-PSBuildUpdatableHelp.ps1 @@ -9,13 +9,14 @@ function Build-PSBuildUpdatableHelp { .PARAMETER OutputPath Path to create updatable help .cab file in. .PARAMETER Module - Name of the module to create a .cab file for. Defaults to the $ModuleName variable from the parent scope. + Name of the module to create a .cab file for. Defaults to the + $ModuleName variable from the parent scope. .EXAMPLE PS> Build-PSBuildUpdatableHelp -DocsPath ./docs -OutputPath ./Output/UpdatableHelp Create help .cab file based on PlatyPS markdown help. #> - [cmdletbinding()] + [CmdletBinding()] param( [parameter(Mandatory)] [string]$DocsPath, @@ -27,7 +28,7 @@ function Build-PSBuildUpdatableHelp { ) if ($null -ne $IsWindows -and -not $IsWindows) { - Write-Warning 'MakeCab.exe is only available on Windows. Cannot create help cab.' + Write-Warning $LocalizedData.MakeCabNotAvailable return } @@ -35,18 +36,32 @@ function Build-PSBuildUpdatableHelp { # Create updatable help output directory if (-not (Test-Path -LiteralPath $OutputPath)) { - New-Item $OutputPath -ItemType Directory -Verbose:$VerbosePreference > $null + $newItemSplat = @{ + ItemType = 'Directory' + Verbose = $VerbosePreference + Path = $OutputPath + } + New-Item @newItemSplat > $null } else { - Write-Verbose "Directory already exists [$OutputPath]." - Get-ChildItem $OutputPath | Remove-Item -Recurse -Force -Verbose:$VerbosePreference + Write-Verbose ($LocalizedData.DirectoryAlreadyExists -f $OutputPath) + $removeItemSplat = @{ + Recurse = $true + Force = $true + Verbose = $VerbosePreference + } + Get-ChildItem $OutputPath | Remove-Item @removeItemSplat } - # Generate updatable help files. Note: this will currently update the version number in the module's MD - # file in the metadata. + # Generate updatable help files. Note: this will currently update the + # version number in the module's MD file in the metadata. foreach ($locale in $helpLocales) { $cabParams = @{ CabFilesFolder = [IO.Path]::Combine($moduleOutDir, $locale) - LandingPagePath = [IO.Path]::Combine($DocsPath, $locale, "$Module.md") + LandingPagePath = [IO.Path]::Combine( + $DocsPath, + $locale, + "$Module.md" + ) OutputFolder = $OutputPath Verbose = $VerbosePreference } diff --git a/PowerShellBuild/Public/Clear-PSBuildOutputFolder.ps1 b/PowerShellBuild/Public/Clear-PSBuildOutputFolder.ps1 index a8001af..fd6149a 100644 --- a/PowerShellBuild/Public/Clear-PSBuildOutputFolder.ps1 +++ b/PowerShellBuild/Public/Clear-PSBuildOutputFolder.ps1 @@ -16,11 +16,11 @@ function Clear-PSBuildOutputFolder { # Maybe a bit paranoid but this task nuked \ on my laptop. Good thing I was not running as admin. [parameter(Mandatory)] [ValidateScript({ - if ($_.Length -le 3) { - throw "`$Path [$_] must be longer than 3 characters." - } - $true - })] + if ($_.Length -le 3) { + throw ($LocalizedData.PathLongerThan3Chars -f $_) + } + $true + })] [string]$Path ) diff --git a/PowerShellBuild/Public/Initialize-PSBuild.ps1 b/PowerShellBuild/Public/Initialize-PSBuild.ps1 index 0d0b803..c45dfb7 100644 --- a/PowerShellBuild/Public/Initialize-PSBuild.ps1 +++ b/PowerShellBuild/Public/Initialize-PSBuild.ps1 @@ -7,13 +7,14 @@ function Initialize-PSBuild { .PARAMETER BuildEnvironment Contains the PowerShellBuild settings (known as $PSBPreference). .PARAMETER UseBuildHelpers - Use BuildHelpers module to populate common environment variables based on current build system context. + Use BuildHelpers module to populate common environment variables based + on current build system context. .EXAMPLE PS> Initialize-PSBuild -UseBuildHelpers Populate build system environment variables. #> - [cmdletbinding()] + [CmdletBinding()] param( [Parameter(Mandatory)] [Hashtable] @@ -22,10 +23,24 @@ function Initialize-PSBuild { [switch]$UseBuildHelpers ) - if ($BuildEnvironment.Build.OutDir.StartsWith($env:BHProjectPath, [StringComparison]::OrdinalIgnoreCase)) { - $BuildEnvironment.Build.ModuleOutDir = [IO.Path]::Combine($BuildEnvironment.Build.OutDir, $env:BHProjectName, $BuildEnvironment.General.ModuleVersion) + if ( + $BuildEnvironment.Build.OutDir.StartsWith( + $env:BHProjectPath, + [StringComparison]::OrdinalIgnoreCase + ) + ) { + $BuildEnvironment.Build.ModuleOutDir = [IO.Path]::Combine( + $BuildEnvironment.Build.OutDir, + $env:BHProjectName, + $BuildEnvironment.General.ModuleVersion + ) } else { - $BuildEnvironment.Build.ModuleOutDir = [IO.Path]::Combine($env:BHProjectPath, $BuildEnvironment.Build.OutDir, $env:BHProjectName, $BuildEnvironment.General.ModuleVersion) + $BuildEnvironment.Build.ModuleOutDir = [IO.Path]::Combine( + $env:BHProjectPath, + $BuildEnvironment.Build.OutDir, + $env:BHProjectName, + $BuildEnvironment.General.ModuleVersion + ) } $params = @{ @@ -33,19 +48,19 @@ function Initialize-PSBuild { } Set-BuildEnvironment @params -Force - Write-Host 'Build System Details:' -ForegroundColor Yellow - $psVersion = $PSVersionTable.PSVersion.ToString() - $buildModuleName = $MyInvocation.MyCommand.Module.Name + Write-Host $LocalizedData.BuildSystemDetails -ForegroundColor 'Yellow' + $psVersion = $PSVersionTable.PSVersion.ToString() + $buildModuleName = $MyInvocation.MyCommand.Module.Name $buildModuleVersion = $MyInvocation.MyCommand.Module.Version - "Build Module: $buildModuleName`:$buildModuleVersion" - "PowerShell Version: $psVersion" + $LocalizedData.BuildModule -f $buildModuleName, $buildModuleVersion + $LocalizedData.PowerShellVersion -f $psVersion if ($UseBuildHelpers.IsPresent) { $nl = [System.Environment]::NewLine - Write-Host "$nl`Environment variables:" -ForegroundColor Yellow + Write-Host ($LocalizedData.EnvironmentVariables -f $nl) -ForegroundColor 'Yellow' (Get-Item ENV:BH*).Foreach({ - '{0,-20}{1}' -f $_.name, $_.value - }) + '{0,-20}{1}' -f $_.name, $_.value + }) } } diff --git a/PowerShellBuild/Public/Publish-PSBuildModule.ps1 b/PowerShellBuild/Public/Publish-PSBuildModule.ps1 index 3603c29..68414e5 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)] @@ -50,10 +55,10 @@ function Publish-PSBuildModule { [Alias('ApiKey')] [string]$NuGetApiKey, - [pscredential]$Credential + [PSCredential]$Credential ) - Write-Verbose "Publishing version [$Version] to repository [$Repository]..." + Write-Verbose ($LocalizedData.PublishingVersionToRepository -f $Version, $Repository) $publishParams = @{ Path = $Path diff --git a/PowerShellBuild/Public/Test-PSBuildPester.ps1 b/PowerShellBuild/Public/Test-PSBuildPester.ps1 index 9297971..531e8ed 100644 --- a/PowerShellBuild/Public/Test-PSBuildPester.ps1 +++ b/PowerShellBuild/Public/Test-PSBuildPester.ps1 @@ -31,11 +31,16 @@ function Test-PSBuildPester { .PARAMETER OutputVerbosity The verbosity of output, options are None, Normal, Detailed and Diagnostic. Default is Detailed. .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, @@ -74,7 +79,7 @@ function Test-PSBuildPester { try { if ($ImportModule) { if (-not (Test-Path $ModuleManifest)) { - Write-Error "Unable to find module manifest [$ModuleManifest]. Can't import module" + Write-Error ($LocalizedData.UnableToFindModuleManifest -f $ModuleManifest) } else { # Remove any previously imported project modules and import from the output dir Get-Module $ModuleName | Remove-Module -Force -ErrorAction SilentlyContinue @@ -86,11 +91,11 @@ function Test-PSBuildPester { Import-Module Pester -MinimumVersion 5.0.0 $configuration = [PesterConfiguration]::Default - $configuration.Output.Verbosity = $OutputVerbosity - $configuration.Run.PassThru = $true + $configuration.Output.Verbosity = $OutputVerbosity + $configuration.Run.PassThru = $true $configuration.Run.SkipRemainingOnFailure = $SkipRemainingOnFailure - $configuration.TestResult.Enabled = -not [string]::IsNullOrEmpty($OutputPath) - $configuration.TestResult.OutputPath = $OutputPath + $configuration.TestResult.Enabled = -not [string]::IsNullOrEmpty($OutputPath) + $configuration.TestResult.OutputPath = $OutputPath $configuration.TestResult.OutputFormat = $OutputFormat if ($CodeCoverage.IsPresent) { @@ -98,43 +103,43 @@ function Test-PSBuildPester { if ($CodeCoverageFiles.Count -gt 0) { $configuration.CodeCoverage.Path = $CodeCoverageFiles } - $configuration.CodeCoverage.OutputPath = $CodeCoverageOutputFile + $configuration.CodeCoverage.OutputPath = $CodeCoverageOutputFile $configuration.CodeCoverage.OutputFormat = $CodeCoverageOutputFileFormat } $testResult = Invoke-Pester -Configuration $configuration -Verbose:$VerbosePreference if ($testResult.FailedCount -gt 0) { - throw 'One or more Pester tests failed' + throw $LocalizedData.PesterTestsFailed } if ($CodeCoverage.IsPresent) { - Write-Host "`nCode Coverage:`n" -ForegroundColor Yellow + Write-Host ("`n{0}:`n" -f $LocalizedData.CodeCoverage) -ForegroundColor Yellow if (Test-Path $CodeCoverageOutputFile) { $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." + Write-Error ($LocalizedData.CodeCoverageCodeCoverageFileNotFound -f $CodeCoverageOutputFile) } } } finally { diff --git a/PowerShellBuild/Public/Test-PSBuildScriptAnalysis.ps1 b/PowerShellBuild/Public/Test-PSBuildScriptAnalysis.ps1 index c917d1c..9d47628 100644 --- a/PowerShellBuild/Public/Test-PSBuildScriptAnalysis.ps1 +++ b/PowerShellBuild/Public/Test-PSBuildScriptAnalysis.ps1 @@ -5,17 +5,17 @@ function Test-PSBuildScriptAnalysis { .DESCRIPTION Run PSScriptAnalyzer tests against a module. .PARAMETER Path - Path to PowerShell module directory to run ScriptAnalyser on. + Path to PowerShell module directory to run ScriptAnalyzer on. .PARAMETER SeverityThreshold - Fail ScriptAnalyser test if any issues are found with this threshold or higher. + Fail ScriptAnalyzer test if any issues are found with this threshold or higher. .PARAMETER SettingsPath - Path to ScriptAnalyser settings to use. + Path to ScriptAnalyzer settings to use. .EXAMPLE - PS> Test-PSBuildScriptAnalysis -Path ./Output/Mymodule/0.1.0 -SeverityThreshold Error + PS> Test-PSBuildScriptAnalysis -Path ./Output/MyModule/0.1.0 -SeverityThreshold Error - Run ScriptAnalyzer on built module in ./Output/Mymodule/0.1.0. Throw error if any errors are found. + Run ScriptAnalyzer on built module in ./Output/MyModule/0.1.0. Throw error if any errors are found. #> - [cmdletbinding()] + [CmdletBinding()] param( [parameter(Mandatory)] [string]$Path, @@ -26,15 +26,15 @@ function Test-PSBuildScriptAnalysis { [string]$SettingsPath ) - Write-Verbose "SeverityThreshold set to: $SeverityThreshold" + Write-Verbose ($LocalizedData.SeverityThresholdSetTo -f $SeverityThreshold) $analysisResult = Invoke-ScriptAnalyzer -Path $Path -Settings $SettingsPath -Recurse -Verbose:$VerbosePreference - $errors = ($analysisResult.where({$_Severity -eq 'Error'})).Count - $warnings = ($analysisResult.where({$_Severity -eq 'Warning'})).Count - $infos = ($analysisResult.where({$_Severity -eq 'Information'})).Count + $errors = ($analysisResult.where({ $_Severity -eq 'Error' })).Count + $warnings = ($analysisResult.where({ $_Severity -eq 'Warning' })).Count + $infos = ($analysisResult.where({ $_Severity -eq 'Information' })).Count if ($analysisResult) { - Write-Host 'PSScriptAnalyzer results:' -ForegroundColor Yellow + Write-Host $LocalizedData.PSScriptAnalyzerResults -ForegroundColor Yellow $analysisResult | Format-Table -AutoSize } @@ -44,22 +44,22 @@ function Test-PSBuildScriptAnalysis { } 'Error' { if ($errors -gt 0) { - throw 'One or more ScriptAnalyzer errors were found!' + throw $LocalizedData.ScriptAnalyzerErrors } } 'Warning' { if ($errors -gt 0 -or $warnings -gt 0) { - throw 'One or more ScriptAnalyzer warnings were found!' + throw $LocalizedData.ScriptAnalyzerWarnings } } 'Information' { if ($errors -gt 0 -or $warnings -gt 0 -or $infos -gt 0) { - throw 'One or more ScriptAnalyzer warnings were found!' + throw $LocalizedData.ScriptAnalyzerWarnings } } default { if ($analysisResult.Count -ne 0) { - throw 'One or more ScriptAnalyzer issues were found!' + throw $LocalizedData.ScriptAnalyzerIssues } } } 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/PowerShellBuild/en-US/Messages.psd1 b/PowerShellBuild/en-US/Messages.psd1 new file mode 100644 index 0000000..0d45520 --- /dev/null +++ b/PowerShellBuild/en-US/Messages.psd1 @@ -0,0 +1,26 @@ +ConvertFrom-StringData @' +NoCommandsExported=No commands have been exported. Skipping markdown generation. +FailedToGenerateMarkdownHelp=Failed to generate markdown help. : {0} +AddingFileToPsm1=Adding [{0}] to PSM1 +MakeCabNotAvailable=MakeCab.exe is not available. Cannot create help cab. +DirectoryAlreadyExists=Directory already exists [{0}]. +PathLongerThan3Chars=`$Path [{0}] must be longer than 3 characters. +BuildSystemDetails=Build System Details: +BuildModule=Build Module: {0}`:{1} +PowerShellVersion=PowerShell Version: {0} +EnvironmentVariables={0}`Environment variables: +PublishingVersionToRepository=Publishing version [{0}] to repository [{1}]... +FolderDoesNotExist=Folder does not exist: {0} +PathArgumentMustBeAFolder=The Path argument must be a folder. File paths are not allowed. +UnableToFindModuleManifest=Unable to find module manifest [{0}]. Can't import module +PesterTestsFailed=One or more Pester tests failed +CodeCoverage=Code Coverage +Type=Type +CodeCoverageLessThanThreshold=Code coverage: [{0}] is [{1:p}], which is less than the threshold of [{2:p}] +CodeCoverageCodeCoverageFileNotFound=Code coverage file [{0}] not found. +SeverityThresholdSetTo=SeverityThreshold set to: {0} +PSScriptAnalyzerResults=PSScriptAnalyzer results: +ScriptAnalyzerErrors=One or more ScriptAnalyzer errors were found! +ScriptAnalyzerWarnings=One or more ScriptAnalyzer warnings were found! +ScriptAnalyzerIssues=One or more ScriptAnalyzer issues were found! +'@ diff --git a/README.md b/README.md index 8725468..526f1dc 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 @@ -209,3 +211,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..fc5de25 100644 --- a/tests/build.tests.ps1 +++ b/tests/build.tests.ps1 @@ -1,33 +1,67 @@ # spell-checker:ignore excludeme +BeforeDiscovery { + if ($null -eq $env:BHProjectPath) { + $path = Join-Path -Path $PSScriptRoot -ChildPath '..\build.ps1' + . $path -Task Build + } + $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 + $global: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 +} Describe 'Build' { - BeforeAll { + <# + We prepare the tests by copying the TestModule to a temporary location + and setting the output path to a known location. + #> + + $script:testModuleSource = Join-Path $TestDrive 'TestModule' + Copy-Item $PSScriptRoot/fixtures/TestModule $script:testModuleSource -Recurse + Set-Location $script:testModuleSource # 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') + $script:testModuleOutputPath = [IO.Path]::Combine($script:testModuleSource, 'Output', 'TestModule', '0.1.0') } + + # Capture any of the jobs for cleanup later + [array]$script:jobs = @() + } + + AfterAll { + Set-Location $PSScriptRoot } Context 'Compile module' { BeforeAll { - - Write-Host "PSScriptRoot: $PSScriptRoot" + Write-Host "PSScriptRoot: $script:testModuleSource" 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 { + param($testModuleSource, $outputModVerManifest) + Set-Location -Path $using:testModuleSource + # We want to load the current build of PowerShellBuild so we use a + # global variable to store the output path. + $global:PSBOutput = $outputModVerManifest $global:PSBuildCompile = $true ./build.ps1 -Task Build - } | Wait-Job + } -WorkingDirectory $script:testModuleSource -ArgumentList $testModuleSource, $outputModVerManifest | Wait-Job } - AfterAll { Remove-Item $script:testModuleOutputPath -Recurse -Force + $jobs | Stop-Job -ErrorAction Ignore + $jobs | Remove-Job -ErrorAction Ignore } It 'Creates module' { @@ -70,28 +104,41 @@ Describe 'Build' { Context 'Dot-sourced module' { BeforeAll { + $copyItemSplat = @{ + Path = "$PSScriptRoot/fixtures/DotSource.psm1" + Destination = "$script:testModuleSource/TestModule/TestModule.psm1" + Force = $true + } + # Overwrite the existing PSM1 with the dot-sourced version + Copy-Item @copyItemSplat # 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 { + param($testModuleSource, $outputModVerManifest) + Set-Location -Path $testModuleSource + # We want to load the current build of PowerShellBuild so we use a + # global variable to store the output path. + $global:PSBOutput = $outputModVerManifest $global:PSBuildCompile = $false ./build.ps1 -Task Build - } | Wait-Job + } -WorkingDirectory $script:testModuleSource -ArgumentList $testModuleSource, $outputModVerManifest | Wait-Job } - AfterAll { Remove-Item $script:testModuleOutputPath -Recurse -Force + $jobs | Stop-Job -ErrorAction Ignore + $jobs | Remove-Job -ErrorAction Ignore } It 'Creates module' { $script:testModuleOutputPath | Should -Exist } - It 'Has PSD1 and dot-sourced functions' { - (Get-ChildItem -Path $script:testModuleOutputPath).Count | Should -Be 6 - "$script:testModuleOutputPath/TestModule.psd1" | Should -Exist - "$script:testModuleOutputPath/TestModule.psm1" | Should -Exist - "$script:testModuleOutputPath/Public" | Should -Exist - "$script:testModuleOutputPath/Private" | Should -Exist + It '<_> should exist' -ForEach @( + "TestModule.psd1", + "TestModule.psm1", + "Public", + "Private" + ) { + Join-Path -Path $script:testModuleOutputPath -ChildPath $_ | Should -Exist } It 'Does not contain excluded stuff' { @@ -99,7 +146,66 @@ Describe 'Build' { } It 'Has MAML help XML' { - "$script:testModuleOutputPath/en-US/TestModule-help.xml" | Should -Exist + [IO.Path]::Combine($script:testModuleOutputPath, "en-US", "TestModule-help.xml") | Should -Exist + } + } + Context 'Overwrite Docs' { + BeforeAll { + + Write-Host "PSScriptRoot: $script:testModuleSource" + Write-Host "OutputPath: $script:testModuleOutputPath" + + $copyItemSplat = @{ + Path = "$PSScriptRoot/fixtures/DotSource.psm1" + Destination = "$script:testModuleSource/TestModule/TestModule.psm1" + Force = $true + } + # Overwrite the existing PSM1 with the dot-sourced version + Copy-Item @copyItemSplat + # Build once, and then we'll modify + $script:jobs += Start-Job -ScriptBlock { + param($testModuleSource, $outputModVerManifest) + Set-Location -Path $using:testModuleSource + # We want to load the current build of PowerShellBuild so we use a + # global variable to store the output path. + $global:PSBOutput = $global:outputModVerManifest + $global:PSBuildCompile = $false + ./build.ps1 -Task Build + } -WorkingDirectory $script:testModuleSource -ArgumentList $testModuleSource, $outputModVerManifest | Wait-Job + + # Replace with a different string to test the overwrite + $script:docPath = [IO.Path]::Combine($script:testModuleSource, "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 = [IO.Path]::Combine($script:testModuleSource, "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 { + param($testModuleSource, $outputModVerManifest) + Set-Location -Path $using:testModuleSource + # We want to load the current build of PowerShellBuild so we use a + # global variable to store the output path. + $global:PSBOutput = $global:outputModVerManifest + $global:PSBuildCompile = $false + ./build.ps1 -Task Build + } -WorkingDirectory $script:testModuleSource -ArgumentList $testModuleSource, $outputModVerManifest | Wait-Job + } + + AfterAll { + Remove-Item $script:testModuleOutputPath -Recurse -Force + $jobs | Stop-Job -ErrorAction Ignore + $jobs | Remove-Job -ErrorAction Ignore + } + + 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/fixtures/DotSource.psm1 b/tests/fixtures/DotSource.psm1 new file mode 100644 index 0000000..4a6ddc9 --- /dev/null +++ b/tests/fixtures/DotSource.psm1 @@ -0,0 +1,11 @@ +# Dot source public functions +$private = @(Get-ChildItem -Path ([IO.Path]::Combine($PSScriptRoot, 'Private/*.ps1')) -Recurse) +$public = @(Get-ChildItem -Path ([IO.Path]::Combine($PSScriptRoot, 'Public/*.ps1')) -Recurse) +foreach ($import in $public + $private) { + try { + . $import.FullName + } catch { + throw "Unable to dot source [$($import.FullName)]" + } +} +Export-ModuleMember -Function $public.Basename 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 63% rename from tests/TestModule/psakeFile.ps1 rename to tests/fixtures/TestModule/psakeFile.ps1 index 68e4dbb..86bfa20 100644 --- a/tests/TestModule/psakeFile.ps1 +++ b/tests/fixtures/TestModule/psakeFile.ps1 @@ -1,6 +1,7 @@ -Import-Module ../../Output/PowerShellBuild -Force +$psb = Import-Module $global:PSBOutput -Force -PassThru +Write-Host "Using PowerShellBuild version $($psb.Version)" -properties { +Properties { # Pester can build the module using both scenarios if (Test-Path -Path 'Variable:\PSBuildCompile') { $PSBPreference.Build.CompileModule = $global:PSBuildCompile @@ -15,21 +16,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 56% rename from tests/TestModule/requirements.psd1 rename to tests/fixtures/TestModule/requirements.psd1 index ff8889f..bbeef6c 100644 --- a/tests/TestModule/requirements.psd1 +++ b/tests/fixtures/TestModule/requirements.psd1 @@ -1,23 +1,23 @@ @{ - PSDependOptions = @{ + PSDependOptions = @{ Target = 'CurrentUser' } - 'InvokeBuild' = @{ + 'InvokeBuild' = @{ Version = '5.5.1' } - 'Pester' = @{ - Version = '4.8.1' + 'Pester' = @{ + Version = '4.8.1' Parameters = @{ SkipPublisherCheck = $true } } - 'psake' = @{ + 'psake' = @{ Version = '4.8.0' } - 'BuildHelpers' = @{ + 'BuildHelpers' = @{ Version = '2.0.10' } 'PowerShellBuild' = @{ - Version = '0.5.0' + Version = '0.7.2' } }