public/Get-WtwUpdateStatus.ps1

function Get-WtwGalleryLatestVersionText {
    <#
    .SYNOPSIS
        Read the latest published wtw version from the PowerShell Gallery.
    .DESCRIPTION
        ``FindPackagesById()?id='wtw'&$filter=IsLatestVersion`` lags behind
        newly published versions — it kept returning 0.2.26 after 0.2.28 was
        already ``IsLatestVersion`` on ``Packages()``. Prefer ``Packages()``
        filtered to the latest package; if that fails, take the max version
        from the unfiltered ``FindPackagesById`` list.
    #>

    [CmdletBinding()]
    param(
        [ValidateRange(1, 30)]
        [int] $TimeoutSec = 5
    )

    $versionPattern = '<d:Version>(?<version>[^<]+)</d:Version>'
    $regexOptions = [Text.RegularExpressions.RegexOptions]::CultureInvariant

    $latestUri = "https://www.powershellgallery.com/api/v2/Packages()?`$filter=Id eq 'wtw' and IsLatestVersion eq true"
    $response = Invoke-WebRequest -Uri $latestUri -TimeoutSec $TimeoutSec -ErrorAction Stop
    $match = [regex]::Match([string]$response.Content, $versionPattern, $regexOptions)
    if ($match.Success) {
        $text = $match.Groups['version'].Value
        [version]$text | Out-Null
        return $text
    }

    $allUri = "https://www.powershellgallery.com/api/v2/FindPackagesById()?id='wtw'"
    $all = Invoke-WebRequest -Uri $allUri -TimeoutSec $TimeoutSec -ErrorAction Stop
    $parsed = foreach ($hit in [regex]::Matches([string]$all.Content, $versionPattern, $regexOptions)) {
        $parsedVersion = $null
        if ([version]::TryParse($hit.Groups['version'].Value, [ref] $parsedVersion)) {
            $parsedVersion
        }
    }
    $best = @($parsed) | Sort-Object -Descending | Select-Object -First 1
    if ($null -eq $best) {
        throw 'The PowerShell Gallery response did not contain a wtw version.'
    }
    return $best.ToString()
}

function Get-WtwUpdateStatus {
    <#
    .SYNOPSIS
        Report whether a newer wtw is published on the PowerShell Gallery.
    .DESCRIPTION
        Caches the Gallery answer in ~/.wtw/update-check.json so repeated calls
        cost nothing. An offline or unreachable Gallery is a normal outcome: the
        failed attempt is cached too, so a disconnected workstation does not
        retry on every command.
 
        This is the function the detached background refresh runs; the
        foreground notice never calls it, because it must not wait on a network
        request. See Write-WtwUpdateNotice.
    .EXAMPLE
        Get-WtwUpdateStatus -Force
        Refreshes the cache and returns the current/latest comparison.
    #>

    [CmdletBinding()]
    param(
        [TimeSpan] $CacheTtl = [TimeSpan]::FromHours(24),

        [ValidateRange(1, 30)]
        [int] $TimeoutSec = 5,

        [string] $CachePath = (Join-Path $HOME '.wtw/update-check.json'),

        [switch] $Force
    )

    $manifestPath = Join-Path $script:WtwModuleRoot 'wtw.psd1'
    $currentVersion = $null
    try {
        $currentVersion = [version](Import-PowerShellDataFile -LiteralPath $manifestPath).ModuleVersion
    } catch {
        $currentVersion = $null
    }

    $cacheFile = [IO.Path]::GetFullPath($CachePath)
    $cache = Read-WtwJsonFile -Path $cacheFile

    $now = [DateTime]::UtcNow
    $checkedAt = ConvertTo-WtwUtcDate -Value (Get-WtwPropertyValue -Object $cache -Name 'CheckedAtUtc')
    $cacheIsFresh = -not $Force -and $null -ne $checkedAt -and ($now - $checkedAt) -lt $CacheTtl

    $latestText = [string](Get-WtwPropertyValue -Object $cache -Name 'LatestVersion' -DefaultValue '')
    $status = [string](Get-WtwPropertyValue -Object $cache -Name 'Status' -DefaultValue 'Unknown')
    $source = 'cache'

    if (-not $cacheIsFresh) {
        $source = 'gallery'
        $status = 'Unavailable'
        try {
            $latestText = Get-WtwGalleryLatestVersionText -TimeoutSec $TimeoutSec
            $status = 'Available'
        } catch {
            # Offline is normal. The failed attempt is cached below so wtw does
            # not retry - or pause - on every command.
        }

        try {
            $cacheDirectory = Split-Path -Parent $cacheFile
            New-Item -ItemType Directory -Path $cacheDirectory -Force -ErrorAction Stop | Out-Null
            [ordered]@{
                CheckedAtUtc  = $now.ToString('O')
                LatestVersion = $latestText
                Status        = $status
            } | ConvertTo-Json | Set-Content -LiteralPath $cacheFile -Encoding utf8 -ErrorAction Stop
        } catch {
            # Update checks must never prevent wtw from running.
        }
        $checkedAt = $now
    }

    $latestVersion = $null
    if (-not [version]::TryParse($latestText, [ref] $latestVersion)) {
        $latestVersion = $null
    }

    [pscustomobject]@{
        CurrentVersion  = $currentVersion
        LatestVersion   = $latestVersion
        UpdateAvailable = $null -ne $latestVersion -and $null -ne $currentVersion -and $latestVersion -gt $currentVersion
        Status          = $status
        CheckedAtUtc    = $checkedAt
        Source          = $source
    }
}