Private/Logic/Eigenverft.Manifested.Sandbox.Runtime.VsCode.Package.ps1

<#
    Eigenverft.Manifested.Sandbox.Runtime.VsCode.Package
#>


function ConvertTo-VSCodeSha256 {
    [CmdletBinding()]
    param(
        [string]$Sha256
    )

    if ([string]::IsNullOrWhiteSpace($Sha256)) {
        return $null
    }

    $normalized = $Sha256.Trim().ToLowerInvariant()
    if ($normalized -notmatch '^[a-f0-9]{64}$') {
        return $null
    }

    return $normalized
}

function Invoke-ManifestedVSCodeHeadRequest {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$Uri
    )

    Enable-ManifestedTls12Support

    $request = [System.Net.HttpWebRequest]::Create($Uri)
    $request.Method = 'HEAD'
    $request.AllowAutoRedirect = $false
    $request.UserAgent = 'Eigenverft.Manifested.Sandbox'

    $proxy = [System.Net.WebRequest]::GetSystemWebProxy()
    if ($proxy) {
        $proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials
        $request.Proxy = $proxy
    }

    $response = $null
    try {
        try {
            $response = [System.Net.HttpWebResponse]$request.GetResponse()
        }
        catch [System.Net.WebException] {
            if ($_.Exception.Response) {
                $response = [System.Net.HttpWebResponse]$_.Exception.Response
            }
            else {
                throw
            }
        }

        [pscustomobject]@{
            StatusCode = [int]$response.StatusCode
            Location   = $response.Headers['Location']
            Sha256     = ConvertTo-VSCodeSha256 -Sha256 $response.Headers['X-SHA256']
            Headers    = $response.Headers
        }
    }
    finally {
        if ($response) {
            $response.Close()
        }
    }
}

function Get-VSCodeRelease {
    [CmdletBinding()]
    param(
        [string]$Flavor
    )

    if ([string]::IsNullOrWhiteSpace($Flavor)) {
        $Flavor = Get-VSCodeFlavor
    }

    $channel = 'stable'
    $latestUri = 'https://update.code.visualstudio.com/latest/{0}/{1}' -f (Get-VSCodeUpdateTarget -Flavor $Flavor), $channel
    $headResult = Invoke-ManifestedVSCodeHeadRequest -Uri $latestUri

    if ($headResult.StatusCode -notin @(301, 302, 303, 307, 308)) {
        throw "Unexpected VS Code update response status code $($headResult.StatusCode)."
    }
    if ([string]::IsNullOrWhiteSpace($headResult.Location)) {
        throw 'The VS Code update service did not return a redirect location.'
    }
    if ([string]::IsNullOrWhiteSpace($headResult.Sha256)) {
        throw 'The VS Code update service did not return an X-SHA256 header.'
    }

    $resolvedUri = [uri]$headResult.Location
    $fileName = Split-Path -Leaf $resolvedUri.AbsolutePath
    $match = [regex]::Match($fileName, '^(VSCode-(win32-(?:x64|arm64))-(\d+\.\d+\.\d+)\.zip)$')
    if (-not $match.Success) {
        throw "Could not parse the VS Code archive name '$fileName'."
    }
    if ($match.Groups[2].Value -ne $Flavor) {
        throw "The VS Code update service resolved flavor '$($match.Groups[2].Value)' instead of '$Flavor'."
    }

    [pscustomobject]@{
        TagName     = $channel
        Version     = $match.Groups[3].Value
        Flavor      = $Flavor
        Channel     = $channel
        FileName    = $match.Groups[1].Value
        Path        = $null
        Source      = 'online'
        Action      = 'SelectedOnline'
        DownloadUrl = $resolvedUri.AbsoluteUri
        Sha256      = $headResult.Sha256
        ShaSource   = 'X-SHA256'
        ReleaseUrl  = $latestUri
    }
}

function Get-CachedVSCodeRuntimePackages {
    [CmdletBinding()]
    param(
        [string]$Flavor,
        [string]$LocalRoot = (Get-ManifestedLocalRoot)
    )

    if ([string]::IsNullOrWhiteSpace($Flavor)) {
        $Flavor = Get-VSCodeFlavor
    }

    $layout = Get-ManifestedLayout -LocalRoot $LocalRoot
    $persistedDetails = Get-VSCodePersistedPackageDetails -LocalRoot $layout.LocalRoot
    $pattern = '^VSCode-(' + [regex]::Escape($Flavor) + ')-(\d+\.\d+\.\d+)\.zip$'

    return @(Get-ManifestedArchiveCachedPackages -CacheRootPath $layout.VsCodeCacheRoot -Pattern $pattern -BuildPackageInfo {
        param($item, $matchTable)

        $persistedAsset = Get-ManifestedArchivePersistedAssetDetails -PersistedDetails $persistedDetails -AssetName $item.Name
        $channel = if ($persistedAsset -and $persistedAsset.Channel) { $persistedAsset.Channel } else { 'stable' }
        [pscustomobject]@{
            TagName     = $channel
            Version     = $matchTable[2]
            Flavor      = $matchTable[1]
            Channel     = $channel
            FileName    = $item.Name
            Path        = $item.FullName
            Source      = 'cache'
            Action      = 'SelectedCache'
            DownloadUrl = if ($persistedAsset) { $persistedAsset.DownloadUrl } else { $null }
            Sha256      = if ($persistedAsset) { $persistedAsset.Sha256 } else { $null }
            ShaSource   = if ($persistedAsset) { $persistedAsset.ShaSource } else { $null }
            ReleaseUrl  = $null
        }
    } -SortVersion {
        param($package)
        ConvertTo-VSCodeVersion -VersionText $package.Version
    })
}

function Get-LatestCachedVSCodeRuntimePackage {
    [CmdletBinding()]
    param(
        [string]$Flavor,
        [string]$LocalRoot = (Get-ManifestedLocalRoot)
    )

    return (Get-LatestManifestedArchiveRuntimePackage -CachedPackages @(Get-CachedVSCodeRuntimePackages -Flavor $Flavor -LocalRoot $LocalRoot))
}

function Save-VSCodeRuntimePackage {
    [CmdletBinding()]
    param(
        [switch]$RefreshVSCode,
        [string]$Flavor,
        [string]$LocalRoot = (Get-ManifestedLocalRoot)
    )

    if ([string]::IsNullOrWhiteSpace($Flavor)) {
        $Flavor = Get-VSCodeFlavor
    }

    $release = $null
    try {
        $release = Get-VSCodeRelease -Flavor $Flavor
    }
    catch {
        $release = $null
    }

    $layout = Get-ManifestedLayout -LocalRoot $LocalRoot
    return (Save-ManifestedArchiveRuntimePackage -Release $release -Refresh:$RefreshVSCode -CacheRootPath $layout.VsCodeCacheRoot -GetCachedPackage {
        Get-LatestCachedVSCodeRuntimePackage -Flavor $Flavor -LocalRoot $LocalRoot
    } -DownloadLabel 'VS Code' -RefreshWarningPrefix 'Could not refresh the VS Code package. Using cached copy. ' -OfflineErrorMessage 'Could not reach the VS Code update service and no cached VS Code ZIP was found.')
}