From 74d07fcf3948abf4af1570cb35aace0205e33974 Mon Sep 17 00:00:00 2001 From: Gilbert Sanchez Date: Tue, 22 Jul 2025 16:47:29 -0700 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=E2=9C=A8=20Add=20localization=20su?= =?UTF-8?q?pport=20and=20improve=20error=20handling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Introduced `Messages.psd1` for localized messages. * Updated functions to utilize localized messages for warnings and errors. * Improved formatting and readability in several functions. * Ensured consistent use of `[CmdletBinding()]` across all functions. --- PowerShellBuild/PowerShellBuild.psm1 | 10 +- .../Public/Build-PSBuildMAMLHelp.ps1 | 4 +- .../Public/Build-PSBuildMarkdown.ps1 | 6 +- .../Public/Build-PSBuildModule.ps1 | 115 +++++++++++++----- .../Public/Build-PSBuildUpdatableHelp.ps1 | 33 +++-- .../Public/Clear-PSBuildOutputFolder.ps1 | 10 +- PowerShellBuild/Public/Initialize-PSBuild.ps1 | 41 +++++-- .../Public/Publish-PSBuildModule.ps1 | 22 ++-- PowerShellBuild/Public/Test-PSBuildPester.ps1 | 50 ++++---- .../Public/Test-PSBuildScriptAnalysis.ps1 | 30 ++--- PowerShellBuild/en-US/Messages.psd1 | 26 ++++ 11 files changed, 231 insertions(+), 116 deletions(-) create mode 100644 PowerShellBuild/en-US/Messages.psd1 diff --git a/PowerShellBuild/PowerShellBuild.psm1 b/PowerShellBuild/PowerShellBuild.psm1 index 1726b41..e6b2e9d 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,14 @@ foreach ($import in $public + $private) { } } +$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..f5ae020 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, @@ -23,7 +23,7 @@ function Build-PSBuildMAMLHelp { [string]$DestinationPath ) - $helpLocales = (Get-ChildItem -Path $Path -Directory).Name + $helpLoc2ales = (Get-ChildItem -Path $Path -Directory).Name # Generate the module's primary MAML help file foreach ($locale in $helpLocales) { 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..dd4708d 100644 --- a/PowerShellBuild/Public/Publish-PSBuildModule.ps1 +++ b/PowerShellBuild/Public/Publish-PSBuildModule.ps1 @@ -27,18 +27,18 @@ 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')] + [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 ($LocalizedData.PathDoesNotExist -f $_) + } + if (-not (Test-Path -Path $_ -PathType Container)) { + throw $LocalizedData.PathArgumentMustBeAFolder + } + $true + })] [System.IO.FileInfo]$Path, [parameter(Mandatory)] @@ -50,10 +50,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..3dbd4a9 100644 --- a/PowerShellBuild/Public/Test-PSBuildPester.ps1 +++ b/PowerShellBuild/Public/Test-PSBuildPester.ps1 @@ -31,11 +31,11 @@ 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()] param( [parameter(Mandatory)] [string]$Path, @@ -74,7 +74,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 +86,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 +98,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) - } - }) + '{0}: [{1}]: {2:p}' -f $LocalizedData.Type, $_.name, $_.percent + if ($_.percent -lt $CodeCoverageThreshold) { + $ccFailMsgs += ($LocalizedData.CodeCoverageLessThanThreshold -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/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! +'@ From 4ccbef4596118098919d048a41bccfbcc4866ec7 Mon Sep 17 00:00:00 2001 From: Gilbert Sanchez Date: Tue, 22 Jul 2025 16:48:09 -0700 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=E2=9C=A8=20Add=20localization=20su?= =?UTF-8?q?pport=20to=20CHANGELOG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Documented the addition of localization support in the Unreleased section. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92685cf..53a93ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Add new dependencies variables to allow end user to modify which tasks are run. +- Add localization support. ## [0.7.2] 2025-05-21 From 79cced782504fdfb6a909e3f4e037e851cbb5211 Mon Sep 17 00:00:00 2001 From: Gilbert Sanchez Date: Thu, 24 Jul 2025 17:45:13 -0700 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Correct=20variable=20?= =?UTF-8?q?name=20typo=20in=20Build-PSBuildMAMLHelp=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fixed the typo in the variable name from `$helpLoc2ales` to `$helpLocales`.\ * Added search exclusion patterns in `.vscode/settings.json`. * Reformatted command arguments in `.vscode/tasks.json` for better readability. * Introduced a new task `Bootstrap` in `.vscode/tasks.json` for initialization. * Updated `PowerShellBuild` version in `requirements.psd1` to `0.7.2`. * Enhanced test assertions to provide clearer output and ensure all expected files exist. * Comment out incorrect output path assignment for GitHub Actions. * Added debug statements to log the output path and contents of the `TestModule` after build. * Removed redundant count test for PSD1 and dot-sourced functions with specific file look ups. * Add LocalizedData to PSM incase of load failure --- .github/workflows/test.yml | 8 ++++- .vscode/settings.json | 10 +++++- .vscode/tasks.json | 25 ++++++++++--- PowerShellBuild/PowerShellBuild.psm1 | 29 +++++++++++++++ .../Public/Build-PSBuildMAMLHelp.ps1 | 2 +- tests/TestModule/requirements.psd1 | 14 ++++---- tests/build.tests.ps1 | 35 ++++++++++++------- 7 files changed, 95 insertions(+), 28 deletions(-) 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/settings.json b/.vscode/settings.json index 227163e..bce2856 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,5 +6,13 @@ "powershell.codeFormatting.addWhitespaceAroundPipe": true, "powershell.codeFormatting.useCorrectCasing": true, "powershell.codeFormatting.newLineAfterOpenBrace": true, - "powershell.codeFormatting.alignPropertyValuePairs": true + "powershell.codeFormatting.alignPropertyValuePairs": true, + "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..ca72255 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" ] + "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/PowerShellBuild/PowerShellBuild.psm1 b/PowerShellBuild/PowerShellBuild.psm1 index e6b2e9d..87a2200 100644 --- a/PowerShellBuild/PowerShellBuild.psm1 +++ b/PowerShellBuild/PowerShellBuild.psm1 @@ -9,6 +9,35 @@ 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' diff --git a/PowerShellBuild/Public/Build-PSBuildMAMLHelp.ps1 b/PowerShellBuild/Public/Build-PSBuildMAMLHelp.ps1 index f5ae020..20d42a6 100644 --- a/PowerShellBuild/Public/Build-PSBuildMAMLHelp.ps1 +++ b/PowerShellBuild/Public/Build-PSBuildMAMLHelp.ps1 @@ -23,7 +23,7 @@ function Build-PSBuildMAMLHelp { [string]$DestinationPath ) - $helpLoc2ales = (Get-ChildItem -Path $Path -Directory).Name + $helpLocales = (Get-ChildItem -Path $Path -Directory).Name # Generate the module's primary MAML help file foreach ($locale in $helpLocales) { diff --git a/tests/TestModule/requirements.psd1 b/tests/TestModule/requirements.psd1 index ff8889f..bbeef6c 100644 --- a/tests/TestModule/requirements.psd1 +++ b/tests/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' } } diff --git a/tests/build.tests.ps1 b/tests/build.tests.ps1 index 9bbbdc3..6022d47 100644 --- a/tests/build.tests.ps1 +++ b/tests/build.tests.ps1 @@ -6,9 +6,11 @@ Describe 'Build' { # 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:testModuleSource = [IO.Path]::Combine($PSScriptRoot, 'TestModule') $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:testModuleSource = [IO.Path]::Combine($PSScriptRoot, 'TestModule') + $script:testModuleOutputPath = [IO.Path]::Combine($script:testModuleSource, 'Output', 'TestModule', '0.1.0') } } @@ -19,11 +21,11 @@ Describe 'Build' { 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 + Start-Job -Scriptblock { + Set-Location -Path $using:testModuleSource $global:PSBuildCompile = $true ./build.ps1 -Task Build - } | Wait-Job + } -WorkingDirectory $script:testModuleSource | Wait-Job } AfterAll { @@ -71,11 +73,17 @@ 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 + Start-Job -Scriptblock { + Set-Location -Path $using:testModuleSource $global:PSBuildCompile = $false ./build.ps1 -Task Build - } | Wait-Job + } -WorkingDirectory $script:testModuleSource | Wait-Job + Write-Debug "TestModule output path: $script:testModuleSource" + $items = Get-ChildItem -Path $script:testModuleSource -Recurse -File + Write-Debug ($items | Format-Table FullName | Out-String) + Write-Debug "TestModule output path: $script:testModuleOutputPath" + $items = Get-ChildItem -Path $script:testModuleOutputPath -Recurse -File + Write-Debug ($items | Format-Table FullName | Out-String) } AfterAll { @@ -86,12 +94,13 @@ Describe 'Build' { $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' {