PublishAndTest-ActionLogging.ps1

# =============================================================
# PublishAndTest-ActionLogging
# =============================================================
# Script Version : 1.0
# Date Created: 01-17-2025
# Last Revised: 01-17-2025
# Revision details: Initial creation - PowerShell Gallery publication automation
# Created by : Gonzalo More
# Required files: .env (with PSGALLERY_API_KEY), Action.Logging module files
# Required folders: PSGallery\Action.Logging
# Licensed under MIT License.
# =============================================================

<#
.SYNOPSIS
    Automated PowerShell Gallery publication and testing script for Action.Logging module.

.DESCRIPTION
    This script automates the complete publication pipeline for the Action.Logging module:
    1. Loads API key from environment file
    2. Validates module quality and compliance
    3. Publishes to PowerShell Gallery
    4. Tests the published module functionality
    5. Provides comprehensive reporting

.PARAMETER WhatIf
    Shows what would be published without actually publishing.

.PARAMETER SkipTests
    Skips post-publication testing (not recommended).

.PARAMETER Verbose
    Provides detailed output during execution.

.EXAMPLE
    .\PublishAndTest-ActionLogging.ps1
    Publishes the Action.Logging module to PowerShell Gallery and tests it.

.EXAMPLE
    .\PublishAndTest-ActionLogging.ps1 -WhatIf
    Shows what would be published without actually doing it.

.EXAMPLE
    .\PublishAndTest-ActionLogging.ps1 -Verbose
    Provides detailed output during the publication process.
#>


[CmdletBinding(SupportsShouldProcess)]
param(
    [Parameter(Mandatory = $false)]
    [switch]$SkipTests
)

# Simple console logging function - no file logging to avoid conflicts
function Write-Log {
    param([string]$Message, [string]$Level = 'INFO')
    
    $timestamp = Get-Date -Format 'MM-dd-yyyy HH:mm:ss'
    $logEntry = "$timestamp [$Level] $Message"
    
    $color = switch($Level) { 
        'ERROR' {'Red'} 
        'WARN' {'Yellow'} 
        'INFO' {'Green'} 
        'DEBUG' {'Cyan'} 
        'TRACE' {'Gray'} 
        default {'White'} 
    }
    
    Write-Host $logEntry -ForegroundColor $color
}

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

#region Helper Functions

<#
.SYNOPSIS
    Loads environment variables from .env file.
#>

function Load-EnvironmentVariables {
    [CmdletBinding()]
    param()
    
    Write-Log -Message "Loading environment variables from .env file" -Level INFO
    
    $envPath = Join-Path $PSScriptRoot "..\.env"
    
    if (-not (Test-Path $envPath)) {
        throw "Environment file not found at: $envPath"
    }
    
    try {
        Get-Content $envPath | ForEach-Object {
            if ($_ -match '^([^#=]+)=(.*)$') {
                $key = $matches[1].Trim()
                $value = $matches[2].Trim()
                [Environment]::SetEnvironmentVariable($key, $value, "Process")
                Write-Log -Message "Loaded environment variable: $key" -Level DEBUG
            }
        }
        
        # Verify required variables
        if (-not $env:PSGALLERY_API_KEY) {
            throw "PSGALLERY_API_KEY not found in environment variables"
        }
        
        Write-Log -Message "Environment variables loaded successfully" -Level INFO
        return $env:PSGALLERY_API_KEY
    }
    catch {
        Write-Log -Message "Failed to load environment variables: $($_.Exception.Message)" -Level ERROR
        throw
    }
}

<#
.SYNOPSIS
    Validates the Action.Logging module for publication readiness.
#>

function Test-ModuleReadiness {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$ModulePath
    )
    
    Write-Log -Message "Starting module readiness validation" -Level INFO
    
    # Test 1: Verify module directory exists
    if (-not (Test-Path $ModulePath)) {
        throw "Module directory not found: $ModulePath"
    }
    
    $manifestPath = Join-Path $ModulePath "Action.Logging.psd1"
    $modulePath = Join-Path $ModulePath "Action.Logging.psm1"
    
    # Test 2: Verify required files exist
    @($manifestPath, $modulePath) | ForEach-Object {
        if (-not (Test-Path $_)) {
            throw "Required file not found: $_"
        }
    }
    
    Write-Log -Message "Module files found successfully" -Level INFO
    
    # Test 3: Validate module manifest
    try {
        $manifest = Test-ModuleManifest -Path $manifestPath -ErrorAction Stop
        Write-Log -Message "Module manifest is valid - Version: $($manifest.Version)" -Level INFO
    }
    catch {
        Write-Log -Message "Module manifest validation failed: $($_.Exception.Message)" -Level ERROR
        throw
    }
    
    # Test 4: Run PSScriptAnalyzer
    Write-Log -Message "Running PSScriptAnalyzer validation" -Level INFO
    
    try {
        $analyzerResults = Invoke-ScriptAnalyzer -Path $ModulePath -Recurse -ExcludeRule PSAvoidUsingWriteHost -Severity Error,Warning
        
        if ($analyzerResults) {
            # Handle single result or array
            $issueCount = if ($analyzerResults -is [array]) { $analyzerResults.Count } else { 1 }
            Write-Log -Message "PSScriptAnalyzer found $issueCount issues:" -Level WARN
            $analyzerResults | ForEach-Object {
                Write-Log -Message " $($_.Severity): $($_.Message) in $($_.ScriptName):$($_.Line)" -Level WARN
            }
            
            $errors = @($analyzerResults | Where-Object {$_.Severity -eq 'Error'})
            $errorCount = $errors.Count
            if ($errorCount -gt 0) {
                throw "PSScriptAnalyzer found $errorCount error(s). Publication blocked."
            }
        } else {
            Write-Log -Message "PSScriptAnalyzer validation passed - no issues found" -Level INFO
        }
    }
    catch {
        Write-Log -Message "PSScriptAnalyzer execution failed: $($_.Exception.Message)" -Level ERROR
        throw
    }
    
    # Test 5: Test module import
    Write-Log -Message "Testing module import" -Level INFO
    
    try {
        # Remove any existing Action.Logging module first to avoid conflicts
        Remove-Module Action.Logging -Force -ErrorAction SilentlyContinue
        
        Import-Module $manifestPath -Force -ErrorAction Stop
        
        $expectedFunctions = @('Write-EnhancedLog', 'Clear-OldLog', 'Start-AsyncLogger', 'Stop-AsyncLogger', 'Write-EnhancedLogAsync', 'Set-LoggingConsoleOutput', 'Set-LogRetention')
        $availableFunctions = Get-Command -Module Action.Logging
        
        Write-Log -Message "Found functions: $($availableFunctions.Name -join ', ')" -Level DEBUG
        
        $functionCheckResults = @()
        foreach ($function in $expectedFunctions) {
            if ($availableFunctions.Name -contains $function) {
                Write-Log -Message "Function '$function' found in module exports" -Level DEBUG
                $functionCheckResults += $true
            } else {
                $functionCheckResults += $false
                Write-Log -Message "Function '$function' NOT found. Available: $($availableFunctions.Name -join ', ')" -Level ERROR
            }
        }
        
        # Check if all functions were found
        if ($functionCheckResults -contains $false) {
            throw "One or more expected functions not found in module exports"
        }
        
        Write-Log -Message "Module imports successfully - all $($expectedFunctions.Count) functions available" -Level INFO
        
        # Clean up
        Remove-Module Action.Logging -Force -ErrorAction SilentlyContinue
    }
    catch {
        Write-Log -Message "Module import test failed: $($_.Exception.Message)" -Level ERROR
        throw
    }
    
    Write-Log -Message "Module readiness validation completed successfully" -Level INFO
    return $manifest
}

<#
.SYNOPSIS
    Publishes the module to PowerShell Gallery.
#>

function Publish-ModuleToGallery {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory = $true)]
        [string]$ModulePath,
        
        [Parameter(Mandatory = $true)]
        [string]$ApiKey,
        
        [Parameter(Mandatory = $true)]
        [object]$Manifest
    )
    
    if ($PSCmdlet.ShouldProcess("Action.Logging v$($Manifest.Version)", "Publish to PowerShell Gallery")) {
        Write-Log -Message "Publishing Action.Logging v$($Manifest.Version) to PowerShell Gallery" -Level INFO
        
        try {
            Publish-Module -Path $ModulePath -NuGetApiKey $ApiKey -Verbose:$VerbosePreference -Force -ErrorAction Stop
            Write-Log -Message "Module published successfully to PowerShell Gallery" -Level INFO
            
            # Wait for propagation
            Write-Log -Message "Waiting 15 seconds for module propagation..." -Level INFO
            Start-Sleep -Seconds 15
            
            return $true
        }
        catch {
            Write-Log -Message "Module publication failed: $($_.Exception.Message)" -Level ERROR
            throw
        }
    } else {
        Write-Log -Message "WhatIf: Would publish Action.Logging v$($Manifest.Version) to PowerShell Gallery" -Level INFO
        return $false
    }
}

<#
.SYNOPSIS
    Tests the published module functionality.
#>

function Test-PublishedModule {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$ModuleName,
        
        [Parameter(Mandatory = $true)]
        [string]$ExpectedVersion
    )
    
    Write-Log -Message "Starting published module testing" -Level INFO
    
    # Test 1: Verify module is available in gallery
    try {
        $publishedModule = Find-Module -Name $ModuleName -ErrorAction Stop
        Write-Log -Message "Module found in PowerShell Gallery - Version: $($publishedModule.Version)" -Level INFO
        
        if ($publishedModule.Version -ne $ExpectedVersion) {
            Write-Log -Message "Warning: Published version ($($publishedModule.Version)) differs from expected ($ExpectedVersion)" -Level WARN
        }
    }
    catch {
        Write-Log -Message "Failed to find module in PowerShell Gallery: $($_.Exception.Message)" -Level ERROR
        throw
    }
    
    # Test 2: Install from gallery
    Write-Log -Message "Installing module from PowerShell Gallery" -Level INFO
    
    try {
        # Uninstall any existing version first
        $existingModule = Get-Module -Name $ModuleName -ListAvailable -ErrorAction SilentlyContinue
        if ($existingModule) {
            Write-Log -Message "Removing existing module installation" -Level INFO
            Uninstall-Module -Name $ModuleName -AllVersions -Force -ErrorAction SilentlyContinue
        }
        
        Install-Module -Name $ModuleName -Scope CurrentUser -Force -ErrorAction Stop
        Write-Log -Message "Module installed successfully from PowerShell Gallery" -Level INFO
    }
    catch {
        Write-Log -Message "Module installation failed: $($_.Exception.Message)" -Level ERROR
        throw
    }
    
    # Test 3: Import and test functionality
    Write-Log -Message "Testing module functionality" -Level INFO
    
    try {
        Import-Module $ModuleName -Force -ErrorAction Stop
        
        # Test basic logging functionality
        Write-Log -Message "Testing basic logging functions" -Level INFO
        
        # Temporarily disable console output for testing
        Set-LoggingConsoleOutput -Enabled $false
        
        # Test each log level using the Action.Logging module functions
        Write-EnhancedLog -Message "Test INFO message" -Level INFO
        Write-EnhancedLog -Message "Test WARN message" -Level WARN  
        Write-EnhancedLog -Message "Test ERROR message" -Level ERROR
        Write-EnhancedLog -Message "Test DEBUG message" -Level DEBUG
        Write-EnhancedLog -Message "Test TRACE message" -Level TRACE
        
        # Test async functions (just check they exist and can be called)
        $asyncStart = Get-Command Start-AsyncLogger -ErrorAction SilentlyContinue
        $asyncStop = Get-Command Stop-AsyncLogger -ErrorAction SilentlyContinue
        $clearLog = Get-Command Clear-OldLog -ErrorAction SilentlyContinue
        
        if (-not $asyncStart) { throw "Start-AsyncLogger function not found" }
        if (-not $asyncStop) { throw "Stop-AsyncLogger function not found" }
        if (-not $clearLog) { throw "Clear-OldLog function not found" }
        
        # Re-enable console output
        Set-LoggingConsoleOutput -Enabled $true
        
        Write-Log -Message "All module functionality tests passed" -Level INFO
    }
    catch {
        Write-Log -Message "Module functionality test failed: $($_.Exception.Message)" -Level ERROR
        throw
    }
    finally {
        # Clean up
        Remove-Module $ModuleName -Force -ErrorAction SilentlyContinue
    }
    
    Write-Log -Message "Published module testing completed successfully" -Level INFO
}

<#
.SYNOPSIS
    Generates a publication summary report.
#>

function Write-PublicationSummary {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [object]$Manifest,
        
        [Parameter(Mandatory = $true)]
        [bool]$PublishSuccess,
        
        [Parameter(Mandatory = $true)]
        [bool]$TestSuccess
    )
    
    Write-Host "`n" -NoNewline
    Write-Host "===============================================" -ForegroundColor Cyan
    Write-Host " Action.Logging Publication Summary" -ForegroundColor Cyan  
    Write-Host "===============================================" -ForegroundColor Cyan
    
    Write-Host "Module: Action.Logging" -ForegroundColor White
    Write-Host "Version: $($Manifest.Version)" -ForegroundColor White
    Write-Host "GUID: $($Manifest.GUID)" -ForegroundColor Gray
    Write-Host "Publication Time: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" -ForegroundColor Gray
    
    if ($PublishSuccess) {
        Write-Host "Publication Status: SUCCESS" -ForegroundColor Green
        Write-Host "Gallery URL: https://www.powershellgallery.com/packages/Action.Logging" -ForegroundColor Gray
    } else {
        Write-Host "Publication Status: SKIPPED (WhatIf mode)" -ForegroundColor Yellow
    }
    
    if ($PublishSuccess -and $TestSuccess) {
        Write-Host "Testing Status: SUCCESS" -ForegroundColor Green
    } elseif ($PublishSuccess) {
        Write-Host "Testing Status: FAILED" -ForegroundColor Red
    } else {
        Write-Host "Testing Status: SKIPPED" -ForegroundColor Yellow
    }
    
    Write-Host "`nNext Steps:" -ForegroundColor Cyan
    if ($PublishSuccess) {
        Write-Host " 1. Module is now available worldwide via: Install-Module Action.Logging" -ForegroundColor Gray
        Write-Host " 2. Update documentation with gallery badges and links" -ForegroundColor Gray
        Write-Host " 3. Consider setting up automated CI/CD for future releases" -ForegroundColor Gray
        Write-Host " 4. Monitor PowerShell Gallery statistics and feedback" -ForegroundColor Gray
    } else {
        Write-Host " 1. Run without -WhatIf to actually publish the module" -ForegroundColor Gray
        Write-Host " 2. Ensure all validation checks pass before publication" -ForegroundColor Gray
    }
    
    Write-Host "===============================================" -ForegroundColor Cyan
}

#endregion

#region Main Script Logic

try {
    Write-Log -Message "Starting Action.Logging PowerShell Gallery publication process" -Level INFO
    
    # Define paths - script is already in the Action.Logging directory
    $moduleDirectory = $PSScriptRoot
    
    # Step 1: Load environment variables
    Write-Log -Message "Step 1: Loading environment configuration" -Level INFO
    $apiKey = Load-EnvironmentVariables
    
    # Step 2: Validate module readiness
    Write-Log -Message "Step 2: Validating module readiness" -Level INFO
    $manifest = Test-ModuleReadiness -ModulePath $moduleDirectory
    
    # Step 3: Publish to PowerShell Gallery
    Write-Log -Message "Step 3: Publishing to PowerShell Gallery" -Level INFO
    $publishSuccess = Publish-ModuleToGallery -ModulePath $moduleDirectory -ApiKey $apiKey -Manifest $manifest
    
    # Step 4: Test published module (if actually published and not skipped)
    $testSuccess = $true
    if ($publishSuccess -and -not $SkipTests) {
        Write-Log -Message "Step 4: Testing published module" -Level INFO
        try {
            Test-PublishedModule -ModuleName "Action.Logging" -ExpectedVersion $manifest.Version
        }
        catch {
            $testSuccess = $false
            Write-Log -Message "Published module testing failed, but publication was successful" -Level WARN
        }
    } elseif ($SkipTests) {
        Write-Log -Message "Step 4: Skipping published module testing (SkipTests specified)" -Level INFO
    }
    
    # Step 5: Generate summary report
    Write-Log -Message "Step 5: Generating publication summary" -Level INFO
    Write-PublicationSummary -Manifest $manifest -PublishSuccess $publishSuccess -TestSuccess $testSuccess
    
    if ($publishSuccess -and $testSuccess) {
        Write-Log -Message "Action.Logging publication and testing completed successfully!" -Level INFO
        exit 0
    } elseif ($publishSuccess) {
        Write-Log -Message "Action.Logging published successfully, but testing had issues" -Level WARN
        exit 1
    } else {
        Write-Log -Message "Action.Logging publication process completed (WhatIf mode)" -Level INFO
        exit 0
    }
}
catch {
    Write-Log -Message "Action.Logging publication failed: $($_.Exception.Message)" -Level ERROR
    Write-Host "`nPublication failed. Check logs for details." -ForegroundColor Red
    exit 1
}

#endregion