Private/Test-AvmModuleIdentifierMatch.ps1

function Test-AvmModuleIdentifierMatch {
    <#
    .SYNOPSIS
        Tests whether an AVM module reference matches a single config-supplied identifier
        pattern (used for ignoreModules / pinnedVersions matching in Get-AvmUpdatePlan).

    .DESCRIPTION
        A pattern matches a reference if it equals — case-insensitively, with glob wildcard
        support (*, ?) via PowerShell's -like operator — any of:
          - the short module name (e.g. 'storage-account' for Bicep,
            'avm-res-storage-storageaccount' for Terraform)
          - the full registryPath (e.g. 'bicep/avm/res/storage/storage-account',
            'Azure/avm-res-storage-storageaccount/azurerm')
          - for Bicep references, the AVM module path 'avm/<category>/<group>/<module>'
            (e.g. 'avm/res/network/nsg'), so glob patterns like 'avm/res/network/*' work
            against the same path shape used in AVM documentation/tags.

    .PARAMETER Reference
        The AVM module reference object (from Get-AvmModuleReference).

    .PARAMETER Pattern
        The identifier or glob pattern from config (ignoreModules[] entry, or a
        pinnedVersions key).

    .OUTPUTS
        Boolean.
    #>

    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory)]
        [PSCustomObject]$Reference,

        [Parameter(Mandatory)]
        [string]$Pattern
    )

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

    $candidates = [System.Collections.Generic.List[string]]::new()
    if ($Reference.module) { $candidates.Add($Reference.module) }
    if ($Reference.registryPath) { $candidates.Add($Reference.registryPath) }
    if ($Reference.category -and $Reference.group -and $Reference.module) {
        $candidates.Add("avm/$($Reference.category)/$($Reference.group)/$($Reference.module)")
    }

    foreach ($candidate in $candidates) {
        if ($candidate -like $Pattern) { return $true }
    }

    return $false
}

function Get-AvmPinnedVersion {
    <#
    .SYNOPSIS
        Resolves the highest version at or below a configured pin from a list of available
        versions.

    .DESCRIPTION
        Supports two pin shapes:
          - A full version (e.g. '0.4.2'): the highest available version <= the pin.
          - A major.minor shorthand (e.g. '0.4'): the highest available version whose
            major.minor equals the pin (i.e. "pin to the 0.4.x line"), regardless of patch.
        $AllVersions is expected pre-sorted descending (as returned by Get-AvmLatestVersion);
        the first match found is returned.

    .OUTPUTS
        The matching version string, or $null if no available version satisfies the pin.
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory)]
        [string[]]$AllVersions,

        [Parameter(Mandatory)]
        [string]$Pin
    )

    $isMinorOnly = $Pin -match '^\d+\.\d+$'
    $pinParsed   = ConvertTo-SemVer -Version $Pin
    if (-not $pinParsed) { return $null }

    foreach ($v in $AllVersions) {
        $vParsed = ConvertTo-SemVer -Version $v
        if (-not $vParsed) { continue }

        if ($isMinorOnly) {
            if ($vParsed.Major -eq $pinParsed.Major -and $vParsed.Minor -eq $pinParsed.Minor) { return $v }
        } elseif ($vParsed -le $pinParsed) {
            return $v
        }
    }

    return $null
}