diff --git a/.github/copilot-pull-request-description-instructions.md b/.github/copilot-pull-request-description-instructions.md index ecbc496..5763857 100644 --- a/.github/copilot-pull-request-description-instructions.md +++ b/.github/copilot-pull-request-description-instructions.md @@ -37,5 +37,4 @@ References: ## Pull Request description -- Add a summary of the intention of the PR. Use the title and the messages of the commits to create a summary. -- Add a bullet list with all the commit messages in the PR commits. +- Add a bullet list with all the commit messages in the PR commits. Use the full commit message. Do not remove the Semantic header. diff --git a/.gitignore b/.gitignore index 1c72bc5..2fd3534 100755 --- a/.gitignore +++ b/.gitignore @@ -320,4 +320,7 @@ OpenCover/ ASALocalRun/ # MSBuild Binary and Structured Log -*.binlog \ No newline at end of file +*.binlog + +# Mack +**/.DS_Store \ No newline at end of file diff --git a/Test/helper/module.helper.ps1 b/Test/helper/module.helper.ps1 index 670d090..ab8f16e 100644 --- a/Test/helper/module.helper.ps1 +++ b/Test/helper/module.helper.ps1 @@ -1,3 +1,5 @@ +# {"Version":"v1.0.0","Date":"2026-06-14"} +# # Helper for module variables function Find-ModuleRootPath{ @@ -7,19 +9,31 @@ function Find-ModuleRootPath{ [string]$Path ) - $path = Convert-Path -Path $Path + $path = Resolve-Path -Path $Path while (-not [string]::IsNullOrWhiteSpace($Path)){ $psd1 = Get-ChildItem -Path $Path -Filter *.psd1 | Select-Object -First 1 if ($psd1 | Test-Path) { + # Skip if module found is Test if($psd1.BaseName -eq "Test"){ #foudn testing module. Continue $path = $path | Split-Path -Parent continue } + # It can happen that on a parent folder of a folder there is a lost psd1 file. + # Confirm that the psd1 file name equals the folder name + # Found this bug because on the test machine there was a psd1 on a tempo folder parent of the testing folder. + # This may break modules where the psd1 does not match the folder name + $folderName = Split-Path -Path $path -Leaf + if ($psd1.BaseName -ne $folderName) { + # psd1 file no on folder name + $path = $path | Split-Path -Parent + continue + } + # foudn module return $path } diff --git a/Test/include/Mock-SaveParameterToVariable.ps1 b/Test/include/Mock-SaveParameterToVariable.ps1 new file mode 100644 index 0000000..3fe6134 --- /dev/null +++ b/Test/include/Mock-SaveParameterToVariable.ps1 @@ -0,0 +1,69 @@ +# Mock-SaveParameterToVariable +# +# Allows mocking commands using a temp varible for the content. +# +# When the parameters for an invoke is too big as string, we can use a json +# as a single parameter to hold all the parameters wanted. +# +# We can use this Mock functions to mock the calls to this invokes that use a json to +# transfer an object as parameter. + +function Reset-MockSaveParameterToVariable { + + $script:varCommands = @{} +} + +function Get-MockSavedParameterFromVariable ($Command){ + + $result = $script:varCommands[$Command] + + if ($nulll -eq $result) { + throw "Command [$Command] was not initialized." + } + + return $result + +} + +function Invoke-MockSaveParameterToVariable{ + param( + [Parameter(Position=0)][string] $Command, + [Parameter(Position=1)][string] $json, + [Parameter(Position=2)][string] $FileName, + [Parameter()][switch] $ashashtable + ) + + # Save call to Variable + $script:varCommands.$Command += $json + + # Return some value from FielName if provided + if(-Not [string]::IsNullOrWhiteSpace($FileName)){ + + $ret = Get-MockFileContentJson -filename $FileName -asHashtable:$ashashtable + + return $ret + } + +} Export-ModuleMember -Function Invoke-MockSaveParameterToVariable + +function MockCallJson_SaveParametersToVariable{ + param( + [Parameter(Position=0)][string] $command, + [Parameter(Position=2)][string] $fileName, + [Parameter()][switch] $ashashtable + ) + + # Prepare variable to receive calls + $script:varCommands.$Command = @() + + # Mock call back leaving {json} as the parameter to be replaced by the call + $mockCommand = 'Invoke-MockSaveParameterToVariable "{command}" ''{json}'' "{fileName}" -ashashtable:${ashashtable}' + $mockCommand = $mockCommand -replace "{command}", $command + $mockCommand = $mockCommand -replace "{fileName}", $fileName + $mockCommand = $mockCommand -replace "{json}", '{json}' + $mockCommand = $mockCommand -replace "{ashashtable}", $ashashtable.IsPresent.ToString() + + # Set invoke command mock with the prepared command + Set-InvokeCommandMock -Alias $command -Command $mockCommand + + } \ No newline at end of file diff --git a/Test/include/base64.ps1 b/Test/include/base64.ps1 new file mode 100644 index 0000000..d21262a --- /dev/null +++ b/Test/include/base64.ps1 @@ -0,0 +1,24 @@ +function ConvertTo-Base64 { + [CmdletBinding()] + param ( + [Parameter(Mandatory,ValueFromPipeline, Position=0)][string[]]$Text + ) + + process{ + # Encode the string to base64 + $ret = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($Text)) + return $ret + } +} + +function ConvertFrom-Base64 { + [CmdletBinding()] + param ( + [Parameter(Mandatory,ValueFromPipeline, Position=0)][string[]]$Text + ) + + process{ + $ret = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Text)) + return $ret + } +} \ No newline at end of file diff --git a/Test/include/callPrivateContext.ps1 b/Test/include/callPrivateContext.ps1 index d4aabd9..0fdb27e 100644 --- a/Test/include/callPrivateContext.ps1 +++ b/Test/include/callPrivateContext.ps1 @@ -16,7 +16,7 @@ function Invoke-PrivateContext { [Parameter(Mandatory, Position = 0)] [scriptblock]$ScriptBlock, [string]$ModulePath, - [string[]]$Arguments + [object[]]$Arguments ) if ([string]::IsNullOrEmpty($ModulePath)) { diff --git a/Test/include/invokeCommand.mock.ps1 b/Test/include/invokeCommand.mock.ps1 index 3bf6773..ad8f4c7 100644 --- a/Test/include/invokeCommand.mock.ps1 +++ b/Test/include/invokeCommand.mock.ps1 @@ -1,3 +1,6 @@ +# {"Version":"1.0.0","Date":"2026-06-17","HeaderVersion":1} +# DO NOT MODIFY THIS FILE MANUALLY. IT IS GENERATED BY THE IncludeHelper MODULE. +# # INVOKE COMMAND MOCK # @@ -200,6 +203,17 @@ function MockCallToString{ Set-InvokeCommandMock -Alias $command -Command $outputstring } +function MockCallToBool{ + param( + [Parameter(Position=0)][string] $command, + [Parameter(Position=1)][bool] $OutBool + ) + + $outputstring = $outBool ? 'return $true' : 'return $false' + + Set-InvokeCommandMock -Alias $command -Command $outputstring +} + function MockCallToObject{ param( @@ -242,11 +256,6 @@ function MockCallExpression{ [Parameter(Position=1)][string] $expression ) - $mockCommand = @' - Invoke-Expression -Command '{expression}' -'@ - $mockCommand = $mockCommand -replace "{expression}", $expression - Set-InvokeCommandMock -Alias $command -Command $expression } diff --git a/Test/public/MyWrite.test.ps1 b/Test/public/MyWrite.test.ps1 index 3f6002c..f99f1d5 100644 --- a/Test/public/MyWrite.test.ps1 +++ b/Test/public/MyWrite.test.ps1 @@ -6,7 +6,7 @@ function Test_WriteMyHost_Singleline { Write-MyHost -Message "This is a test transcript." } - $result = Stop-MyTranscript + $result = @(Stop-MyTranscript) Assert-AreEqual -Expected "This is a test transcript." -Presented $result @@ -20,7 +20,7 @@ function Test_WriteMyHost_Multiline { Write-MyHost -Message "This is a test transcript 0" Write-MyHost -Message "This is a test transcript 1" } - $result = Stop-MyTranscript + $result = @(Stop-MyTranscript) Assert-Count -Expected 2 -Presented $result Assert-AreEqual -Expected "This is a test transcript 0" -Presented $result[0] @@ -109,7 +109,7 @@ function Test_EnableMyDebug_All{ } -Arguments $text0,$text1 - $result = Stop-MyTranscript + $result = @(Stop-MyTranscript) Assert-Count -Expected 2 -Presented $result Assert-DbgMsg $result[0] "none" $text0 @@ -117,8 +117,6 @@ function Test_EnableMyDebug_All{ } - - function Test_EnableMyDebug_All_Sections{ Enable-IncludeHelperDebug @@ -136,7 +134,7 @@ function Test_EnableMyDebug_All_Sections{ } -Arguments $text0,$text1 - $result = Stop-MyTranscript + $result = @(Stop-MyTranscript) Assert-Count -Expected 2 -Presented $result Assert-DbgMsg $result[0] "section0" $text0 @@ -162,7 +160,7 @@ function Test_EnableMyDebug_Sections{ } -Arguments $text0,$text1,$text2 - $result = Stop-MyTranscript + $result = @(Stop-MyTranscript) Assert-Count -Expected 2 -Presented $result Assert-DbgMsg $result[0] "section0" $text0 @@ -188,7 +186,7 @@ function Test_EnableMyDebug_All_Filter{ } -Arguments $text0,$text1,$text2 - $result = Stop-MyTranscript + $result = @(Stop-MyTranscript) Assert-Count -Expected 2 -Presented $result Assert-DbgMsg $result[0] "section0" $text0 @@ -216,7 +214,7 @@ function Test_EnableMyDebug_All_Filter_morethanone{ } -Arguments $text0,$text1,$text2 ,$text3 - $result = Stop-MyTranscript + $result = @(Stop-MyTranscript) Assert-Count -Expected 2 -Presented $result Assert-DbgMsg $result[0] "section0" $text0 @@ -247,7 +245,7 @@ function Test_EnableMyDebug_All_LoggingFilePath{ } -Arguments $text0,$text1,$text2 ,$text3 - $result = Stop-MyTranscript + $result = @(Stop-MyTranscript) Assert-Count -Expected 4 -Presented $result @@ -259,6 +257,62 @@ function Test_EnableMyDebug_All_LoggingFilePath{ Assert-DbgMsg $result[3] "section3" $text3 } +function Test_EnableMyDebug_SectionWithAll{ + + Enable-IncludeHelperDebug -Sections "algoconall" + + $text0 = "Debug message 0" + $text1 = "Debug message 1" + $text2 = "Debug message 2" + $text3 = "Debug message 3" + + Start-MyTranscript + + Invoke-PrivateContext { + param($Arguments) + + Write-MyDebug -Message $Arguments[0] -Section "section0" + Write-MyDebug -Message $Arguments[1] -Section "section1" + Write-MyDebug -Message $Arguments[2] -Section "algoconall" + Write-MyDebug -Message $Arguments[3] -Section "section3" + + } -Arguments $text0,$text1,$text2 ,$text3 + + $result = @(Stop-MyTranscript) + + Assert-Count -Expected 1 -Presented $result + Assert-DbgMsg $result[0] "algoconall" $text2 +} + +function Test_EnableMyDebug_SectionUpperCase{ + + Enable-IncludeHelperDebug -Sections "alGocOnall" + + $text0 = "Debug message 0" + $text1 = "Debug message 1" + $text2 = "Debug message 2" + $text3 = "Debug message 3" + + Start-MyTranscript + + Invoke-PrivateContext { + param($Arguments) + + Write-MyDebug -Message $Arguments[0] -Section "section0" + Write-MyDebug -Message $Arguments[1] -Section "section1" + Write-MyDebug -Message $Arguments[2] -Section "alGocOnall" + Write-MyDebug -Message $Arguments[3] -Section "algoconall" + Write-MyDebug -Message $Arguments[3] -Section "section3" + + } -Arguments $text0,$text1,$text2 ,$text3 + + $result = @(Stop-MyTranscript) + + Assert-Count -Expected 2 -Presented $result + Assert-DbgMsg $result[0] "algoconall" $text2 + Assert-DbgMsg $result[1] "algoconall" $text3 +} + function Assert-DbgMsg($Presented,$Section,$Message){ Assert-IsTrue -Condition ($Presented -match "^\[\d{2}:\d{2}:\d{2}\.\d{3}\]\[IncludeHelper\]\[D\]\[$Section\] $Message$") diff --git a/Test/public/copyIncludeToWorkspace.test.ps1 b/Test/public/copyIncludeToWorkspace.test.ps1 index 4b7de89..08786f7 100644 --- a/Test/public/copyIncludeToWorkspace.test.ps1 +++ b/Test/public/copyIncludeToWorkspace.test.ps1 @@ -4,7 +4,7 @@ function Test_CopyIncludeToWorkspace{ New-ModuleV3 -Name TestModule # Test for Include - $name = "sync.Helper.ps1" + $name = "deploy.Helper.ps1" $folderPath = "tools" $destinationModulePath = "TestModule" diff --git a/Test/public/dependencies.test.ps1 b/Test/public/dependencies.test.ps1 index 791cb9b..0c866de 100644 --- a/Test/public/dependencies.test.ps1 +++ b/Test/public/dependencies.test.ps1 @@ -80,7 +80,7 @@ function Test_ImportDepepency_Import_From_Module_Manager{ Enable-IncludeHelperVerbose Start-MyTranscript $output = Import-Dependency -Name $name -Verbose -Confirm:$false - $result = Stop-MyTranscript + $result = @(Stop-MyTranscript) Disable-IncludeHelperVerbose #Assert verbose message @@ -122,7 +122,7 @@ function Test_ImportDepepency_Install_From_Gallery{ Enable-IncludeHelperVerbose Start-MyTranscript $output = Import-Dependency -Name $name -Verbose -Confirm:$false - $result = Stop-MyTranscript + $result = @(Stop-MyTranscript) Disable-IncludeHelperVerbose #Assert verbose message @@ -157,7 +157,7 @@ function Test_ImportDependency_Clone_From_GitHub{ Enable-IncludeHelperVerbose Start-MyTranscript $output = Import-Dependency -Name $name -Verbose -Confirm:$false - $result = Stop-MyTranscript + $result = @(Stop-MyTranscript) Disable-IncludeHelperVerbose #Assert verbose message diff --git a/Test/public/getfileversion.test.ps1 b/Test/public/getfileversion.test.ps1 new file mode 100644 index 0000000..c1ce95b --- /dev/null +++ b/Test/public/getfileversion.test.ps1 @@ -0,0 +1,62 @@ +function Test_GetFileVersion_EmptyFile{ + + touch "TestFile.ps1" + + # Act + $result = Get-IncludeFileVersion -Path "TestFile.ps1" + + Assert-IsNull -Object $result +} + +function Test_GetFileVersion_NoVersion{ + + + New-Testingfile -Name "TestFile.ps1" -Content $FILE_FACKE_CONTENT + + # Act + $result = Get-IncludeFileVersion -Path "TestFile.ps1" + + # Assert + Assert-IsNull -Object $result + +} + +function Test_GetFileVersion_OnlyVersion{ + + # Arrange version in file + $headerversion = 1 + $version = "1.0.0" + $date = "1975-02-18" + $json = Build-TestFileVersionJson $Version $Date $headerversion + New-Testingfile -Name "TestFile.ps1" -Content $("# $json") + Assert-FileVersion -Path "TestFile.ps1" -Version $version -Date $date + + # Act + $result = Get-IncludeFileVersion -Path "TestFile.ps1" + + # Assert + Assert-IsNotNull -Object $result + Assert-AreEqual -Expected $version -Presented $result.Version + Assert-AreEqual -Expected $date -Presented $result.Date +} + +function Test_GetFileVersion_VersionAndBody{ + + # Arrange version in file + $headerversion = 1 + $version = "1.0.0" + $date = "1975-02-18" + $header = Build-TestVersionHeader $Version $Date $headerversion + $body = $FILE_FACKE_CONTENT + $content = $header + $body | Out-String + New-Testingfile -Name "TestFile.ps1" -Content $content + Assert-FileVersion -Path "TestFile.ps1" -Version $version -Date $date -Body $body + + # Act + $result = Get-IncludeFileVersion -Path "TestFile.ps1" + + # Assert + Assert-IsNotNull -Object $result + Assert-AreEqual -Expected $version -Presented $result.Version + Assert-AreEqual -Expected $date -Presented $result.Date +} \ No newline at end of file diff --git a/Test/public/listIncludes.test.ps1 b/Test/public/listIncludes.test.ps1 index e3f9330..4d3ad8f 100644 --- a/Test/public/listIncludes.test.ps1 +++ b/Test/public/listIncludes.test.ps1 @@ -42,12 +42,12 @@ function Test_GetIncludeSystemFiles { @{ FolderName = "Root" ; Name = "deploy.ps1" } @{ FolderName = "Root" ; Name = "LICENSE" } @{ FolderName = "Root" ; Name = "release.ps1" } - @{ FolderName = "Root" ; Name = "sync.ps1" } + # @{ FolderName = "Root" ; Name = "sync.ps1" } @{ FolderName = "Root" ; Name = "test.ps1" } # Tools @{ FolderName = "Tools" ; Name = "deploy.Helper.ps1" } - @{ FolderName = "Tools" ; Name = "sync.Helper.ps1" } + # @{ FolderName = "Tools" ; Name = "sync.Helper.ps1" } # TestRoot @{ FolderName = "TestRoot" ; Name = "Test.psm1" } diff --git a/Test/public/module.helper.test.ps1 b/Test/public/module.helper.test.ps1 index 330d3c6..a4abb4f 100644 --- a/Test/public/module.helper.test.ps1 +++ b/Test/public/module.helper.test.ps1 @@ -80,28 +80,30 @@ function Test_FindModuleRootPath{ New-TestingFolder -Path "$moduleName/Test/include/kk1/kk2" New-testingFolder -Path "kk1/kk2/kk3" - $moduleRootPath = $moduleName | Convert-Path - + # Act + # All this paths should return $ModuleRootPath they are folders and child folders of the module @( "$moduleName", "$moduleName/Test", - "$moduleName//include/kk1/kk2", + "$moduleName/include/kk1/kk2", "$moduleName/Test/include/kk1/kk2" - ) | foreach{ - $path = $_ - $result = Find-ModuleRootPath -Path $path - Assert-AreEqual -Expected $moduleRootPath -Presented $result + ) | ForEach-Object{ + $path = $_ + $result = Find-ModuleRootPath -Path $path + Assert-AreEqualPath -Expected $moduleName -Presented $result } + # Act + # All this paths should return null they are not folders or child folders of the module @( ".", "kk1", "kk1/kk2", "kk1/kk2/kk3" - ) | foreach{ - $path = $_ - $result = Find-ModuleRootPath -Path $path - Assert-IsNull -Object $result + ) | ForEach-Object{ + $path = $_ + $result = Find-ModuleRootPath -Path $path + Assert-IsNull -Object $result } } \ No newline at end of file diff --git a/Test/public/setfileversion.test.ps1 b/Test/public/setfileversion.test.ps1 new file mode 100644 index 0000000..c859531 --- /dev/null +++ b/Test/public/setfileversion.test.ps1 @@ -0,0 +1,153 @@ +function Test_SetFileVersion_EmptyFile{ + + $version = "1.0.0" + + touch "TestFile.ps1" + + # Act + $result = Set-IncludeFileVersion -Path "TestFile.ps1" -Version $version + + # Assert + Assert-IsTrue -Condition $result + Assert-FileVersion -Path "TestFile.ps1" -Version $version +} + +function Test_SetFileVersion_NoVersion{ + + $version = "1.0.0" + + New-Testingfile -Name "TestFile.ps1" -Content $FILE_FACKE_CONTENT + + # Act + $result = Set-IncludeFileVersion -Path "TestFile.ps1" -Version $version + + # Assert + Assert-IsTrue -Condition $result + Assert-FileVersion -Path "TestFile.ps1" -Version $version -Body $FILE_FACKE_CONTENT +} + + +function Test_SetFileVersion_OnlyVersion{ + + $version = "3.2.1" + + # Arrange version in file + $headerversion = 1 + $version = "1.0.0" + $date = "1975-02-18" + $json = Build-TestFileVersionJson $Version $Date $headerversion + New-Testingfile -Name "TestFile.ps1" -Content $("# $json") + Assert-FileVersion -Path "TestFile.ps1" -Version $version -Date $date + + # Act + $result = Set-IncludeFileVersion -Path "TestFile.ps1" -Version $version + + # Assert + Assert-IsTrue -Condition $result + Assert-FileVersion -Path "TestFile.ps1" -Version $version +} + +function Test_SetFileVersion_VersionAndBody{ + + $version = "3.2.1" + + # Arrange version in file + $headerversion = 1 + $version = "1.0.0" + $date = "1975-02-18" + $json = Build-TestFileVersionJson $Version $Date $headerversion + New-Testingfile -Name "TestFile.ps1" -Content $("# $json") + Assert-FileVersion -Path "TestFile.ps1" -Version $version -Date $date + + # Act + $result = Set-IncludeFileVersion -Path "TestFile.ps1" -Version $version + + # Assert + Assert-IsTrue -Condition $result + Enable-IncludeHelperDebug -Section "Assert-FileVersion" + Assert-FileVersion -Path "TestFile.ps1" -Version $version -Body $body + Disable-IncludeHelperDebug +} + +$FILE_FACKE_CONTENT = @' +# This is text +# Not sure if I will + + +morethings or less +'@ + +function Assert-FileVersion{ + param( + [string]$Path, + [string]$Version, + [string]$Date = (Get-Date -Format "yyyy-MM-dd"), + [string]$Body + ) + + $headerversion = 1 + $headeersizeinlines = $script:TEST_HEADER_V1.Count + + $content = Get-Content -Path $Path | Out-String + $json = Build-TestFileVersionJson $Version $Date $headerversion + $expected = "# $json" + + $content = @(Get-Content -Path $Path) + # "Content of file $Path :" | Write-MyDebug -Section "Assert-FileVersion" -Object $content + + # Assert first line + Assert-AreEqual -Expected $expected -Presented $content[0] + + # Assert content if provided + if(-not [string]::IsNullOrWhiteSpace($Body)){ + # "Comparing body" | Write-MyDebug -Section "Assert-FileVersion" + # Remove the first line from content + $rest = $content | Select-Object -Skip $headeersizeinlines + # join string[] to string + $rest = $rest -join "`n" + # "Body presented:" | Write-MyDebug -Section "Assert-FileVersion" -Object $Body + # "Rest Content :" | Write-MyDebug -Section "Assert-FileVersion" -Object $rest + Assert-AreEqual -Expected $Body -Presented $rest -Comment "Rest of the file content should be the same" + } +} + +function Build-TestFileVersionJson($Version, $Date, $Headerversion){ + + # Need to create the full object to control the order of members for mocking + if(-not [string]::IsNullOrWhiteSpace($Headerversion)){ + $json = [pscustomobject]@{ + Version = $Version + Date = $Date + HeaderVersion = $Headerversion + } + } else { + $json = [pscustomobject]@{ + Version = $Version + Date = $Date + } + } + + $ret = $json | ConvertTo-Json -Depth 3 -Compress + + return $ret +} + +$script:TEST_HEADER_V1 = @( + '# {"Version":"{version}","Date":"{date}","HeaderVersion":{headerversion}}', + "# DO NOT MODIFY THIS FILE MANUALLY. IT IS GENERATED BY THE IncludeHelper MODULE." +) + +function Build-TestVersionHeader($version, $Date, $headerversion){ + # Sync this code with Build-VersionHeader at public/setfileversion.ps1 + + $headerversion = $headerversion + $header = $script:TEST_HEADER_V1 + + # replace the {json} placeholder with the actual json + + $header = $header | ForEach-Object { $_ -replace "{headerversion}", "$headerversion" } + $header = $header | ForEach-Object { $_ -replace "{version}", $Version } + $header = $header | ForEach-Object { $_ -replace "{date}", $Date } + + return $header +} \ No newline at end of file diff --git a/Test/public/transcriptHelp.test.ps1 b/Test/public/transcriptHelp.test.ps1 index 6898dfe..a6641d5 100644 --- a/Test/public/transcriptHelp.test.ps1 +++ b/Test/public/transcriptHelp.test.ps1 @@ -12,7 +12,7 @@ function Test_transcript{ # } # catch {} - $result = Stop-MyTranscript + $result = @(Stop-MyTranscript) Assert-Contains -Expected "This is a test transcript." -Presented $result Assert-Contains -Expected "WARNING: This is a warning message." -Presented $result @@ -28,7 +28,7 @@ function Test_ExportTranscriptLines{ Write-Host "Line 2" Write-Host "Line 3" - $result = Stop-MyTranscript + $result = @(Stop-MyTranscript) # Assert $expectedLines = @( diff --git a/helper/module.helper.ps1 b/helper/module.helper.ps1 index 670d090..ab8f16e 100644 --- a/helper/module.helper.ps1 +++ b/helper/module.helper.ps1 @@ -1,3 +1,5 @@ +# {"Version":"v1.0.0","Date":"2026-06-14"} +# # Helper for module variables function Find-ModuleRootPath{ @@ -7,19 +9,31 @@ function Find-ModuleRootPath{ [string]$Path ) - $path = Convert-Path -Path $Path + $path = Resolve-Path -Path $Path while (-not [string]::IsNullOrWhiteSpace($Path)){ $psd1 = Get-ChildItem -Path $Path -Filter *.psd1 | Select-Object -First 1 if ($psd1 | Test-Path) { + # Skip if module found is Test if($psd1.BaseName -eq "Test"){ #foudn testing module. Continue $path = $path | Split-Path -Parent continue } + # It can happen that on a parent folder of a folder there is a lost psd1 file. + # Confirm that the psd1 file name equals the folder name + # Found this bug because on the test machine there was a psd1 on a tempo folder parent of the testing folder. + # This may break modules where the psd1 does not match the folder name + $folderName = Split-Path -Path $path -Leaf + if ($psd1.BaseName -ne $folderName) { + # psd1 file no on folder name + $path = $path | Split-Path -Parent + continue + } + # foudn module return $path } diff --git a/include/MyWrite.ps1 b/include/MyWrite.ps1 index 1ecc794..607b4ac 100644 --- a/include/MyWrite.ps1 +++ b/include/MyWrite.ps1 @@ -1,3 +1,6 @@ +# {"Version":"1.0.2","Date":"2026-06-16","HeaderVersion":1} +# DO NOT MODIFY THIS FILE MANUALLY. IT IS GENERATED BY THE IncludeHelper MODULE. +# # Include MyWrite.ps1 # Provides Write-MyError, Write-MyWarning, Write-MyVerbose, Write-MyHost, Write-MyDebug # and Test-MyVerbose, Test-MyDebug functions for consistent logging and debugging output. @@ -64,7 +67,10 @@ function Clear-MyHost { [CmdletBinding()] param() - Clear-Host +© # If debug do not clear host page + if (-not (Test-MyDebug -section "donotclearhost")) { + Clear-Host + } } function Write-MyDebug { @@ -84,10 +90,8 @@ function Write-MyDebug { $objString = $Object | Get-ObjetString $message = $message + " - " + $objString } - $timestamp = Get-Date -Format 'HH:mm:ss.fff' - # Write on host - $logMessage ="[$timestamp][$MODULE_NAME][D][$section] $message" + $logMessage = normalizeMessage -Message $message -Section $section -Tag "D" $logMessage | Write-ToConsole -Color $DEBUG_COLOR $logMessage | Write-MyDebugLogging @@ -95,6 +99,39 @@ function Write-MyDebug { } } +function normalizeMessage { + param( + [Parameter(Mandatory, ValueFromPipeline)][string]$Message, + #section + [Parameter(Position = 1)][string]$Section, + # tag + [Parameter(Position = 2)][string]$Tag + ) + + process{ + $timestamp = Get-Date -Format 'HH:mm:ss.fff' + + $message = remove-secrets -Message $message + + return "[$timestamp][$MODULE_NAME][$Tag][$Section] $Message" + } +} + +function remove-secrets { + param( + [Parameter(Mandatory, ValueFromPipeline)][string]$Message + ) + + process{ + + # Remove GitHub tokens (ghp_ or gho_ or ghu_ or ghs_) + $tokenPattern = "gh[opsu]_[A-Za-z0-9_]{36,255}" + $message = [regex]::Replace($message, $tokenPattern, "gh***") + + return $message + } +} + function Write-MyDebugLogging { param( [Parameter(Position = 1, ValueFromPipeline)][string]$LogMessage @@ -187,13 +224,11 @@ function Test-MyDebug { if($flags.Count -eq 0){ return $false } - $flags = $flags.ToLower() - $section = $section.ToLower() - return ($flags.Contains("all")) -or ( $flags -eq $section) + return ($flags.Contains("all")) -or ( $flags.Contains("$section")) } - + $section = $section.ToLower() $sectionsString = get-DebugSections # No configuration means no debug @@ -269,10 +304,17 @@ function getSectionsFromSectionsString($sectionsString){ $list = $sectionsString.Split(" ", [StringSplitOptions]::RemoveEmptyEntries) + # make all string in $list lowercase + $list = $list | ForEach-Object { $_.ToLower() } + + # Split between allow and filter. Filter sections that start with '-' $split = @($list).Where({ $_ -like '-*' }, 'Split') - $sections.filter = $split[0] | ForEach-Object { $_ -replace '^-', '' } # -> API, Auth - $sections.allow = $split[1] # -> Sync, Cache + # Get sections that are filtered. Remove the '-' from the beginning of the section name + $sections.filter = @($split[0] | ForEach-Object { $_ -replace '^-', '' }) # -> API, Auth + + # Get allow sections + $sections.allow = @($split[1]) # -> Sync, Cache return $sections } diff --git a/include/base64.ps1 b/include/base64.ps1 new file mode 100644 index 0000000..ad5ab12 --- /dev/null +++ b/include/base64.ps1 @@ -0,0 +1,29 @@ +# Base64 +# +# function that convert from and to base64 string using powershell standard libraries, + + +function ConvertTo-Base64 { + [CmdletBinding()] + param ( + [Parameter(Mandatory,ValueFromPipeline, Position=0)][string[]]$Text + ) + + process{ + # Encode the string to base64 + $ret = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($Text)) + return $ret + } +} + +function ConvertFrom-Base64 { + [CmdletBinding()] + param ( + [Parameter(Mandatory,ValueFromPipeline, Position=0)][string[]]$Text + ) + + process{ + $ret = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Text)) + return $ret + } +} \ No newline at end of file diff --git a/include/callAPI.ps1 b/include/callAPI.ps1 index f99c1e7..a92b71e 100644 --- a/include/callAPI.ps1 +++ b/include/callAPI.ps1 @@ -71,8 +71,11 @@ function Invoke-RestAPI { param( [Parameter(Mandatory)][string]$Api, [Parameter()][string]$Token, + [Parameter()][ValidateSet("GET","POST","PATCH","PUT","DELETE")][string]$Method = "GET", [Parameter()] [string]$ApiHost, - [Parameter()] [string]$PageSize = 30 + [Parameter()] [string]$PageSize = 30, + [Parameter()] [object]$Body + ) ">>>" | writedebug @@ -93,14 +96,34 @@ function Invoke-RestAPI { $uriBuilder.Scheme = "https" $uriBuilder.Host = $apiHost $uriBuilder.Path = $api - $uriBuilder.Query = "?per_page=$PageSize" - - $url = $uriBuilder.Uri.AbsoluteUri - + # Send the request $start = Get-Date - ">>> Invoke-RestMethod - $url" | writedebug - $response = Invoke-RestMethod -Uri $url -Method Get -Headers $headers -ResponseHeadersVariable responseHeaders + if($Method -eq "PATCH"){ + $url = $uriBuilder.Uri.AbsoluteUri + $bodyJson = $Body | ConvertTo-Json -Depth 100 + $params = @{ + Uri = $url + Method = "PATCH" + Headers = $headers + Body = $bodyJson + ResponseHeadersVariable = "responseHeaders" + } + } + else { + # Check the paging + $uriBuilder.Query = "?per_page=$PageSize" + $url = $uriBuilder.Uri.AbsoluteUri + $params = @{ + Uri = $url + Method = "GET" + Headers = $headers + ResponseHeadersVariable = "responseHeaders" + } + } + + ">>> Invoke-RestMethod - $url" | writedebug -Object $params + $response = Invoke-RestMethod @params $responseHeaders | writedebug @@ -136,7 +159,7 @@ function Invoke-RestAPI { catch { throw } -} +} Export-ModuleMember -Function Invoke-RestAPI #################################################################################################### @@ -180,7 +203,7 @@ function Get-ApiToken { ) if(![string]::IsNullOrWhiteSpace($Token)){ - "Token provided" | Write-TraceApi + "Token provided" | writedebug return $Token } @@ -215,7 +238,11 @@ function Get-EnvVariable{ return $null } - $ret = "Env:$Name" + $ret = [System.Environment]::GetEnvironmentVariable($Name) + + if([string]::IsNullOrWhiteSpace($ret)){ + return $null + } return $ret } @@ -225,10 +252,11 @@ function Get-EnvVariable{ function writedebug{ [CmdletBinding()] param( - [Parameter(ValueFromPipeline)][string]$Message + [Parameter(ValueFromPipeline)][string]$Message, + [Parameter()][object]$Object ) process{ - Write-MyDebug $Message -Section "api" + Write-MyDebug $Message -Section "api" -object:$Object } } diff --git a/include/functionParameterHelper.ps1 b/include/functionParameterHelper.ps1 new file mode 100644 index 0000000..d9abb5e --- /dev/null +++ b/include/functionParameterHelper.ps1 @@ -0,0 +1,69 @@ +# Register-ArgumentCompleter +# +# To control the parametrs autofil we can register a scriptblock variable with a function to manage +# the parametrs. this function should return the possible values. +# Use ConvertTo-CompleteResults to generate the output objects from a list of valid strings +# +# Sample code: +# +# Register-ArgumentCompleter -CommandName -ParameterName -ScriptBlock $ValidParameterValues +# +# $ValidParameterValues = { +# param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) +# +# $list = @( +# "Value1", +# "Value2", +# "Value3" +# ) +# +# $list | ConvertTo-CompleteResults -wordToComplete $wordToComplete +# } + +function ConvertTo-CompleteResults{ + param( + [parameter(Mandatory, ValueFromPipeline,Position=0)][string[]]$Values, + [string]$wordToComplete, + [switch]$StartWith + ) + + begin { + $pattern = $StartWith.IsPresent ? "$wordToComplete*" : "*$wordToComplete*" + + $list = @() + } + + process{ + $list += $Values + } + + end{ + + "Processing $($list.Count) values for completion with pattern '$pattern'" | Write-Mydebug -Section "ArgumentCompleter" + + $ret = $list | Where-Object { $_ -like $pattern } | Select-Object -Unique + + "Returning $($ret.Count) values matching '$pattern'" | Write-Mydebug -Section "ArgumentCompleter" -object $ret + + $ret = $ret | addQuotesIfSpaces + + $ret | ForEach-Object { [System.Management.Automation.CompletionResult]::new( $_, $_, 'ParameterValue', $_) } + } +} + +function addQuotesIfSpaces { + param( + [parameter(Mandatory, ValueFromPipeline,Position=0)][string]$Value + ) + + process{ + + if($Value -like "* *"){ + "Adding quotes to value '$Value'" | Write-Mydebug -Section "ArgumentCompleter" + return "`"$Value`"" + } + else{ + return $Value + } + } +} \ No newline at end of file diff --git a/public/getfileversion.ps1 b/public/getfileversion.ps1 new file mode 100644 index 0000000..c47eb6b --- /dev/null +++ b/public/getfileversion.ps1 @@ -0,0 +1,33 @@ +function Get-IncludeFileVersion{ + [CmdletBinding()] + param( + [string]$Path + ) + + # Return of file does not exist + if(-not (Test-Path -Path $Path)){ + throw "File $Path not found" + } + + $content = Get-Content -Path $Path + + # If file is empty no version can be found + if($null -eq $content){ + return $null + } + + $version = Get-VersionHeader -content $content + + if($version){ + $ret = [PsCustomObject]@{ + Version = $version.Version + Date = $version.Date + } + } else { + $ret = $null + } + + return $ret + +} Export-ModuleMember -Function Get-IncludeFileVersion + diff --git a/public/setfileversion.ps1 b/public/setfileversion.ps1 new file mode 100644 index 0000000..d06b4c0 --- /dev/null +++ b/public/setfileversion.ps1 @@ -0,0 +1,119 @@ +function Set-IncludeFileVersion{ + [CmdletBinding()] + [outputtype([bool])] + param( + [string]$Path, + [string]$Version, + [string]$Date + ) + + # Return of file does not exist + if(-not (Test-Path -Path $Path)){ + throw "File $Path not found" + } + + # initialize date + $Date = [string]::IsNullOrEmpty($Date) ? (Get-Date).ToString("yyyy-MM-dd") : $Date + + # Read actual content + $content = Get-Content -Path $Path + + # Get actual version + $actualVersion = $null -eq $content ? $null : $(Get-VersionHeader -content $content) + + if($actualVersion){ + # Check if actual version is the same as the new version, if so return true + if(($actualVersion.Version -eq $Version) -and ($actualVersion.Date -eq $Date)){ + return $true + } + } + + # If version remove header + if($actualVersion){ + $content = Remove-VersionHeader -content $content + } + + # Build Target version line + $tragetFirstLine = Build-VersionHeader -Version $Version -Date $Date + + # Add the new version header as the first line of the file + $newContent = @($tragetFirstLine) + $content + + # Set value and return true + # Do not use Set-Content as it adds a last line break, use Out-File instead + $newContent -join "`n" | Out-File -Path $Path -NoNewline + return $true + +} Export-ModuleMember -Function Set-IncludeFileVersion + +$script:HEADER_V1 = @( + '# {"Version":"{version}","Date":"{date}","HeaderVersion":{headerversion}}', + "# DO NOT MODIFY THIS FILE MANUALLY. IT IS GENERATED BY THE IncludeHelper MODULE." +) + +function Build-VersionHeader_V1{ + [alias("Build-VersionHeader")] + param( + [string]$Version, + [string]$Date + ) + + $headerversion = 1 + $header = $script:HEADER_V1 + + # replace the {json} placeholder with the actual json + + $header = $header | ForEach-Object { $_ -replace "{headerversion}", "$headerversion" } + $header = $header | ForEach-Object { $_ -replace "{version}", $Version } + $header = $header | ForEach-Object { $_ -replace "{date}", $Date } + + return $header +} + +function Remove-VersionHeader($content){ + + $headerSizeV0 = 1 + $headersSizeV1 = $script:HEADER_V1.Count + + $version = Get-VersionHeader -content $content + + if(-not $version){ + throw "No version header found in content" + } + + switch ($version.HeaderVersion){ + $null { + $content = $content | Select-Object -Skip $headerSizeV0 + } + 1 { + # Remove first 2 lines + $content = $content | Select-Object -Skip $headersSizeV1 + } + default { + throw "Unknown version header version $($version.HeaderVersion)" + } + } + + return $content +} + +function Get-VersionHeader($content){ + + # So far the version json is always on the first line + + # Get first line + $firstline = $content | Select-Object -First 1 + + # Empty first line + if([string]::IsNullOrWhiteSpace($firstline)){ + return $null + } + + # Remove comment char + $json = $firstline.Substring(1) + + try { $version = $json | ConvertFrom-Json } catch { return $null } + + return $version +} + diff --git a/sync.ps1 b/sync.ps1 deleted file mode 100644 index ca51c06..0000000 --- a/sync.ps1 +++ /dev/null @@ -1,33 +0,0 @@ -<# -.SYNOPSIS - Synchronizes with TestingHelper templates files - -.DESCRIPTION - Synchronizes with TestingHelper templates to the local repo. - TestingHelper uses templates to create a new module. - This script will update the local module with the latest templates. -.LINK - https://raw.githubusercontent.com/rulasg/TestingHelper/main/sync.ps1 -#> - -[cmdletbinding(SupportsShouldProcess)] -param() - -$MODULE_PATH = $PSScriptRoot -$TOOLS_PATH = $MODULE_PATH | Join-Path -ChildPath "tools" -$WORKFLOW_PATH = $MODULE_PATH | Join-Path -ChildPath ".github" -AdditionalChildPath "workflows" - -. ($TOOLS_PATH | Join-Path -ChildPath "sync.Helper.ps1") - -Save-UrlContentToFile -File 'deploy_module_on_release.yml' -Folder $WORKFLOW_PATH -Url 'https://raw.githubusercontent.com/rulasg/TestingHelper/main/private/templates/template.v3.deploy_module_on_release.yml' -Save-UrlContentToFile -File 'powershell.yml' -Folder $WORKFLOW_PATH -Url 'https://raw.githubusercontent.com/rulasg/TestingHelper/main/private/templates/template.v3.powershell.yml' -Save-UrlContentToFile -File 'test_with_TestingHelper.yml' -Folder $WORKFLOW_PATH -Url 'https://raw.githubusercontent.com/rulasg/TestingHelper/main/private/templates/template.v3.test_with_TestingHelper.yml' - -Save-UrlContentToFile -File 'deploy.Helper.ps1' -Folder $TOOLS_PATH -Url 'https://raw.githubusercontent.com/rulasg/TestingHelper/main/private/templates/template.v3.deploy.Helper.ps1' -Save-UrlContentToFile -File 'deploy.ps1' -Folder $MODULE_PATH -Url 'https://raw.githubusercontent.com/rulasg/TestingHelper/main/private/templates/template.v3.deploy.ps1' - -Save-UrlContentToFile -File 'sync.Helper.ps1' -Folder $TOOLS_PATH -Url 'https://raw.githubusercontent.com/rulasg/TestingHelper/main/private/templates/template.v3.sync.Helper.ps1' -Save-UrlContentToFile -File 'sync.ps1' -Folder $MODULE_PATH -Url 'https://raw.githubusercontent.com/rulasg/TestingHelper/main/private/templates/template.v3.sync.ps1' - -Save-UrlContentToFile -File 'release.ps1' -Folder $MODULE_PATH -Url 'https://raw.githubusercontent.com/rulasg/TestingHelper/main/private/templates/template.v3.release.ps1' -Save-UrlContentToFile -File 'test.ps1' -Folder $MODULE_PATH -Url 'https://raw.githubusercontent.com/rulasg/TestingHelper/main/private/templates/template.v3.test.ps1' diff --git a/tools/sync.Helper.ps1 b/tools/sync.Helper.ps1 deleted file mode 100644 index deb1b6c..0000000 --- a/tools/sync.Helper.ps1 +++ /dev/null @@ -1,61 +0,0 @@ -<# -.SYNOPSIS - Helper functions to Synchronize TestingHelper templates files - -.DESCRIPTION - Helper functions Synchronize TestingHelper templates to the local repo. - TestingHelper uses templates to create a new module. - This script will update the local module with the latest templates. -.LINK - https://raw.githubusercontent.com/rulasg/DemoPsModule/main/sync.ps1 -#> - -[cmdletbinding()] -param() - -function Get-UrlContent { - [cmdletbinding()] - param( - [Parameter(Mandatory=$true)][string]$url - ) - $wc = New-Object -TypeName System.Net.WebClient - $fileContent = $wc.DownloadString($url) - - return $fileContent -} - -function Out-ContentToFile { - [cmdletbinding(SupportsShouldProcess)] - param( - [Parameter(ValueFromPipeline)][string]$content, - [Parameter(Mandatory=$true)][string]$filePath - ) - - process{ - - if ($PSCmdlet.ShouldProcess($filePath, "Save content [{0}] to file" -f $content.Length)) { - $content | Out-File -FilePath $filePath -Force - } - } -} - -function Save-UrlContentToFile { - [cmdletbinding(SupportsShouldProcess)] - param( - [Parameter(Mandatory=$true)][string]$Url, - [Parameter(Mandatory=$true)][string]$File, - [Parameter()][string]$Folder - ) - - $fileContent = Get-UrlContent -Url $url - - if ([string]::IsNullOrWhiteSpace($fileContent)) { - Write-Error -Message "Content from [$url] is empty" - return - } - - $filePath = $Folder ? (Join-Path -Path $Folder -ChildPath $File) : $File - - Set-Content -Path $filePath -Value $fileContent - Write-Information -MessageData "Saved content to [$filePath] from [$url]" -}