internal/Get-MtLatestModuleVersion.ps1

function Get-MtLatestModuleVersion {
    <#
    .SYNOPSIS
    Retrieves the latest stable version (non-prerelease) of a module from the PowerShell Gallery.
    #>

    [CmdletBinding()]
    [OutputType([version])]
    param(
        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string] $Name = 'Maester',

        [Parameter(Mandatory = $false)]
        [ValidateRange(1, 300)]
        [int] $TimeoutSec = 10
    )

    function ConvertTo-StableVersion {
        <#
        .SYNOPSIS
        Converts an input version string to a [version] object if it's a stable version.

        .DESCRIPTION
        ConvertTo-StableVersion attempts to parse the input version and returns a [version] object if it's a stable version.
        If the input is null, empty, or contains a prerelease suffix (indicated by a hyphen), it returns $null.
        This ensures that only stable versions are considered when determining the latest module version.
        #>

        [CmdletBinding()]
        [OutputType([version])]
        param(
            [Parameter(Mandatory = $true)]
            [AllowNull()]
            [object] $InputVersion
        )

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

        # If the version is null or empty after converting to string, return null.
        $VersionString = [string]$InputVersion
        if ([string]::IsNullOrWhiteSpace($VersionString)) {
            return $null
        }

        # Keep behavior aligned with Find-Module default by ignoring prerelease versions (indicated by a hyphen).
        if ($VersionString -match '-') {
            return $null
        }

        try {
            return [version]$versionString
        } catch {
            Write-Warning "Could not parse version '$versionString': $_"
            return $null
        }
    } # End of ConvertTo-StableVersion

    function Get-LatestVersionFromOData {
        <#
        .SYNOPSIS
        Retrieves the latest stable version (non-prerelease) of a module from the PowerShell Gallery using the OData API.
        #>

        [CmdletBinding()]
        [OutputType([version])]
        param(
            [Parameter(Mandatory = $true)]
            [string] $ModuleName,

            [Parameter()]
            [int] $RequestTimeoutSec = 10
        )

        $escapedModuleName = $ModuleName.Replace("'", "''")
        $uri = "https://www.powershellgallery.com/api/v2/FindPackagesById()?id='$escapedModuleName'&`$filter=IsLatestVersion and not IsPrerelease"
        $response = Invoke-RestMethod -Uri $uri -Method Get -TimeoutSec $RequestTimeoutSec -ErrorAction Stop

        $xmlNamespaceManager = $null
        if ($response -is [System.Xml.XmlElement] -and $null -ne $response.OwnerDocument) {
            $xmlNamespaceManager = [System.Xml.XmlNamespaceManager]::new($response.OwnerDocument.NameTable)
            $xmlNamespaceManager.AddNamespace('m', 'http://schemas.microsoft.com/ado/2007/08/dataservices/metadata')
            $xmlNamespaceManager.AddNamespace('d', 'http://schemas.microsoft.com/ado/2007/08/dataservices')
        }

        $rawVersion = $null
        if ($null -ne $response.properties -and $null -ne $response.properties.Version) {
            $rawVersion = $response.properties.Version
        } elseif ($response -is [System.Xml.XmlElement] -and $null -ne $xmlNamespaceManager) {
            $versionNode = $response.SelectSingleNode('m:properties/d:Version', $xmlNamespaceManager)
            if ($null -ne $versionNode) {
                $rawVersion = $versionNode.InnerText
            }
        }

        $stableVersion = ConvertTo-StableVersion -InputVersion $rawVersion
        if ($null -ne $stableVersion) {
            return $stableVersion
        } else {
            Write-Verbose "No stable version found in OData response for '$ModuleName'. Raw version value: '$rawVersion'"
            return $null
        }
    } # End of Get-LatestVersionFromOData

    function Get-LatestVersionFromPSResourceGet {
        [CmdletBinding()]
        [OutputType([version])]
        param(
            [Parameter(Mandatory = $true)]
            [string] $ModuleName
        )

        if (-not (Get-Command 'Find-PSResource' -ErrorAction SilentlyContinue)) {
            Write-Verbose 'Skipping Find-PSResource fallback because Microsoft.PowerShell.PSResourceGet is not installed.'
            return $null
        }

        $resource = Find-PSResource -Name $ModuleName -ErrorAction Stop | Select-Object -First 1
        if ($null -eq $resource) {
            Write-Verbose "Find-PSResource returned no results for '$ModuleName'."
            return $null
        }

        return ConvertTo-StableVersion -InputVersion $resource.Version
    } # End of Get-LatestVersionFromPSResourceGet

    function Get-LatestVersionFromPowerShellGet {
        [CmdletBinding()]
        [OutputType([version])]
        param(
            [Parameter(Mandatory = $true)]
            [string] $ModuleName
        )

        # NuGet provider readiness check avoids interactive provider bootstrap prompts in PS7 environments.
        $nugetProvider = Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue
        if ($null -eq $nugetProvider) {
            Write-Verbose 'Skipping Find-Module fallback because NuGet provider is not installed.'
            return $null
        }

        $module = Find-Module -Name $ModuleName -ErrorAction Stop
        return ConvertTo-StableVersion -InputVersion $module.Version
    } # End of Get-LatestVersionFromPowerShellGet

    # Main logic of Get-MtLatestModuleVersion starts here.

    # Attempt to get the latest version from the OData API first, as it's the most direct source and doesn't rely on installed modules.
    try {
        $ODataVersion = Get-LatestVersionFromOData -ModuleName $Name -RequestTimeoutSec $TimeoutSec
        if ($null -ne $ODataVersion) {
            return $ODataVersion
        }
    } catch {
        Write-Verbose "Unable to get latest version from PowerShell Gallery OData API for '$Name': $_"
    }

    # If OData lookup fails, fall back to PSResourceGet if available, then PowerShellGet, as these may have cached data even when the API is unreachable.
    if (Get-Command 'Find-PSResource' -ErrorAction SilentlyContinue) {
        try {
            return Get-LatestVersionFromPSResourceGet -ModuleName $Name
        } catch {
            Write-Verbose "Unable to get latest version using Find-PSResource for '$Name': $_"
        }
    }

    # As a last resort, attempt to get the latest version using PowerShellGet's Find-Module, but only if the NuGet provider is available to avoid unnecessary errors and delays.
    if (Get-Command 'Find-Module' -ErrorAction SilentlyContinue) {
        try {
            return Get-LatestVersionFromPowerShellGet -ModuleName $Name
        } catch {
            Write-Verbose "Unable to get latest version using Find-Module for '$Name': $_"
        }
    }

    # If all methods fail, return null to indicate that the latest version could not be determined.
    return $null
}