Public/New-ColorScriptCache.ps1

function New-ColorScriptCache {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '', Justification = 'Returns structured pipeline records for each cache operation.')]
    [OutputType([pscustomobject])]
    [CmdletBinding(DefaultParameterSetName = 'Selection', SupportsShouldProcess = $true, ConfirmImpact = 'Medium', HelpUri = 'https://nick2bad4u.github.io/PS-Color-Scripts-Enhanced/docs/help-redirect.html?cmdlet=New-ColorScriptCache')]
    [Alias('Update-ColorScriptCache', 'Build-ColorScriptCache')]
    param(
        [Parameter(ParameterSetName = 'Help')]
        [Alias('help')]
        [switch]$h,

        [Parameter(ParameterSetName = 'Selection', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [SupportsWildcards()]
        [ValidateScript({ Test-ColorScriptNameValue $_ -AllowWildcard })]
        [ArgumentCompleter({
                param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)

                $null = $commandName, $parameterName, $commandAst, $fakeBoundParameters

                try {
                    $records = ColorScripts-Enhanced\Get-ColorScriptList -AsObject -Quiet -ErrorAction Stop -WarningAction SilentlyContinue
                }
                catch {
                    return
                }

                $pattern = if ([string]::IsNullOrWhiteSpace($wordToComplete)) {
                    '*'
                }
                else {
                    $trimmed = $wordToComplete.Trim([char]0x27, [char]0x22)
                    if ([string]::IsNullOrWhiteSpace($trimmed)) { '*' }
                    elseif ($trimmed -match '[*?]') { $trimmed }
                    else { $trimmed + '*' }
                }

                $records |
                    Where-Object { $_.Name -and ($_.Name -like $pattern) } |
                        Group-Object -Property Name |
                            Sort-Object -Property Name |
                                ForEach-Object {
                                    $first = $_.Group | Select-Object -First 1
                                    $toolTip = if ($first.Description) {
                                        $first.Description
                                    }
                                    elseif ($first.Category) {
                                        "Category: $($first.Category)"
                                    }
                                    else {
                                        $first.Name
                                    }

                                    [System.Management.Automation.CompletionResult]::new(
                                        $first.Name,
                                        $first.Name,
                                        [System.Management.Automation.CompletionResultType]::ParameterValue,
                                        $toolTip
                                    )
                                }
            })]
        [string[]]$Name,

        [Parameter(ParameterSetName = 'All')]
        [switch]$All,

        [Parameter(ParameterSetName = 'Selection')]
        [Parameter(ParameterSetName = 'All')]
        [switch]$Force,

        [Parameter(ParameterSetName = 'Selection')]
        [Parameter(ParameterSetName = 'All')]
        [switch]$PassThru,

        [Parameter(ParameterSetName = 'Selection')]
        [Parameter(ParameterSetName = 'All')]
        [string[]]$Category,

        [Parameter(ParameterSetName = 'Selection')]
        [Parameter(ParameterSetName = 'All')]
        [string[]]$Tag
    )

    begin {
        $helpRequested = $false
        $summary = $null
        $results = $null
        $nameSet = $null
        $collectedNames = $null
        $addName = $null

        if ($h) {
            Show-ColorScriptHelp -CommandName 'New-ColorScriptCache'
            $helpRequested = $true
        }

        if ($helpRequested) {
            return
        }

        if ($PSBoundParameters.ContainsKey('All') -and -not $All) {
            Invoke-ColorScriptError -Message $script:Messages.SpecifyNameToSelectScripts -ErrorId 'ColorScriptsEnhanced.CacheSelectionMissing' -Category ([System.Management.Automation.ErrorCategory]::InvalidOperation) -Cmdlet $PSCmdlet
        }

        Initialize-CacheDirectory

        $summary = [pscustomobject]@{
            Processed = 0
            Updated   = 0
            Skipped   = 0
            Failed    = 0
        }

        $results = New-Object 'System.Collections.Generic.List[pscustomobject]'
        $nameSet = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
        $collectedNames = [System.Collections.Generic.List[string]]::new()

        $addName = {
            param([string]$Value)

            if ([string]::IsNullOrWhiteSpace($Value)) {
                return
            }

            if ($nameSet.Add($Value)) {
                [void]$collectedNames.Add($Value)
            }
        }

        if ($PSBoundParameters.ContainsKey('Name') -and $Name) {
            foreach ($value in $Name) {
                & $addName $value
            }
        }
    }

    process {
        if ($helpRequested) {
            return
        }

        if ($MyInvocation.ExpectingInput) {
            if ($PSBoundParameters.ContainsKey('Name') -and $Name) {
                foreach ($value in $Name) {
                    & $addName $value
                }
            }
            elseif ($null -ne $_) {
                $candidate = $null

                if ($_ -is [string]) {
                    $candidate = $_
                }
                elseif ($_ -is [System.Management.Automation.PSObject]) {
                    if ($_.PSObject.Properties['Name']) {
                        $candidate = [string]$_.PSObject.Properties['Name'].Value
                    }
                    elseif ($_.PSObject.Properties['ScriptName']) {
                        $candidate = [string]$_.PSObject.Properties['ScriptName'].Value
                    }
                }

                if ($candidate) {
                    & $addName $candidate
                }
            }
        }
    }

    end {
        if ($helpRequested) {
            return
        }

        $requestedNames = $collectedNames
        $allRecords = @()

        try {
            $allRecords = @(Get-ColorScriptEntry -Category $Category -Tag $Tag)
        }
        catch {
            Write-Verbose ("Get-ColorScriptEntry failed: {0}" -f $_.Exception.Message)
            $allRecords = @()
        }

        $normalized = New-Object 'System.Collections.Generic.List[object]'
        $normalize = $null
        $normalize = {
            param($Item)

            if ($null -eq $Item) {
                return
            }

            if ($Item -is [System.Collections.IEnumerable] -and -not ($Item -is [string]) -and -not ($Item -is [System.Management.Automation.PSObject]) -and -not ($Item -is [System.Collections.IDictionary])) {
                foreach ($nested in $Item) {
                    & $normalize $nested
                }
                return
            }

            [void]$normalized.Add($Item)
        }

        foreach ($entry in $allRecords) {
            & $normalize $entry
        }

        $candidateRecords = $normalized.ToArray()

        if ($requestedNames.Count -gt 0) {
            $selection = Select-RecordsByName -Records $candidateRecords -Name $requestedNames.ToArray()
            $candidateRecords = if ($selection.Records) { $selection.Records } else { @() }

            foreach ($pattern in $selection.MissingPatterns) {
                if (-not [string]::IsNullOrWhiteSpace($pattern)) {
                    Write-Warning ($script:Messages.ScriptNotFound -f $pattern)
                }
            }
        }

        if (-not $All -and $requestedNames.Count -eq 0 -and -not $Category -and -not $Tag -and -not $candidateRecords) {
            $candidateRecords = $normalized.ToArray()
        }

        $candidateRecords = @($candidateRecords | Where-Object { $_ })

        if (-not $candidateRecords -or $candidateRecords.Count -eq 0) {
            Write-Warning $script:Messages.NoScriptsSelectedForCacheBuild
            if ($PassThru) {
                return @()
            }

            return
        }

        $total = $candidateRecords.Count
        $index = 0
        $activity = 'Building colorscript cache'

        foreach ($record in $candidateRecords) {
            $index++
            $statusPercent = [math]::Min(100, [math]::Max(0, ($index / $total) * 100))
            $recordObject = if ($record -is [System.Management.Automation.PSObject]) { $record } else { [pscustomobject]$record }
            $scriptName = [string]$recordObject.Name
            $scriptPath = [string]$recordObject.Path

            if ([string]::IsNullOrWhiteSpace($scriptName) -or [string]::IsNullOrWhiteSpace($scriptPath)) {
                continue
            }

            Write-Progress -Activity $activity -Status ("Processing {0} of {1}: {2}" -f $index, $total, $scriptName) -PercentComplete $statusPercent

            $summary.Processed++

            if (-not $Force) {
                $cacheEntry = Get-CachedOutput -ScriptPath $scriptPath
                if ($cacheEntry.Available) {
                    $summary.Skipped++
                    $skipRecord = [pscustomobject]@{
                        Name        = $scriptName
                        ScriptPath  = $scriptPath
                        CacheFile   = $cacheEntry.CacheFile
                        Status      = 'SkippedUpToDate'
                        Message     = $script:Messages.StatusSkippedUpToDate
                        CacheExists = $true
                        ExitCode    = 0
                        StdOut      = $cacheEntry.Content
                        StdErr      = ''
                    }
                    [void]$results.Add($skipRecord)
                    continue
                }
            }

            if (-not (Invoke-ShouldProcess -Cmdlet $PSCmdlet -Target $scriptName -Action 'Build colorscript cache')) {
                $summary.Skipped++

                $skippedRecord = [pscustomobject]@{
                    Name        = $scriptName
                    ScriptPath  = $scriptPath
                    CacheFile   = $null
                    Status      = 'SkippedByUser'
                    Message     = $script:Messages.StatusSkippedByUser
                    CacheExists = $false
                    ExitCode    = $null
                    StdOut      = ''
                    StdErr      = ''
                }

                [void]$results.Add($skippedRecord)
                continue
            }

            $cacheResult = Build-ScriptCache -ScriptPath $scriptPath

            if ($cacheResult.Success) {
                $summary.Updated++
                $resultStatus = 'Updated'
                $resultMessage = $script:Messages.StatusCached
                $cacheExists = $true
            }
            else {
                $summary.Failed++
                $resultStatus = 'Failed'
                $errorDetail = if ($cacheResult.StdErr) {
                    $cacheResult.StdErr
                }
                elseif ($null -ne $cacheResult.ExitCode) {
                    ($script:Messages.ScriptExitedWithCode -f $cacheResult.ExitCode)
                }
                else {
                    'Cache build failed.'
                }

                Write-Warning ("Failed to cache {0}: {1}" -f $scriptName, $errorDetail)

                $resultMessage = $errorDetail
                $cacheExists = $false
            }

            $resultRecord = [pscustomobject]@{
                Name        = if ($cacheResult.ScriptName) { $cacheResult.ScriptName } else { $scriptName }
                ScriptPath  = $scriptPath
                CacheFile   = $cacheResult.CacheFile
                Status      = $resultStatus
                Message     = $resultMessage
                CacheExists = $cacheExists
                ExitCode    = $cacheResult.ExitCode
                StdOut      = $cacheResult.StdOut
                StdErr      = $cacheResult.StdErr
            }

            [void]$results.Add($resultRecord)
        }

        Write-Progress -Activity $activity -Completed -Status 'Completed'

        if (-not $PassThru -and $summary.Processed -gt 0) {
            $summaryMessage = "Cache build summary: Processed {0}, Updated {1}, Skipped {2}, Failed {3}" -f $summary.Processed, $summary.Updated, $summary.Skipped, $summary.Failed
            Write-ColorScriptInformation -Message $summaryMessage -Quiet:$false
        }

        if ($PassThru) {
            return $results.ToArray()
        }
    }
}