Modules/businessdev.ALbuild.Feeds/Public/Get-BcDependencyReconciliation.ps1

function Get-BcDependencyReconciliation {
    <#
    .SYNOPSIS
        Reconciles an app's manifest dependencies against what a container already has and what the
        configured feeds can provide.
 
    .DESCRIPTION
        Read-only (installs nothing). For each dependency (from app.json 'dependencies'), reports whether it
        is already satisfied by an app installed in the container, otherwise whether a configured feed can
        provide a compatible version, otherwise that it is missing. Microsoft first-party apps ship with the
        artifact, so they normally show up as satisfied by the container.
 
        Feeds are taken from -Feeds, else the currently registered feeds (Get-BcFeed) -- register them first
        with Register-BcFeed / Register-BcFeed -FromProjectConfig.
 
    .PARAMETER ContainerName
        The container to reconcile against.
 
    .PARAMETER Dependencies
        The app.json 'dependencies' entries: objects with id (GUID), optional name/publisher, and version
        (the minimum required version).
 
    .PARAMETER Feeds
        Feed definitions to search (default: all currently registered feeds).
 
    .OUTPUTS
        PSCustomObject: containerName, feedsSearched, summary { total, satisfied, availableFromFeed, missing },
        satisfied[], availableFromFeed[], missing[].
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $ContainerName,
        [Parameter(Mandatory)] [AllowEmptyCollection()] [object[]] $Dependencies,
        [object[]] $Feeds
    )

    $feedList = if ($Feeds) { @($Feeds) } else { @(Get-BcFeed) }
    $installed = @(Get-BcContainerAppInfo -Name $ContainerName)

    $satisfied = [System.Collections.Generic.List[object]]::new()
    $available = [System.Collections.Generic.List[object]]::new()
    $missing = [System.Collections.Generic.List[object]]::new()

    foreach ($d in @($Dependencies)) {
        $appId = if ($d.PSObject.Properties.Name -contains 'id' -and $d.id) { "$($d.id)" }
        elseif ($d.PSObject.Properties.Name -contains 'appId' -and $d.appId) { "$($d.appId)" } else { '' }
        $req = "$($d.version)"
        $name = "$($d.name)"; $publisher = "$($d.publisher)"

        $inst = $installed | Where-Object { "$($_.AppId)" -eq $appId } | Select-Object -First 1
        $instOk = $false
        if ($inst) { try { $instOk = ([version]"$($inst.Version)" -ge [version]$req) } catch { $instOk = $true } }
        if ($instOk) {
            $satisfied.Add([PSCustomObject]@{ appId = $appId; name = $name; version = "$($inst.Version)"; source = 'container' })
            continue
        }

        $found = @()
        if ($feedList.Count -gt 0 -and $appId) {
            try { $found = @(Find-BcPackage -AppId $appId -Name $name -Publisher $publisher -Feeds $feedList) } catch { $found = @() }
        }
        $ok = @($found | Where-Object { try { [version]"$($_.Version)" -ge [version]$req } catch { $false } })
        if ($ok.Count -gt 0) {
            $best = $ok | Sort-Object { [version]"$($_.Version)" } -Descending | Select-Object -First 1
            $available.Add([PSCustomObject]@{
                    appId            = $appId; name = $name; requiredVersion = $req
                    feed             = $best.Feed; packageId = $best.PackageId; bestVersion = "$($best.Version)"
                    availableVersions = @($ok | ForEach-Object { "$($_.Version)" })
                })
        }
        else {
            $reason = if ($inst) { "Installed version $($inst.Version) is below required $req and no feed has a newer one" }
            else { 'Not in container and not found in any configured feed' }
            $missing.Add([PSCustomObject]@{
                    appId           = $appId; name = $name; publisher = $publisher; requiredVersion = $req
                    installedVersion = "$($inst.Version)"; reason = $reason; action = 'Provide via publish-app / deps install'
                })
        }
    }

    return [PSCustomObject]@{
        containerName     = $ContainerName
        feedsSearched     = @($feedList | ForEach-Object { $_.Name })
        summary           = [PSCustomObject]@{ total = @($Dependencies).Count; satisfied = $satisfied.Count; availableFromFeed = $available.Count; missing = $missing.Count }
        satisfied         = @($satisfied)
        availableFromFeed = @($available)
        missing           = @($missing)
    }
}