diff --git a/README.md b/README.md index 6319793..98104be 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,60 @@ -# {{ NAME }} +# ElvUI -{{ DESCRIPTION }} +A PowerShell module for installing and updating the [ElvUI](https://www.tukui.org/elvui) addon +for [World of Warcraft](https://worldofwarcraft.blizzard.com/). ## Prerequisites -This uses the following external resources: +- Windows PowerShell 5.1 or PowerShell 7+ +- A World of Warcraft installation - The [PSModule framework](https://github.com/PSModule/Process-PSModule) for building, testing and publishing the module. ## Installation -To install the module from the PowerShell Gallery, you can use the following command: +To install the module from the PowerShell Gallery: ```powershell -Install-PSResource -Name {{ NAME }} -Import-Module -Name {{ NAME }} +# PowerShell 7.4+ (Install-PSResource is built-in) +Install-PSResource -Name ElvUI +Import-Module -Name ElvUI + +# Windows PowerShell 5.1 (use Install-Module instead) +Install-Module -Name ElvUI -Scope CurrentUser +Import-Module -Name ElvUI ``` ## Usage -Here is a list of example that are typical use cases for the module. +### Update ElvUI to the latest version -### Example 1: Greet an entity +```powershell +Update-ElvUI +``` -Provide examples for typical commands that a user would like to do with the module. +### Force reinstall even if already up to date ```powershell -Greet-Entity -Name 'World' -Hello, World! +Update-ElvUI -Force ``` -### Example 2 +### Install ElvUI fresh + +```powershell +Install-ElvUI +``` -Provide examples for typical commands that a user would like to do with the module. +### Target a different WoW installation or flavor ```powershell -Import-Module -Name PSModuleTemplate +Update-ElvUI -WoWPath 'D:\Games\World of Warcraft' -Flavor '_classic_' ``` ### Find more examples To find more examples of how to use the module, please refer to the [examples](examples) folder. -Alternatively, you can use the Get-Command -Module 'This module' to find more commands that are available in the module. -To find examples of each of the commands you can use Get-Help -Examples 'CommandName'. - -## Documentation - -Link to further documentation if available, or describe where in the repository users can find more detailed documentation about -the module's functions and features. +You can also use `Get-Command -Module ElvUI` to list available commands, +and `Get-Help -Examples ` to see usage examples for each. ## Contributing @@ -63,7 +70,3 @@ Please see the issues tab on this project and submit a new issue that matches yo If you do code, we'd love to have your contributions. Please read the [Contribution guidelines](CONTRIBUTING.md) for more information. You can either help by picking up an existing issue or submit a new one if you have an idea for a new feature or improvement. - -## Acknowledgements - -Here is a list of people and projects that helped this project in some way. diff --git a/examples/General.ps1 b/examples/General.ps1 index e193423..3cc2a68 100644 --- a/examples/General.ps1 +++ b/examples/General.ps1 @@ -1,19 +1,19 @@ <# - .SYNOPSIS - This is a general example of how to use the module. + .SYNOPSIS + Examples of how to use the ElvUI module. #> # Import the module -Import-Module -Name 'PSModule' +Import-Module -Name ElvUI -# Define the path to the font file -$FontFilePath = 'C:\Fonts\CodeNewRoman\CodeNewRomanNerdFontPropo-Regular.tff' +# Update ElvUI to the latest version +Update-ElvUI -# Install the font -Install-Font -Path $FontFilePath -Verbose +# Force reinstall even if already up to date +Update-ElvUI -Force -# List installed fonts -Get-Font -Name 'CodeNewRomanNerdFontPropo-Regular' +# Install ElvUI fresh +Install-ElvUI -# Uninstall the font -Get-Font -Name 'CodeNewRomanNerdFontPropo-Regular' | Uninstall-Font -Verbose +# Target a specific WoW installation and game flavor +Update-ElvUI -WoWPath 'D:\Games\World of Warcraft' -Flavor '_classic_' diff --git a/src/README.md b/src/README.md deleted file mode 100644 index af76160..0000000 --- a/src/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Details - -For more info about the expected structure of a module repository, please refer to [Build-PSModule](https://github.com/PSModule/Build-PSModule) diff --git a/src/assemblies/LsonLib.dll b/src/assemblies/LsonLib.dll deleted file mode 100644 index 3661807..0000000 Binary files a/src/assemblies/LsonLib.dll and /dev/null differ diff --git a/src/classes/private/SecretWriter.ps1 b/src/classes/private/SecretWriter.ps1 deleted file mode 100644 index 1b1732a..0000000 --- a/src/classes/private/SecretWriter.ps1 +++ /dev/null @@ -1,15 +0,0 @@ -class SecretWriter { - [string] $Alias - [string] $Name - [string] $Secret - - SecretWriter([string] $alias, [string] $name, [string] $secret) { - $this.Alias = $alias - $this.Name = $name - $this.Secret = $secret - } - - [string] GetAlias() { - return $this.Alias - } -} diff --git a/src/classes/public/Book.ps1 b/src/classes/public/Book.ps1 deleted file mode 100644 index 8917d9a..0000000 --- a/src/classes/public/Book.ps1 +++ /dev/null @@ -1,147 +0,0 @@ -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) - } - } -} - -enum Binding { - Hardcover - Paperback - EBook -} - -enum Genre { - Mystery - Thriller - Romance - ScienceFiction - Fantasy - Horror -} diff --git a/src/data/Config.psd1 b/src/data/Config.psd1 deleted file mode 100644 index fea4466..0000000 --- a/src/data/Config.psd1 +++ /dev/null @@ -1,3 +0,0 @@ -@{ - RandomKey = 'RandomValue' -} diff --git a/src/data/Settings.psd1 b/src/data/Settings.psd1 deleted file mode 100644 index bcfa7b4..0000000 --- a/src/data/Settings.psd1 +++ /dev/null @@ -1,3 +0,0 @@ -@{ - RandomSetting = 'RandomSettingValue' -} diff --git a/src/finally.ps1 b/src/finally.ps1 deleted file mode 100644 index d8fc207..0000000 --- a/src/finally.ps1 +++ /dev/null @@ -1,3 +0,0 @@ -Write-Verbose '------------------------------' -Write-Verbose '--- THIS IS A LAST LOADER ---' -Write-Verbose '------------------------------' diff --git a/src/formats/CultureInfo.Format.ps1xml b/src/formats/CultureInfo.Format.ps1xml deleted file mode 100644 index a715e08..0000000 --- a/src/formats/CultureInfo.Format.ps1xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - System.Globalization.CultureInfo - - System.Globalization.CultureInfo - - - - - 16 - - - 16 - - - - - - - - LCID - - - Name - - - DisplayName - - - - - - - - diff --git a/src/formats/Mygciview.Format.ps1xml b/src/formats/Mygciview.Format.ps1xml deleted file mode 100644 index 4c972c2..0000000 --- a/src/formats/Mygciview.Format.ps1xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - 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/src/functions/private/Get-InternalPSModule.ps1 b/src/functions/private/Get-InternalPSModule.ps1 deleted file mode 100644 index 89f053c..0000000 --- a/src/functions/private/Get-InternalPSModule.ps1 +++ /dev/null @@ -1,18 +0,0 @@ -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/src/functions/private/Get-TukuiAddon.ps1 b/src/functions/private/Get-TukuiAddon.ps1 new file mode 100644 index 0000000..1423ee9 --- /dev/null +++ b/src/functions/private/Get-TukuiAddon.ps1 @@ -0,0 +1,63 @@ +function Get-TukuiAddon { + <# + .SYNOPSIS + Fetches addon info from the Tukui API. + + .DESCRIPTION + Returns metadata for a Tukui-hosted addon including version, download URL, + description, supported patches, and more. When called without parameters, + returns all available addons. + + .EXAMPLE + Get-TukuiAddon -Name elvui + + Returns metadata for ElvUI. + + .EXAMPLE + Get-TukuiAddon + + Returns all available Tukui addons. + + .OUTPUTS + PSCustomObject + #> + [CmdletBinding()] + param( + # The slug name of the addon to retrieve. Omit to return all addons. + [Parameter()] + [ValidateSet('elvui', 'tukui')] + [string] $Name + ) + + if ($Name) { + $url = "https://api.tukui.org/v1/addon/$Name" + } else { + $url = 'https://api.tukui.org/v1/addons' + } + + $response = Invoke-RestMethod -Uri $url + + foreach ($addon in @($response)) { + [PSCustomObject]@{ + Id = $addon.id + Slug = $addon.slug + Name = $addon.name + Author = $addon.author + Version = $addon.version + DownloadUrl = $addon.url + ChangelogUrl = $addon.changelog_url + TicketUrl = $addon.ticket_url + GitUrl = $addon.git_url + Patches = $addon.patch + LastUpdate = $addon.last_update + WebUrl = $addon.web_url + DonateUrl = $addon.donate_url + Description = $addon.small_desc + ScreenshotUrl = $addon.screenshot_url + GalleryUrls = $addon.gallery_url + LogoUrl = $addon.logo_url + LogoSquareUrl = $addon.logo_square_url + Directories = $addon.directories + } + } +} diff --git a/src/functions/private/Get-TukuiInstalledVersion.ps1 b/src/functions/private/Get-TukuiInstalledVersion.ps1 new file mode 100644 index 0000000..271e78f --- /dev/null +++ b/src/functions/private/Get-TukuiInstalledVersion.ps1 @@ -0,0 +1,50 @@ +function Get-TukuiInstalledVersion { + <# + .SYNOPSIS + Gets the currently installed version of a Tukui addon from its .toc file. + + .DESCRIPTION + Reads the .toc file for the specified addon in the AddOns folder and + extracts the version string. Returns $null if the addon is not installed. + + .EXAMPLE + Get-TukuiInstalledVersion -AddOnsPath 'C:\...\AddOns' -Name elvui + + Returns the installed ElvUI version string, or $null if not found. + + .OUTPUTS + System.String or $null if not installed. + #> + [CmdletBinding()] + param( + # The full path to the WoW AddOns directory. + [Parameter(Mandatory)] + [string] $AddOnsPath, + + # The slug name of the addon to check. + [Parameter(Mandatory)] + [ValidateSet('elvui', 'tukui')] + [string] $Name + ) + + $addonFolder = switch ($Name) { + 'elvui' { 'ElvUI' } + 'tukui' { 'Tukui' } + } + + $tocCandidates = @( + (Join-Path -Path $AddOnsPath -ChildPath $addonFolder | Join-Path -ChildPath "${addonFolder}_Mainline.toc") + (Join-Path -Path $AddOnsPath -ChildPath $addonFolder | Join-Path -ChildPath "$addonFolder.toc") + ) + + $tocPath = $tocCandidates | Where-Object { Test-Path -LiteralPath $_ } | Select-Object -First 1 + if (-not $tocPath) { + return $null + } + + $tocContent = Get-Content -LiteralPath $tocPath -Raw -ErrorAction Stop + if ($tocContent -match '(?m)^## Version:\s*(.+)$') { + return $Matches[1].Trim().TrimStart('v') + } + return $null +} diff --git a/src/functions/private/Get-WoWAddOnsPath.ps1 b/src/functions/private/Get-WoWAddOnsPath.ps1 new file mode 100644 index 0000000..1064949 --- /dev/null +++ b/src/functions/private/Get-WoWAddOnsPath.ps1 @@ -0,0 +1,40 @@ +function Get-WoWAddOnsPath { + <# + .SYNOPSIS + Resolves the WoW AddOns folder path for a given flavor. + + .DESCRIPTION + Constructs and validates the full path to the World of Warcraft AddOns directory + based on the installation path and game flavor. + + .EXAMPLE + Get-WoWAddOnsPath + + Returns the default retail AddOns path: C:\Program Files (x86)\World of Warcraft\_retail_\Interface\AddOns + + .EXAMPLE + Get-WoWAddOnsPath -WoWPath 'D:\Games\World of Warcraft' -Flavor '_classic_' + + Returns the classic AddOns path under a custom installation directory. + + .OUTPUTS + System.String + #> + [CmdletBinding()] + param( + # Path to the World of Warcraft installation folder. + [Parameter()] + [string] $WoWPath = 'C:\Program Files (x86)\World of Warcraft', + + # WoW game flavor to target. + [Parameter()] + [ValidateSet('_retail_', '_classic_', '_classic_era_')] + [string] $Flavor = '_retail_' + ) + + $addOnsPath = Join-Path -Path $WoWPath -ChildPath $Flavor | Join-Path -ChildPath 'Interface' | Join-Path -ChildPath 'AddOns' + if (-not (Test-Path -LiteralPath $addOnsPath)) { + throw "AddOns folder not found: $addOnsPath" + } + $addOnsPath +} diff --git a/src/functions/private/Install-TukuiAddon.ps1 b/src/functions/private/Install-TukuiAddon.ps1 new file mode 100644 index 0000000..0148d36 --- /dev/null +++ b/src/functions/private/Install-TukuiAddon.ps1 @@ -0,0 +1,80 @@ +function Install-TukuiAddon { + <# + .SYNOPSIS + Downloads and installs a Tukui addon to the WoW AddOns folder. + + .DESCRIPTION + Downloads the ZIP archive from the Tukui API, extracts it, removes old addon + folders, and copies the new ones into place. Uses a temporary directory + that is cleaned up automatically. + + .EXAMPLE + $addon = Get-TukuiAddon -Name elvui + Install-TukuiAddon -AddOnsPath 'C:\...\AddOns' -Addon $addon + + Downloads and installs ElvUI to the specified AddOns directory. + #> + [CmdletBinding()] + param( + # The full path to the WoW AddOns directory. + [Parameter(Mandatory)] + [string] $AddOnsPath, + + # The addon object returned by Get-TukuiAddon containing metadata and download URL. + [Parameter(Mandatory)] + [PSCustomObject] $Addon + ) + + $tempDir = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath "Tukui_$($Addon.Slug)_Update_$([guid]::NewGuid().ToString('N'))" + try { + if (Test-Path $tempDir) { + Remove-Item $tempDir -Recurse -Force -ErrorAction Stop + } + $null = New-Item -ItemType Directory -Path $tempDir -ErrorAction Stop + + # Download + $zipPath = Join-Path -Path $tempDir -ChildPath "$($Addon.Slug)-$($Addon.Version).zip" + Write-Verbose "Downloading $($Addon.Name) $($Addon.Version) ..." + Invoke-WebRequest -Uri $Addon.DownloadUrl -OutFile $zipPath -ErrorAction Stop + + # Extract + $extractPath = Join-Path -Path $tempDir -ChildPath 'extracted' + Write-Verbose 'Extracting...' + Expand-Archive -Path $zipPath -DestinationPath $extractPath -Force -ErrorAction Stop + + # Remove old addon folders matching the known directory names + $normalizedAddOnsPath = [System.IO.Path]::GetFullPath($AddOnsPath) + $trimChars = @([System.IO.Path]::DirectorySeparatorChar, [System.IO.Path]::AltDirectorySeparatorChar) + $normalizedAddOnsPath = $normalizedAddOnsPath.TrimEnd($trimChars) + [System.IO.Path]::DirectorySeparatorChar + foreach ($dir in $Addon.Directories) { + if ([string]::IsNullOrWhiteSpace($dir) -or $dir.Contains('\') -or $dir.Contains('/') -or $dir.Contains('..')) { + throw "Invalid addon directory entry '$dir' returned by API." + } + + $oldPath = Join-Path -Path $AddOnsPath -ChildPath $dir + $resolvedOldPath = [System.IO.Path]::GetFullPath($oldPath) + if (-not $resolvedOldPath.StartsWith($normalizedAddOnsPath, [System.StringComparison]::OrdinalIgnoreCase)) { + throw "Resolved addon directory path '$resolvedOldPath' is outside the AddOns directory." + } + + if (Test-Path -LiteralPath $resolvedOldPath) { + Write-Verbose " Removing $dir" + Remove-Item -LiteralPath $resolvedOldPath -Recurse -Force -ErrorAction Stop + } + } + + # Copy new folders + $extractedFolders = Get-ChildItem -LiteralPath $extractPath -Directory -ErrorAction Stop + foreach ($folder in $extractedFolders) { + $destination = Join-Path -Path $AddOnsPath -ChildPath $folder.Name + Write-Verbose " Installing $($folder.Name)" + Copy-Item -LiteralPath $folder.FullName -Destination $destination -Recurse -Force -ErrorAction Stop + } + + Write-Verbose "$($Addon.Name) $($Addon.Version) installed successfully!" + } finally { + if (Test-Path -LiteralPath $tempDir) { + Remove-Item -LiteralPath $tempDir -Recurse -Force -ErrorAction SilentlyContinue + } + } +} diff --git a/src/functions/private/Set-InternalPSModule.ps1 b/src/functions/private/Set-InternalPSModule.ps1 deleted file mode 100644 index cf870ba..0000000 --- a/src/functions/private/Set-InternalPSModule.ps1 +++ /dev/null @@ -1,22 +0,0 @@ -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/src/functions/public/Install-ElvUI.ps1 b/src/functions/public/Install-ElvUI.ps1 new file mode 100644 index 0000000..6dac536 --- /dev/null +++ b/src/functions/public/Install-ElvUI.ps1 @@ -0,0 +1,45 @@ +function Install-ElvUI { + <# + .SYNOPSIS + Downloads and installs ElvUI to the WoW AddOns folder. + + .DESCRIPTION + Fetches the latest ElvUI release from the Tukui API, downloads the ZIP archive, + and installs it to the World of Warcraft AddOns directory. Any existing ElvUI + folders are removed before the new version is copied into place. + + .EXAMPLE + Install-ElvUI + + Installs ElvUI to the default retail WoW AddOns folder. + + .EXAMPLE + Install-ElvUI -WoWPath 'D:\Games\World of Warcraft' + + Installs ElvUI using a custom WoW installation path. + + .EXAMPLE + Install-ElvUI -Flavor '_classic_' + + Installs ElvUI to the Classic WoW AddOns folder. + #> + [CmdletBinding(SupportsShouldProcess)] + param( + # Path to the World of Warcraft installation folder. + [Parameter()] + [string] $WoWPath = 'C:\Program Files (x86)\World of Warcraft', + + # WoW game flavor to target. + [Parameter()] + [ValidateSet('_retail_', '_classic_', '_classic_era_')] + [string] $Flavor = '_retail_' + ) + + $addOnsPath = Get-WoWAddOnsPath -WoWPath $WoWPath -Flavor $Flavor + $addon = Get-TukuiAddon -Name elvui + + if ($PSCmdlet.ShouldProcess($addOnsPath, "Install $($addon.Name) $($addon.Version)")) { + Write-Verbose "Installing $($addon.Name) $($addon.Version) ..." + Install-TukuiAddon -AddOnsPath $addOnsPath -Addon $addon + } +} diff --git a/src/functions/public/PSModule/Get-PSModuleTest.ps1 b/src/functions/public/PSModule/Get-PSModuleTest.ps1 deleted file mode 100644 index a07d05b..0000000 --- a/src/functions/public/PSModule/Get-PSModuleTest.ps1 +++ /dev/null @@ -1,26 +0,0 @@ -#Requires -Modules Utilities -#Requires -Modules @{ ModuleName = 'PSSemVer'; RequiredVersion = '1.1.4' } -#Requires -Modules @{ ModuleName = 'DynamicParams'; ModuleVersion = '1.1.8' } -#Requires -Modules @{ ModuleName = 'Store'; ModuleVersion = '0.3.1' } - -function Get-PSModuleTest { - <# - .SYNOPSIS - Performs tests on a module. - - .DESCRIPTION - 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/src/functions/public/PSModule/New-PSModuleTest.ps1 b/src/functions/public/PSModule/New-PSModuleTest.ps1 deleted file mode 100644 index e003841..0000000 --- a/src/functions/public/PSModule/New-PSModuleTest.ps1 +++ /dev/null @@ -1,40 +0,0 @@ -#Requires -Modules @{ModuleName='PSSemVer'; ModuleVersion='1.1.4'} - -function New-PSModuleTest { - <# - .SYNOPSIS - Performs tests on a module. - - .DESCRIPTION - Performs tests on a module. - - .EXAMPLE - Test-PSModule -Name 'World' - - "Hello, World!" - - .NOTES - Testing if a module can have a [Markdown based link](https://example.com). - !"#¤%&/()=?`´^¨*'-_+§½{[]}<>|@£$€¥¢:;.," - \[This is a test\] - #> - [Diagnostics.CodeAnalysis.SuppressMessageAttribute( - 'PSUseShouldProcessForStateChangingFunctions', '', Scope = 'Function', - Justification = 'Reason for suppressing' - )] - [Alias('New-PSModuleTestAlias1')] - [Alias('New-PSModuleTestAlias2')] - [CmdletBinding()] - param ( - # Name of the person to greet. - [Parameter(Mandatory)] - [string] $Name - ) - Write-Output "Hello, $Name!" -} - -New-Alias New-PSModuleTestAlias3 New-PSModuleTest -New-Alias -Name New-PSModuleTestAlias4 -Value New-PSModuleTest - - -Set-Alias New-PSModuleTestAlias5 New-PSModuleTest diff --git a/src/functions/public/PSModule/PSModule.md b/src/functions/public/PSModule/PSModule.md deleted file mode 100644 index a657773..0000000 --- a/src/functions/public/PSModule/PSModule.md +++ /dev/null @@ -1,3 +0,0 @@ -# PSModule - -This is a sub page for PSModule. diff --git a/src/functions/public/SomethingElse/Set-PSModuleTest.ps1 b/src/functions/public/SomethingElse/Set-PSModuleTest.ps1 deleted file mode 100644 index 23ec98e..0000000 --- a/src/functions/public/SomethingElse/Set-PSModuleTest.ps1 +++ /dev/null @@ -1,25 +0,0 @@ -function Set-PSModuleTest { - <# - .SYNOPSIS - Performs tests on a module. - - .DESCRIPTION - 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/src/functions/public/SomethingElse/SomethingElse.md b/src/functions/public/SomethingElse/SomethingElse.md deleted file mode 100644 index d9f7e9e..0000000 --- a/src/functions/public/SomethingElse/SomethingElse.md +++ /dev/null @@ -1 +0,0 @@ -# This is SomethingElse diff --git a/src/functions/public/Test-PSModuleTest.ps1 b/src/functions/public/Test-PSModuleTest.ps1 deleted file mode 100644 index 0c27510..0000000 --- a/src/functions/public/Test-PSModuleTest.ps1 +++ /dev/null @@ -1,21 +0,0 @@ -function Test-PSModuleTest { - <# - .SYNOPSIS - Performs tests on a module. - - .DESCRIPTION - 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/src/functions/public/Update-ElvUI.ps1 b/src/functions/public/Update-ElvUI.ps1 new file mode 100644 index 0000000..d88fe76 --- /dev/null +++ b/src/functions/public/Update-ElvUI.ps1 @@ -0,0 +1,96 @@ +function Update-ElvUI { + <# + .SYNOPSIS + Updates the ElvUI addon to the latest version. + + .DESCRIPTION + Checks the installed ElvUI version against the latest available version from the + Tukui API. If an update is available (or -Force is used), downloads and installs + the new version. If ElvUI is not installed, performs a fresh install. + + .EXAMPLE + Update-ElvUI + + Updates ElvUI in the default retail WoW installation. + + .EXAMPLE + Update-ElvUI -WoWPath 'D:\Games\World of Warcraft' + + Updates ElvUI using a custom WoW installation path. + + .EXAMPLE + Update-ElvUI -Flavor '_classic_' + + Updates ElvUI in the Classic WoW AddOns folder. + + .EXAMPLE + Update-ElvUI -Force + + Reinstalls ElvUI even if the installed version matches the latest. + #> + [CmdletBinding(SupportsShouldProcess)] + param( + # Path to the World of Warcraft installation folder. + [Parameter()] + [string] $WoWPath = 'C:\Program Files (x86)\World of Warcraft', + + # WoW game flavor to target. + [Parameter()] + [ValidateSet('_retail_', '_classic_', '_classic_era_')] + [string] $Flavor = '_retail_', + + # Force reinstall even if the installed version matches the latest. + [Parameter()] + [switch] $Force + ) + + $addOnsPath = Get-WoWAddOnsPath -WoWPath $WoWPath -Flavor $Flavor + $installedVersion = Get-TukuiInstalledVersion -AddOnsPath $addOnsPath -Name elvui + + if ($installedVersion) { + Write-Verbose "Installed version: $installedVersion" + } else { + Write-Verbose 'No existing ElvUI installation detected. Installing fresh.' + } + + $addon = Get-TukuiAddon -Name elvui + Write-Verbose "Latest ElvUI version: $($addon.Version)" + + # Compare versions to prevent unintentional downgrades + $installedVer = $null + $latestVer = $null + $canParseInstalled = [version]::TryParse($installedVersion, [ref]$installedVer) + $canParseLatest = [version]::TryParse($addon.Version, [ref]$latestVer) + $canCompareVersions = $installedVersion -and $canParseInstalled -and $canParseLatest + + if ($canCompareVersions -and $installedVer -gt $latestVer -and -not $Force) { + Write-Verbose "Installed version ($installedVersion) is newer than latest ($($addon.Version)). Use -Force to reinstall." + return + } + + if ($installedVersion -eq $addon.Version -and -not $Force) { + Write-Verbose 'ElvUI is already up to date. Use -Force to reinstall.' + return + } + + if (-not $canCompareVersions -and $installedVersion -and -not $Force) { + $msg = "Cannot compare version formats (installed: '$installedVersion'," + $msg += " latest: '$($addon.Version)'). Use -Force to proceed." + Write-Verbose $msg + return + } + + if ($installedVersion -eq $addon.Version) { + Write-Verbose "Forcing reinstall of $($addon.Version) ..." + } elseif ($canCompareVersions -and $installedVer -gt $latestVer) { + Write-Verbose "Forcing reinstall — installed ($installedVersion) is newer than latest ($($addon.Version))." + } elseif (-not $canCompareVersions -and $installedVersion) { + Write-Verbose "Forcing install — cannot compare versions (installed: '$installedVersion')." + } elseif ($installedVersion) { + Write-Verbose "Updating from $installedVersion to $($addon.Version) ..." + } + + if ($PSCmdlet.ShouldProcess($addOnsPath, "Install $($addon.Name) $($addon.Version)")) { + Install-TukuiAddon -AddOnsPath $addOnsPath -Addon $addon + } +} diff --git a/src/functions/public/completers.ps1 b/src/functions/public/completers.ps1 deleted file mode 100644 index 6b1adbb..0000000 --- a/src/functions/public/completers.ps1 +++ /dev/null @@ -1,8 +0,0 @@ -Register-ArgumentCompleter -CommandName New-PSModuleTest -ParameterName Name -ScriptBlock { - param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) - $null = $commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters - - 'Alice', 'Bob', 'Charlie' | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object { - [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) - } -} diff --git a/src/header.ps1 b/src/header.ps1 deleted file mode 100644 index cc1fde9..0000000 --- a/src/header.ps1 +++ /dev/null @@ -1,3 +0,0 @@ -[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidLongLines', '', Justification = 'Contains long links.')] -[CmdletBinding()] -param() diff --git a/src/init/initializer.ps1 b/src/init/initializer.ps1 deleted file mode 100644 index 28396fb..0000000 --- a/src/init/initializer.ps1 +++ /dev/null @@ -1,3 +0,0 @@ -Write-Verbose '-------------------------------' -Write-Verbose '--- THIS IS AN INITIALIZER ---' -Write-Verbose '-------------------------------' diff --git a/src/manifest.psd1 b/src/manifest.psd1 deleted file mode 100644 index ff720bd..0000000 --- a/src/manifest.psd1 +++ /dev/null @@ -1,5 +0,0 @@ -# This file always wins! -# Use this file to override any of the framework defaults and generated values. -@{ - ModuleVersion = '0.0.0' -} diff --git a/src/modules/OtherPSModule.psm1 b/src/modules/OtherPSModule.psm1 deleted file mode 100644 index 5d6af8e..0000000 --- a/src/modules/OtherPSModule.psm1 +++ /dev/null @@ -1,19 +0,0 @@ -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/src/scripts/loader.ps1 b/src/scripts/loader.ps1 deleted file mode 100644 index 973735a..0000000 --- a/src/scripts/loader.ps1 +++ /dev/null @@ -1,3 +0,0 @@ -Write-Verbose '-------------------------' -Write-Verbose '--- THIS IS A LOADER ---' -Write-Verbose '-------------------------' diff --git a/src/types/DirectoryInfo.Types.ps1xml b/src/types/DirectoryInfo.Types.ps1xml deleted file mode 100644 index aef538b..0000000 --- a/src/types/DirectoryInfo.Types.ps1xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - System.IO.FileInfo - - - Status - Success - - - - - System.IO.DirectoryInfo - - - Status - Success - - - - diff --git a/src/types/FileInfo.Types.ps1xml b/src/types/FileInfo.Types.ps1xml deleted file mode 100644 index 4cfaf6b..0000000 --- a/src/types/FileInfo.Types.ps1xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - System.IO.FileInfo - - - Age - - ((Get-Date) - ($this.CreationTime)).Days - - - - - diff --git a/src/variables/private/PrivateVariables.ps1 b/src/variables/private/PrivateVariables.ps1 deleted file mode 100644 index f1fc2c3..0000000 --- a/src/variables/private/PrivateVariables.ps1 +++ /dev/null @@ -1,47 +0,0 @@ -$script:HabitablePlanets = @( - @{ - Name = 'Earth' - Mass = 5.97 - Diameter = 12756 - DayLength = 24.0 - }, - @{ - Name = 'Mars' - Mass = 0.642 - Diameter = 6792 - DayLength = 24.7 - }, - @{ - Name = 'Proxima Centauri b' - Mass = 1.17 - Diameter = 11449 - DayLength = 5.15 - }, - @{ - Name = 'Kepler-442b' - Mass = 2.34 - Diameter = 11349 - DayLength = 5.7 - }, - @{ - Name = 'Kepler-452b' - Mass = 5.0 - Diameter = 17340 - DayLength = 20.0 - } -) - -$script:InhabitedPlanets = @( - @{ - Name = 'Earth' - Mass = 5.97 - Diameter = 12756 - DayLength = 24.0 - }, - @{ - Name = 'Mars' - Mass = 0.642 - Diameter = 6792 - DayLength = 24.7 - } -) diff --git a/src/variables/public/Moons.ps1 b/src/variables/public/Moons.ps1 deleted file mode 100644 index dd0f33c..0000000 --- a/src/variables/public/Moons.ps1 +++ /dev/null @@ -1,6 +0,0 @@ -$script:Moons = @( - @{ - Planet = 'Earth' - Name = 'Moon' - } -) diff --git a/src/variables/public/Planets.ps1 b/src/variables/public/Planets.ps1 deleted file mode 100644 index 5927bc5..0000000 --- a/src/variables/public/Planets.ps1 +++ /dev/null @@ -1,20 +0,0 @@ -$script:Planets = @( - @{ - Name = 'Mercury' - Mass = 0.330 - Diameter = 4879 - DayLength = 4222.6 - }, - @{ - Name = 'Venus' - Mass = 4.87 - Diameter = 12104 - DayLength = 2802.0 - }, - @{ - Name = 'Earth' - Mass = 5.97 - Diameter = 12756 - DayLength = 24.0 - } -) diff --git a/src/variables/public/SolarSystems.ps1 b/src/variables/public/SolarSystems.ps1 deleted file mode 100644 index acbcedf..0000000 --- a/src/variables/public/SolarSystems.ps1 +++ /dev/null @@ -1,17 +0,0 @@ -$script:SolarSystems = @( - @{ - Name = 'Solar System' - Planets = $script:Planets - Moons = $script:Moons - }, - @{ - Name = 'Alpha Centauri' - Planets = @() - Moons = @() - }, - @{ - Name = 'Sirius' - Planets = @() - Moons = @() - } -) diff --git a/tests/ElvUI.Tests.ps1 b/tests/ElvUI.Tests.ps1 new file mode 100644 index 0000000..ad9ca3f --- /dev/null +++ b/tests/ElvUI.Tests.ps1 @@ -0,0 +1,38 @@ +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSReviewUnusedParameter', '', + Justification = 'Required for Pester tests' +)] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseDeclaredVarsMoreThanAssignments', '', + Justification = 'Required for Pester tests' +)] +[CmdletBinding()] +param() + +Describe 'ElvUI' { + Context 'Install-ElvUI' { + It 'Should be available' { + Get-Command Install-ElvUI -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty + } + It 'Should have WoWPath parameter' { + (Get-Command Install-ElvUI).Parameters.ContainsKey('WoWPath') | Should -BeTrue + } + It 'Should have Flavor parameter' { + (Get-Command Install-ElvUI).Parameters.ContainsKey('Flavor') | Should -BeTrue + } + } + Context 'Update-ElvUI' { + It 'Should be available' { + Get-Command Update-ElvUI -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty + } + It 'Should have WoWPath parameter' { + (Get-Command Update-ElvUI).Parameters.ContainsKey('WoWPath') | Should -BeTrue + } + It 'Should have Flavor parameter' { + (Get-Command Update-ElvUI).Parameters.ContainsKey('Flavor') | Should -BeTrue + } + It 'Should have Force parameter' { + (Get-Command Update-ElvUI).Parameters.ContainsKey('Force') | Should -BeTrue + } + } +} diff --git a/tests/Install-ElvUI.Tests.ps1 b/tests/Install-ElvUI.Tests.ps1 new file mode 100644 index 0000000..79458f1 --- /dev/null +++ b/tests/Install-ElvUI.Tests.ps1 @@ -0,0 +1,25 @@ +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSReviewUnusedParameter', '', + Justification = 'Required for Pester tests' +)] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseDeclaredVarsMoreThanAssignments', '', + Justification = 'Required for Pester tests' +)] +[CmdletBinding()] +param() + +Describe 'Install-ElvUI' { + It 'Should be available' { + Get-Command Install-ElvUI -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty + } + It 'Should have WoWPath parameter' { + (Get-Command Install-ElvUI).Parameters.ContainsKey('WoWPath') | Should -BeTrue + } + It 'Should have Flavor parameter' { + (Get-Command Install-ElvUI).Parameters.ContainsKey('Flavor') | Should -BeTrue + } + It 'Should throw when WoW path does not exist' { + { Install-ElvUI -WoWPath 'TestDrive:\NonExistent' } | Should -Throw + } +} diff --git a/tests/PSModuleTest.Tests.ps1 b/tests/PSModuleTest.Tests.ps1 deleted file mode 100644 index b856855..0000000 --- a/tests/PSModuleTest.Tests.ps1 +++ /dev/null @@ -1,25 +0,0 @@ -[Diagnostics.CodeAnalysis.SuppressMessageAttribute( - 'PSReviewUnusedParameter', '', - Justification = 'Required for Pester tests' -)] -[Diagnostics.CodeAnalysis.SuppressMessageAttribute( - 'PSUseDeclaredVarsMoreThanAssignments', '', - Justification = 'Required for Pester tests' -)] -[CmdletBinding()] -param() - -Describe 'Module' { - It 'Function: Get-PSModuleTest' { - Get-PSModuleTest -Name 'World' | Should -Be 'Hello, World!' - } - It 'Function: New-PSModuleTest' { - New-PSModuleTest -Name 'World' | Should -Be 'Hello, World!' - } - It 'Function: Set-PSModuleTest' { - Set-PSModuleTest -Name 'World' | Should -Be 'Hello, World!' - } - It 'Function: Test-PSModuleTest' { - Test-PSModuleTest -Name 'World' | Should -Be 'Hello, World!' - } -} diff --git a/tests/Update-ElvUI.Tests.ps1 b/tests/Update-ElvUI.Tests.ps1 new file mode 100644 index 0000000..8c9b00b --- /dev/null +++ b/tests/Update-ElvUI.Tests.ps1 @@ -0,0 +1,28 @@ +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSReviewUnusedParameter', '', + Justification = 'Required for Pester tests' +)] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseDeclaredVarsMoreThanAssignments', '', + Justification = 'Required for Pester tests' +)] +[CmdletBinding()] +param() + +Describe 'Update-ElvUI' { + It 'Should be available' { + Get-Command Update-ElvUI -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty + } + It 'Should have WoWPath parameter' { + (Get-Command Update-ElvUI).Parameters.ContainsKey('WoWPath') | Should -BeTrue + } + It 'Should have Flavor parameter' { + (Get-Command Update-ElvUI).Parameters.ContainsKey('Flavor') | Should -BeTrue + } + It 'Should have Force parameter' { + (Get-Command Update-ElvUI).Parameters.ContainsKey('Force') | Should -BeTrue + } + It 'Should throw when WoW path does not exist' { + { Update-ElvUI -WoWPath 'TestDrive:\NonExistent' } | Should -Throw + } +}