PackageModel/Support/Package/Eigenverft.Manifested.Sandbox.PackageModel.Selection.ps1

<#
    Eigenverft.Manifested.Sandbox.PackageModel.Selection
#>


function ConvertTo-PackageModelVersion {
<#
.SYNOPSIS
Converts a package version string to a sortable version object.
 
.DESCRIPTION
Normalizes version text so PackageModel release selection can sort exact
version entries by semantic-like version ordering.
 
.PARAMETER VersionText
The version string to convert.
 
.EXAMPLE
ConvertTo-PackageModelVersion -VersionText '1.116.0'
#>

    [CmdletBinding()]
    param(
        [AllowNull()]
        [string]$VersionText
    )

    if ([string]::IsNullOrWhiteSpace($VersionText)) {
        return [version]'0.0.0'
    }

    try {
        return [version]$VersionText.Trim()
    }
    catch {
        $match = [regex]::Match($VersionText, '\d+(?:\.\d+){0,3}')
        if ($match.Success) {
            try {
                return [version]$match.Value
            }
            catch {
            }
        }
    }

    return [version]'0.0.0'
}

function Test-PackageModelConstraintSetMatch {
<#
.SYNOPSIS
Checks whether a constraint set matches an actual value.
 
.DESCRIPTION
Treats empty constraint sets as unrestricted and otherwise performs a
case-insensitive comparison against each configured value.
 
.PARAMETER Values
The configured constraint values.
 
.PARAMETER ActualValue
The effective value to test.
 
.EXAMPLE
Test-PackageModelConstraintSetMatch -Values @('windows') -ActualValue 'windows'
#>

    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [AllowNull()]
        [object[]]$Values,

        [AllowNull()]
        [string]$ActualValue
    )

    if ($null -eq $Values -or $Values.Count -eq 0) {
        return $true
    }

    foreach ($value in $Values) {
        if ([string]::Equals([string]$value, [string]$ActualValue, [System.StringComparison]::OrdinalIgnoreCase)) {
            return $true
        }
    }

    return $false
}

function Test-PackageModelRequirementAllowedBlockedMatch {
<#
.SYNOPSIS
Checks allowed/blocked requirement lists against one actual value.
 
.DESCRIPTION
Treats empty allowed/blocked lists as unrestricted and otherwise enforces
case-insensitive allow and block matching for one actual value.
 
.PARAMETER Allowed
The optional allowed values.
 
.PARAMETER Blocked
The optional blocked values.
 
.PARAMETER ActualValue
The value to test.
#>

    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [AllowNull()]
        [object[]]$Allowed,

        [AllowNull()]
        [object[]]$Blocked,

        [AllowNull()]
        [string]$ActualValue
    )

    if ($Blocked -and $Blocked.Count -gt 0) {
        foreach ($value in @($Blocked)) {
            if ([string]::Equals([string]$value, [string]$ActualValue, [System.StringComparison]::OrdinalIgnoreCase)) {
                return $false
            }
        }
    }

    if ($Allowed -and $Allowed.Count -gt 0) {
        foreach ($value in @($Allowed)) {
            if ([string]::Equals([string]$value, [string]$ActualValue, [System.StringComparison]::OrdinalIgnoreCase)) {
                return $true
            }
        }

        return $false
    }

    return $true
}

function Test-PackageModelRequirementChecks {
<#
.SYNOPSIS
Evaluates typed PackageModel requirement checks for one selected release.
 
.DESCRIPTION
Runs the current requirement kinds against the resolved runtime context and
returns the normalized requirement results and overall acceptance state.
 
.PARAMETER PackageModelConfig
The resolved PackageModel config object.
 
.PARAMETER Requirements
The effective release requirements object.
#>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [psobject]$PackageModelConfig,

        [AllowNull()]
        [psobject]$Requirements
    )

    $results = New-Object System.Collections.Generic.List[object]
    $allAccepted = $true
    $checks = if ($Requirements -and $Requirements.PSObject.Properties['checks']) { @($Requirements.checks) } else { @() }

    foreach ($check in @($checks)) {
        if ($null -eq $check) {
            continue
        }

        $kind = [string]$check.kind
        $accepted = $false
        $actualValue = $null
        $expectedSummary = $null

        switch -Exact ($kind) {
            'osFamily' {
                $actualValue = [string]$PackageModelConfig.Platform
                $allowedValues = if ($check.PSObject.Properties['allowed']) { @($check.allowed) } else { @() }
                $blockedValues = if ($check.PSObject.Properties['blocked']) { @($check.blocked) } else { @() }
                $accepted = Test-PackageModelRequirementAllowedBlockedMatch -Allowed $allowedValues -Blocked $blockedValues -ActualValue $actualValue
                $expectedSummary = @(
                    if ($allowedValues.Count -gt 0) { 'allowed=' + (($allowedValues | ForEach-Object { [string]$_ }) -join ',') }
                    if ($blockedValues.Count -gt 0) { 'blocked=' + (($blockedValues | ForEach-Object { [string]$_ }) -join ',') }
                ) -join '; '
            }
            'cpuArchitecture' {
                $actualValue = [string]$PackageModelConfig.Architecture
                $allowedValues = if ($check.PSObject.Properties['allowed']) { @($check.allowed) } else { @() }
                $blockedValues = if ($check.PSObject.Properties['blocked']) { @($check.blocked) } else { @() }
                $accepted = Test-PackageModelRequirementAllowedBlockedMatch -Allowed $allowedValues -Blocked $blockedValues -ActualValue $actualValue
                $expectedSummary = @(
                    if ($allowedValues.Count -gt 0) { 'allowed=' + (($allowedValues | ForEach-Object { [string]$_ }) -join ',') }
                    if ($blockedValues.Count -gt 0) { 'blocked=' + (($blockedValues | ForEach-Object { [string]$_ }) -join ',') }
                ) -join '; '
            }
            'osVersion' {
                $actualValue = [string]$PackageModelConfig.OSVersion
                $expectedVersion = ConvertTo-PackageModelVersion -VersionText ([string]$check.value)
                $actualVersion = ConvertTo-PackageModelVersion -VersionText $actualValue
                $operator = [string]$check.operator
                $accepted = switch -Exact ($operator) {
                    '=' { $actualVersion -eq $expectedVersion }
                    '==' { $actualVersion -eq $expectedVersion }
                    '!=' { $actualVersion -ne $expectedVersion }
                    '>' { $actualVersion -gt $expectedVersion }
                    '>=' { $actualVersion -ge $expectedVersion }
                    '<' { $actualVersion -lt $expectedVersion }
                    '<=' { $actualVersion -le $expectedVersion }
                    default { throw "Unsupported PackageModel osVersion requirement operator '$operator'." }
                }
                $expectedSummary = '{0} {1}' -f $operator, [string]$check.value
            }
            default {
                throw "Unsupported PackageModel requirement kind '$kind'."
            }
        }

        if (-not $accepted) {
            $allAccepted = $false
        }

        $results.Add([pscustomobject]@{
            Kind     = $kind
            Accepted = $accepted
            Actual   = $actualValue
            Expected = $expectedSummary
        }) | Out-Null
    }

    return [pscustomobject]@{
        Accepted = $allAccepted
        Checks   = @($results.ToArray())
    }
}

function Resolve-PackageModelPackage {
<#
.SYNOPSIS
Attaches the selected release to a PackageModel result.
 
.DESCRIPTION
Filters the definition releases from the resolved PackageModel config by
platform, architecture, and release track, selects the newest matching release,
applies definition-level release defaults, and attaches the package-facing
data to the result object.
 
.PARAMETER PackageModelResult
The PackageModel result object to enrich.
 
.EXAMPLE
Resolve-PackageModelPackage -PackageModelResult $result
#>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [psobject]$PackageModelResult
    )

    $packageModelConfig = $PackageModelResult.PackageModelConfig
    $definition = $packageModelConfig.Definition
    $effectiveReleaseTrack = if ([string]::IsNullOrWhiteSpace($packageModelConfig.ReleaseTrack)) { 'none' } else { [string]$packageModelConfig.ReleaseTrack }

    $matchingPackages = @(
        foreach ($package in @($definition.releases)) {
            $constraints = if ($package.PSObject.Properties['constraints']) { $package.constraints } else { $null }
            $osConstraints = if ($constraints -and $constraints.PSObject.Properties['os']) { @($constraints.os) } else { @() }
            $cpuConstraints = if ($constraints -and $constraints.PSObject.Properties['cpu']) { @($constraints.cpu) } else { @() }
            $packageReleaseTrack = if ($package.PSObject.Properties['releaseTrack'] -and -not [string]::IsNullOrWhiteSpace([string]$package.releaseTrack)) {
                [string]$package.releaseTrack
            }
            else {
                'none'
            }

            if ([string]::Equals($packageReleaseTrack, $effectiveReleaseTrack, [System.StringComparison]::OrdinalIgnoreCase) -and
                (Test-PackageModelConstraintSetMatch -Values $osConstraints -ActualValue $packageModelConfig.Platform) -and
                (Test-PackageModelConstraintSetMatch -Values $cpuConstraints -ActualValue $packageModelConfig.Architecture)) {
                $package
            }
        }
    )

    if (-not $matchingPackages) {
        throw "No PackageModel release matched platform '$($packageModelConfig.Platform)', architecture '$($packageModelConfig.Architecture)', and releaseTrack '$($packageModelConfig.ReleaseTrack)'."
    }

    if (-not [string]::Equals([string]$packageModelConfig.SelectionStrategy, 'latestByVersion', [System.StringComparison]::OrdinalIgnoreCase)) {
        throw "Unsupported PackageModel selection strategy '$($packageModelConfig.SelectionStrategy)'."
    }

    $selectedPackage = $matchingPackages |
        Sort-Object -Descending -Property @{ Expression = { ConvertTo-PackageModelVersion -VersionText $_.version } } |
        Select-Object -First 1
    $selectedPackage = Resolve-PackageModelEffectiveRelease -Definition $definition -Release $selectedPackage

    $PackageModelResult.Package = $selectedPackage
    $PackageModelResult.EffectiveRelease = $selectedPackage
    $PackageModelResult.PackageId = [string]$selectedPackage.id
    $PackageModelResult.PackageVersion = [string]$selectedPackage.version
    $requirementsEvaluation = Test-PackageModelRequirementChecks -PackageModelConfig $packageModelConfig -Requirements $selectedPackage.requirements
    $PackageModelResult.Requirements = @($requirementsEvaluation.Checks)
    if (-not $requirementsEvaluation.Accepted) {
        $failedRequirementText = @(
            foreach ($checkResult in @($requirementsEvaluation.Checks)) {
                if (-not $checkResult.Accepted) {
                    "{0} actual='{1}' expected='{2}'" -f $checkResult.Kind, [string]$checkResult.Actual, [string]$checkResult.Expected
                }
            }
        ) -join '; '
        throw "PackageModel release '$($selectedPackage.id)' does not satisfy requirements.checks. $failedRequirementText"
    }

    $selectedFlavor = if ($selectedPackage.PSObject.Properties['flavor']) { [string]$selectedPackage.flavor } else { 'default' }
    Write-PackageModelExecutionMessage -Message ("[STATE] Selected release '{0}' version '{1}' for platform '{2}', architecture '{3}', releaseTrack '{4}', flavor '{5}'." -f $PackageModelResult.PackageId, $PackageModelResult.PackageVersion, $packageModelConfig.Platform, $packageModelConfig.Architecture, $effectiveReleaseTrack, $selectedFlavor)
    if (@($requirementsEvaluation.Checks).Count -gt 0) {
        $requirementSummary = @(
            foreach ($checkResult in @($requirementsEvaluation.Checks)) {
                "{0}={1}" -f $checkResult.Kind, $(if ($checkResult.Accepted) { 'accepted' } else { 'rejected' })
            }
        ) -join ', '
        Write-PackageModelExecutionMessage -Message ("[STATE] Requirement checks accepted: {0}." -f $requirementSummary)
    }

    return $PackageModelResult
}