diff --git a/.github/workflows/Action-Test.yml b/.github/workflows/Action-Test.yml index 81733bc..a471c31 100644 --- a/.github/workflows/Action-Test.yml +++ b/.github/workflows/Action-Test.yml @@ -7,6 +7,8 @@ on: [pull_request] env: GH_TOKEN: ${{ github.token }} +permissions: {} + jobs: ActionTestDefault: name: Action-Test - [Default] @@ -15,24 +17,13 @@ jobs: - name: Checkout repo uses: actions/checkout@v4 - - name: Checkout tests -> PSModuleTest - uses: actions/checkout@v4 - with: - repository: PSModule/PSModuleTest - path: tests - - - name: Delete outputs - shell: pwsh - run: | - Remove-Item -Path tests/outputs -Recurse -Force -Verbose - - name: Initialize environment uses: PSModule/Initialize-PSModule@main - name: Action-Test uses: ./ with: - Name: PSModule + Name: PSModuleTest Path: tests/src ModulesOutputPath: tests/outputs/modules DocsOutputPath: tests/outputs/docs diff --git a/.github/workflows/Linter.yml b/.github/workflows/Linter.yml index 89ac29a..cfd32e6 100644 --- a/.github/workflows/Linter.yml +++ b/.github/workflows/Linter.yml @@ -4,6 +4,11 @@ run-name: "Linter - [${{ github.event.pull_request.title }} #${{ github.event.pu on: [pull_request] +permissions: + contents: read + packages: read + statuses: write # To report GitHub Actions status checks + jobs: Lint: name: Lint code base @@ -11,8 +16,10 @@ jobs: steps: - name: Checkout repo uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Lint code base - uses: github/super-linter@latest + uses: super-linter/super-linter@latest env: GITHUB_TOKEN: ${{ github.token }} diff --git a/scripts/helpers/Build/Build-PSModuleManifest.ps1 b/scripts/helpers/Build/Build-PSModuleManifest.ps1 index 31cabc6..55a0456 100644 --- a/scripts/helpers/Build/Build-PSModuleManifest.ps1 +++ b/scripts/helpers/Build/Build-PSModuleManifest.ps1 @@ -333,8 +333,11 @@ function Build-PSModuleManifest { Show-FileContent -Path $outputManifestPath Stop-LogGroup - Start-LogGroup 'Build manifest file - Result - After format' + Start-LogGroup 'Build manifest file - Format' Set-ModuleManifest -Path $outputManifestPath + Stop-LogGroup + + Start-LogGroup 'Build manifest file - Result - After format' Show-FileContent -Path $outputManifestPath Stop-LogGroup diff --git a/tests/src/PSModuleTest/PSModuleTest.psd1 b/tests/src/PSModuleTest/PSModuleTest.psd1 new file mode 100644 index 0000000..466eca8 --- /dev/null +++ b/tests/src/PSModuleTest/PSModuleTest.psd1 @@ -0,0 +1,4 @@ +@{ + ModuleVersion = '0.0.0' + RootModule = 'PSModuleTest.psm1' +} diff --git a/tests/src/PSModuleTest/PSModuleTest.psm1 b/tests/src/PSModuleTest/PSModuleTest.psm1 new file mode 100644 index 0000000..ab67c5e --- /dev/null +++ b/tests/src/PSModuleTest/PSModuleTest.psm1 @@ -0,0 +1,73 @@ +[Cmdletbinding()] +param() + +Write-Verbose 'Importing subcomponents' +$Folders = 'init', 'classes', 'private', 'public' +# Import everything in these folders +Foreach ($Folder in $Folders) { + $Root = Join-Path -Path $PSScriptRoot -ChildPath $Folder + Write-Verbose "Processing folder: $Root" + if (Test-Path -Path $Root) { + Write-Verbose "Getting all files in $Root" + $Files = $null + $Files = Get-ChildItem -Path $Root -Include '*.ps1', '*.psm1' -Recurse + # dot source each file + foreach ($File in $Files) { + Write-Verbose "Importing $($File)" + Import-Module $File + Write-Verbose "Importing $($File): Done" + } + } +} + +. "$PSScriptRoot\finally.ps1" + +# Define the types to export with type accelerators. +$ExportableTypes = @( + [Book] + [BookList] +) + +# Get the internal TypeAccelerators class to use its static methods. +$TypeAcceleratorsClass = [psobject].Assembly.GetType( + 'System.Management.Automation.TypeAccelerators' +) +# Ensure none of the types would clobber an existing type accelerator. +# If a type accelerator with the same name exists, throw an exception. +$ExistingTypeAccelerators = $TypeAcceleratorsClass::Get +foreach ($Type in $ExportableTypes) { + if ($Type.FullName -in $ExistingTypeAccelerators.Keys) { + $Message = @( + "Unable to register type accelerator '$($Type.FullName)'" + 'Accelerator already exists.' + ) -join ' - ' + + throw [System.Management.Automation.ErrorRecord]::new( + [System.InvalidOperationException]::new($Message), + 'TypeAcceleratorAlreadyExists', + [System.Management.Automation.ErrorCategory]::InvalidOperation, + $Type.FullName + ) + } +} +# Add type accelerators for every exportable type. +foreach ($Type in $ExportableTypes) { + $TypeAcceleratorsClass::Add($Type.FullName, $Type) +} +# Remove type accelerators when the module is removed. +$MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = { + foreach ($Type in $ExportableTypes) { + $TypeAcceleratorsClass::Remove($Type.FullName) + } +}.GetNewClosure() + +$Param = @{ + Function = (Get-ChildItem -Path "$PSScriptRoot\public" -Include '*.ps1' -Recurse).BaseName + Variable = '*' + Cmdlet = '*' + Alias = '*' +} + +Write-Verbose 'Exporting module members' + +Export-ModuleMember @Param diff --git a/tests/src/PSModuleTest/assemblies/LsonLib.dll b/tests/src/PSModuleTest/assemblies/LsonLib.dll new file mode 100644 index 0000000..3661807 Binary files /dev/null and b/tests/src/PSModuleTest/assemblies/LsonLib.dll differ diff --git a/tests/src/PSModuleTest/classes/Book.ps1 b/tests/src/PSModuleTest/classes/Book.ps1 new file mode 100644 index 0000000..3e22c27 --- /dev/null +++ b/tests/src/PSModuleTest/classes/Book.ps1 @@ -0,0 +1,132 @@ +class Book { + # Class properties + [string] $Title + [string] $Author + [string] $Synopsis + [string] $Publisher + [datetime] $PublishDate + [int] $PageCount + [string[]] $Tags + # Default constructor + Book() { $this.Init(@{}) } + # Convenience constructor from hashtable + Book([hashtable]$Properties) { $this.Init($Properties) } + # Common constructor for title and author + Book([string]$Title, [string]$Author) { + $this.Init(@{Title = $Title; Author = $Author }) + } + # Shared initializer method + [void] Init([hashtable]$Properties) { + foreach ($Property in $Properties.Keys) { + $this.$Property = $Properties.$Property + } + } + # Method to calculate reading time as 2 minutes per page + [timespan] GetReadingTime() { + if ($this.PageCount -le 0) { + throw 'Unable to determine reading time from page count.' + } + $Minutes = $this.PageCount * 2 + return [timespan]::new(0, $Minutes, 0) + } + # Method to calculate how long ago a book was published + [timespan] GetPublishedAge() { + if ( + $null -eq $this.PublishDate -or + $this.PublishDate -eq [datetime]::MinValue + ) { throw 'PublishDate not defined' } + + return (Get-Date) - $this.PublishDate + } + # Method to return a string representation of the book + [string] ToString() { + return "$($this.Title) by $($this.Author) ($($this.PublishDate.Year))" + } +} + +class BookList { + # Static property to hold the list of books + static [System.Collections.Generic.List[Book]] $Books + # Static method to initialize the list of books. Called in the other + # static methods to avoid needing to explicit initialize the value. + static [void] Initialize() { [BookList]::Initialize($false) } + static [bool] Initialize([bool]$force) { + if ([BookList]::Books.Count -gt 0 -and -not $force) { + return $false + } + + [BookList]::Books = [System.Collections.Generic.List[Book]]::new() + + return $true + } + # Ensure a book is valid for the list. + static [void] Validate([book]$Book) { + $Prefix = @( + 'Book validation failed: Book must be defined with the Title,' + 'Author, and PublishDate properties, but' + ) -join ' ' + if ($null -eq $Book) { throw "$Prefix was null" } + if ([string]::IsNullOrEmpty($Book.Title)) { + throw "$Prefix Title wasn't defined" + } + if ([string]::IsNullOrEmpty($Book.Author)) { + throw "$Prefix Author wasn't defined" + } + if ([datetime]::MinValue -eq $Book.PublishDate) { + throw "$Prefix PublishDate wasn't defined" + } + } + # Static methods to manage the list of books. + # Add a book if it's not already in the list. + static [void] Add([Book]$Book) { + [BookList]::Initialize() + [BookList]::Validate($Book) + if ([BookList]::Books.Contains($Book)) { + throw "Book '$Book' already in list" + } + + $FindPredicate = { + param([Book]$b) + + $b.Title -eq $Book.Title -and + $b.Author -eq $Book.Author -and + $b.PublishDate -eq $Book.PublishDate + }.GetNewClosure() + if ([BookList]::Books.Find($FindPredicate)) { + throw "Book '$Book' already in list" + } + + [BookList]::Books.Add($Book) + } + # Clear the list of books. + static [void] Clear() { + [BookList]::Initialize() + [BookList]::Books.Clear() + } + # Find a specific book using a filtering scriptblock. + static [Book] Find([scriptblock]$Predicate) { + [BookList]::Initialize() + return [BookList]::Books.Find($Predicate) + } + # Find every book matching the filtering scriptblock. + static [Book[]] FindAll([scriptblock]$Predicate) { + [BookList]::Initialize() + return [BookList]::Books.FindAll($Predicate) + } + # Remove a specific book. + static [void] Remove([Book]$Book) { + [BookList]::Initialize() + [BookList]::Books.Remove($Book) + } + # Remove a book by property value. + static [void] RemoveBy([string]$Property, [string]$Value) { + [BookList]::Initialize() + $Index = [BookList]::Books.FindIndex({ + param($b) + $b.$Property -eq $Value + }.GetNewClosure()) + if ($Index -ge 0) { + [BookList]::Books.RemoveAt($Index) + } + } +} diff --git a/tests/src/PSModuleTest/data/Config.psd1 b/tests/src/PSModuleTest/data/Config.psd1 new file mode 100644 index 0000000..fea4466 --- /dev/null +++ b/tests/src/PSModuleTest/data/Config.psd1 @@ -0,0 +1,3 @@ +@{ + RandomKey = 'RandomValue' +} diff --git a/tests/src/PSModuleTest/data/Settings.psd1 b/tests/src/PSModuleTest/data/Settings.psd1 new file mode 100644 index 0000000..bcfa7b4 --- /dev/null +++ b/tests/src/PSModuleTest/data/Settings.psd1 @@ -0,0 +1,3 @@ +@{ + RandomSetting = 'RandomSettingValue' +} diff --git a/tests/src/PSModuleTest/finally.ps1 b/tests/src/PSModuleTest/finally.ps1 new file mode 100644 index 0000000..e51c226 --- /dev/null +++ b/tests/src/PSModuleTest/finally.ps1 @@ -0,0 +1,3 @@ +Write-Verbose '------------------------------' -Verbose +Write-Verbose '--- THIS IS A LAST LOADER ---' -Verbose +Write-Verbose '------------------------------' -Verbose diff --git a/tests/src/PSModuleTest/formats/CultureInfo.Format.ps1xml b/tests/src/PSModuleTest/formats/CultureInfo.Format.ps1xml new file mode 100644 index 0000000..a715e08 --- /dev/null +++ b/tests/src/PSModuleTest/formats/CultureInfo.Format.ps1xml @@ -0,0 +1,37 @@ + + + + + System.Globalization.CultureInfo + + System.Globalization.CultureInfo + + + + + 16 + + + 16 + + + + + + + + LCID + + + Name + + + DisplayName + + + + + + + + diff --git a/tests/src/PSModuleTest/formats/Mygciview.Format.ps1xml b/tests/src/PSModuleTest/formats/Mygciview.Format.ps1xml new file mode 100644 index 0000000..4c972c2 --- /dev/null +++ b/tests/src/PSModuleTest/formats/Mygciview.Format.ps1xml @@ -0,0 +1,65 @@ + + + + + mygciview + + System.IO.DirectoryInfo + System.IO.FileInfo + + + PSParentPath + + + + + + 7 + Left + + + + 26 + Right + + + + 26 + Right + + + + 14 + Right + + + + Left + + + + + + + + ModeWithoutHardLink + + + LastWriteTime + + + CreationTime + + + Length + + + Name + + + + + + + + diff --git a/tests/src/PSModuleTest/header.ps1 b/tests/src/PSModuleTest/header.ps1 new file mode 100644 index 0000000..cc1fde9 --- /dev/null +++ b/tests/src/PSModuleTest/header.ps1 @@ -0,0 +1,3 @@ +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidLongLines', '', Justification = 'Contains long links.')] +[CmdletBinding()] +param() diff --git a/tests/src/PSModuleTest/init/initializer.ps1 b/tests/src/PSModuleTest/init/initializer.ps1 new file mode 100644 index 0000000..f4121d2 --- /dev/null +++ b/tests/src/PSModuleTest/init/initializer.ps1 @@ -0,0 +1,3 @@ +Write-Verbose '-------------------------------' -Verbose +Write-Verbose '--- THIS IS AN INITIALIZER ---' -Verbose +Write-Verbose '-------------------------------' -Verbose diff --git a/tests/src/PSModuleTest/modules/OtherPSModule.psm1 b/tests/src/PSModuleTest/modules/OtherPSModule.psm1 new file mode 100644 index 0000000..9e4353b --- /dev/null +++ b/tests/src/PSModuleTest/modules/OtherPSModule.psm1 @@ -0,0 +1,19 @@ +Function Get-OtherPSModule { + <# + .SYNOPSIS + Performs tests on a module. + + .DESCRIPTION + A longer description of the function. + + .EXAMPLE + Get-OtherPSModule -Name 'World' + #> + [CmdletBinding()] + param( + # Name of the person to greet. + [Parameter(Mandatory)] + [string] $Name + ) + Write-Output "Hello, $Name!" +} diff --git a/tests/src/PSModuleTest/private/Get-InternalPSModule.ps1 b/tests/src/PSModuleTest/private/Get-InternalPSModule.ps1 new file mode 100644 index 0000000..3366e44 --- /dev/null +++ b/tests/src/PSModuleTest/private/Get-InternalPSModule.ps1 @@ -0,0 +1,18 @@ +Function Get-InternalPSModule { + <# + .SYNOPSIS + Performs tests on a module. + + .EXAMPLE + Test-PSModule -Name 'World' + + "Hello, World!" + #> + [CmdletBinding()] + param ( + # Name of the person to greet. + [Parameter(Mandatory)] + [string] $Name + ) + Write-Output "Hello, $Name!" +} diff --git a/tests/src/PSModuleTest/private/Set-InternalPSModule.ps1 b/tests/src/PSModuleTest/private/Set-InternalPSModule.ps1 new file mode 100644 index 0000000..11c2fa1 --- /dev/null +++ b/tests/src/PSModuleTest/private/Set-InternalPSModule.ps1 @@ -0,0 +1,22 @@ +Function Set-InternalPSModule { + <# + .SYNOPSIS + Performs tests on a module. + + .EXAMPLE + Test-PSModule -Name 'World' + + "Hello, World!" + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseShouldProcessForStateChangingFunctions', '', Scope = 'Function', + Justification = 'Reason for suppressing' + )] + [CmdletBinding()] + param ( + # Name of the person to greet. + [Parameter(Mandatory)] + [string] $Name + ) + Write-Output "Hello, $Name!" +} diff --git a/tests/src/PSModuleTest/public/Get-PSModuleTest.ps1 b/tests/src/PSModuleTest/public/Get-PSModuleTest.ps1 new file mode 100644 index 0000000..0e9aacf --- /dev/null +++ b/tests/src/PSModuleTest/public/Get-PSModuleTest.ps1 @@ -0,0 +1,20 @@ +#Requires -Modules Utilities + +function Get-PSModuleTest { + <# + .SYNOPSIS + Performs tests on a module. + + .EXAMPLE + Test-PSModule -Name 'World' + + "Hello, World!" + #> + [CmdletBinding()] + param ( + # Name of the person to greet. + [Parameter(Mandatory)] + [string] $Name + ) + Write-Output "Hello, $Name!" +} diff --git a/tests/src/PSModuleTest/public/New-PSModuleTest.ps1 b/tests/src/PSModuleTest/public/New-PSModuleTest.ps1 new file mode 100644 index 0000000..7f26215 --- /dev/null +++ b/tests/src/PSModuleTest/public/New-PSModuleTest.ps1 @@ -0,0 +1,24 @@ +#Requires -Modules @{ModuleName='PSSemVer'; ModuleVersion='1.0'} + +function New-PSModuleTest { + <# + .SYNOPSIS + Performs tests on a module. + + .EXAMPLE + Test-PSModule -Name 'World' + + "Hello, World!" + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseShouldProcessForStateChangingFunctions', '', Scope = 'Function', + Justification = 'Reason for suppressing' + )] + [CmdletBinding()] + param ( + # Name of the person to greet. + [Parameter(Mandatory)] + [string] $Name + ) + Write-Output "Hello, $Name!" +} diff --git a/tests/src/PSModuleTest/public/Set-PSModuleTest.ps1 b/tests/src/PSModuleTest/public/Set-PSModuleTest.ps1 new file mode 100644 index 0000000..a87ac11 --- /dev/null +++ b/tests/src/PSModuleTest/public/Set-PSModuleTest.ps1 @@ -0,0 +1,22 @@ +function Set-PSModuleTest { + <# + .SYNOPSIS + Performs tests on a module. + + .EXAMPLE + Test-PSModule -Name 'World' + + "Hello, World!" + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseShouldProcessForStateChangingFunctions', '', Scope = 'Function', + Justification = 'Reason for suppressing' + )] + [CmdletBinding()] + param ( + # Name of the person to greet. + [Parameter(Mandatory)] + [string] $Name + ) + Write-Output "Hello, $Name!" +} diff --git a/tests/src/PSModuleTest/public/Test-PSModuleTest.ps1 b/tests/src/PSModuleTest/public/Test-PSModuleTest.ps1 new file mode 100644 index 0000000..26be2b9 --- /dev/null +++ b/tests/src/PSModuleTest/public/Test-PSModuleTest.ps1 @@ -0,0 +1,18 @@ +function Test-PSModuleTest { + <# + .SYNOPSIS + Performs tests on a module. + + .EXAMPLE + Test-PSModule -Name 'World' + + "Hello, World!" + #> + [CmdletBinding()] + param ( + # Name of the person to greet. + [Parameter(Mandatory)] + [string] $Name + ) + Write-Output "Hello, $Name!" +} diff --git a/tests/src/PSModuleTest/scripts/loader.ps1 b/tests/src/PSModuleTest/scripts/loader.ps1 new file mode 100644 index 0000000..29ad42f --- /dev/null +++ b/tests/src/PSModuleTest/scripts/loader.ps1 @@ -0,0 +1,3 @@ +Write-Verbose '-------------------------' -Verbose +Write-Verbose '--- THIS IS A LOADER ---' -Verbose +Write-Verbose '-------------------------' -Verbose diff --git a/tests/src/PSModuleTest/types/DirectoryInfo.Types.ps1xml b/tests/src/PSModuleTest/types/DirectoryInfo.Types.ps1xml new file mode 100644 index 0000000..aef538b --- /dev/null +++ b/tests/src/PSModuleTest/types/DirectoryInfo.Types.ps1xml @@ -0,0 +1,21 @@ + + + + System.IO.FileInfo + + + Status + Success + + + + + System.IO.DirectoryInfo + + + Status + Success + + + + diff --git a/tests/src/PSModuleTest/types/FileInfo.Types.ps1xml b/tests/src/PSModuleTest/types/FileInfo.Types.ps1xml new file mode 100644 index 0000000..4cfaf6b --- /dev/null +++ b/tests/src/PSModuleTest/types/FileInfo.Types.ps1xml @@ -0,0 +1,14 @@ + + + + System.IO.FileInfo + + + Age + + ((Get-Date) - ($this.CreationTime)).Days + + + + +