Private/Resolve-ForgeConfiguration.ps1

function Resolve-ForgeConfiguration {
    <#
    .SYNOPSIS
        Enriches a Forge configuration object with GitHub release versions and source package information.
 
    .DESCRIPTION
        Takes a configuration object from Read-ForgeConfiguration, fetches all releases from the specified GitHub repository,
        expands them by all flavors, and queries package information for each source.
         
        Adds a 'versions' property containing all expanded releases to the configuration object.
         
        For each source, adds published and missing version information, as well as API key and publishing status details.
         
        Returns the enriched configuration object.
 
    .PARAMETER Configuration
        The configuration object returned by Read-ForgeConfiguration. This object is enriched with a 'versions' property and updated 'sources' information.
 
    .EXAMPLE
        $config = Read-ForgeConfiguration -Path 'Samples/firebird.forge.yaml'
        $resolved = Resolve-ForgeConfiguration -Configuration $config
         
        Enriches the configuration with release and source package information.
 
    .OUTPUTS
        PSCustomObject
        The enriched configuration object, including expanded releases and source publishing information.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [object]$Configuration
    )

    Write-VerboseMark -Message 'Starting Resolve-ForgeConfiguration.'

    # Parse GitHub repo owner/name from releases.source
    $sourceUrl = $Configuration.releases.source
    if ($sourceUrl -notmatch '^https://github.com/(?<owner>[^/]+)/(?<repo>[^/]+)') {
        throw 'Invalid GitHub repository URL in releases.source.'
    }
    $repoOwner = $Matches['owner']
    $repoName = $Matches['repo']
    Write-VerboseMark -Message "Repository: $($repoOwner)/$($repoName)"

    # Get all releases from GitHub
    $releases = Find-GitHubReleases -RepositoryOwner $repoOwner -RepositoryName $repoName
    Write-VerboseMark -Message "Fetched $($releases.Count) releases from GitHub."

    # Expand all flavors and collect versions
    $allVersions = @()
    foreach ($flavorName in $Configuration.releases.flavors.Keys) {
        $flavor = $Configuration.releases.flavors[$flavorName]

        # If assetsPattern has a named capture group, transpose by it
        $resolveParameters = @{
            InputObject    = $releases
            VersionPattern = $flavor.versionPattern
            AssetPattern   = $flavor.assetsPattern
        }
        if ($flavor.assetsPattern -match '\(\?<([a-zA-Z_][a-zA-Z0-9_]*)>') {
            $resolveParameters['TransposeProperty'] = $Matches[1]
        }
        if ($flavor.minimumVersion) {
            $resolveParameters['MinimumVersion'] = $flavor.minimumVersion
        }

        $versions = Resolve-GitHubReleases @resolveParameters
        $allVersions += $versions | Add-Member -MemberType NoteProperty -Name 'flavor' -Value $flavorName -PassThru
    }

    # Flatten, sort by version descending and add to configuration
    $allVersions = $allVersions | Sort-Object -Property version -Descending
    $Configuration | Add-Member -NotePropertyName 'versions' -NotePropertyValue $allVersions -Force
    Write-VerboseMark -Message "Added $($allVersions.Count) versions to configuration."

    # Query all chocolatey sources
    foreach ($sourceName in $Configuration.sources.Keys) {
        $source = $Configuration.sources[$sourceName]

        $findArguments = @{
            PackageName = $Configuration.package
            SourceUrl   = $source.url
        }

        if ($source.url.StartsWith('https://nuget.pkg.github.com')) {
            # GitHub requires username and password (api key).

            # GitHub username is always the owner from the URL.
            $userName = ($source.url -replace '^https://nuget.pkg.github.com/', '') -replace '/.*', ''

            $password = Expand-EnvironmentVariables $source.apiKey
            if (-not $password) {
                throw "GitHub source '$sourceName' requires the environment variable $($source.apiKey) to be set."
            }

            $findArguments['User'] = $userName
            $findArguments['Password'] = $password
        }

        if ($source.url.StartsWith('https://gitlab.com')) {
            # GitLab requires username and password (api key).

            $userName = $source.username
            if (-not $userName) {
                throw "GitLab source '$sourceName' requires a username to be set in the configuration."
            }

            $password = Expand-EnvironmentVariables $source.apiKey
            if (-not $password) {
                throw "GitLab source '$sourceName' requires the environment variable $($source.apiKey) to be set."
            }

            $findArguments['User'] = $source.username
            $findArguments['Password'] = Expand-EnvironmentVariables $source.apiKey
        }

        $source.publishedVersions = Find-ChocolateyPublishedVersions @findArguments

        # Find missing versions: those in allVersions but not in publishedVersions.
        $pubVersions = $source.publishedVersions | ForEach-Object { [semver]::new($_.Major, $_.Minor, $_.Build) }
        $source.missingVersions = $allVersions.version | Where-Object { $pubVersions -notcontains $_ }

        Write-VerboseMark -Message "Queried source '$sourceName' for package info. Found $($source.publishedVersions.Count) published versions, $($source.missingVersions.Count) missing versions."

        # Skip sources that have no API key available for publishing
        $resolvedApiKey = $null
        $skipReason = $null
        $warningMessage = $null
        if ($source.apiKey) {
            $resolvedApiKey = Expand-EnvironmentVariables $source.apiKey
            if (-not $resolvedApiKey) {
                Write-VerboseMark "Source '$($sourceName)' environment variable $($source.apiKey) is not set. Skipping publishing."
                $skipReason = "Environment variable $($source.apiKey) not set."
            } elseif ($resolvedApiKey -eq $source.apiKey) {
                Write-VerboseMark "Source '$($sourceName)' has an API key stored in plain text in the configuration file (not recommended). Please consider using and environment variable instead."
                $warningMessage = 'API key stored in plain text in the configuration file (not recommended).'
            }
        } else {
            Write-VerboseMark "Source '$($sourceName)' does not have an API key configured. Skipping publishing."
            $skipReason = 'No API key in the configuration file'
        }

        $source | Add-Member -MemberType NoteProperty -Name 'resolvedApiKey' -Value $resolvedApiKey -Force
        $source | Add-Member -MemberType NoteProperty -Name 'skipReason' -Value $skipReason -Force
        $source | Add-Member -MemberType NoteProperty -Name 'warningMessage' -Value $warningMessage -Force
    }

    Write-VerboseMark -Message 'Resolve-ForgeConfiguration completed.'
    return $Configuration
}