waxpkg 0.20.2

Fast Homebrew-compatible package manager
# Wax Windows installer
# One-liner: irm https://raw.githubusercontent.com/plyght/wax/master/install.ps1 | iex
# From clone: .\install.ps1
# Force release binary in clone: $env:WAX_USE_RELEASE = '1'; .\install.ps1
#
# Note: #Requires cannot be used here - it breaks Invoke-Expression (iex) pipelines.

$ErrorActionPreference = 'Stop'

# GitHub requires TLS 1.2; Windows PowerShell 5.1 defaults to older protocols.
try {
    $tls = [Net.ServicePointManager]::SecurityProtocol
    if ($tls -band [Net.SecurityProtocolType]::Tls12 -eq 0) {
        [Net.ServicePointManager]::SecurityProtocol = $tls -bor [Net.SecurityProtocolType]::Tls12
    }
} catch {
    # Non-fatal on hosts where ServicePointManager is unavailable.
}

if ($PSVersionTable.PSVersion.Major -lt 5) {
    throw 'PowerShell 5.1 or later is required.'
}
if ($PSVersionTable.PSVersion.Major -eq 5 -and $PSVersionTable.PSVersion.Minor -lt 1) {
    throw 'PowerShell 5.1 or later is required.'
}

$Repo = 'plyght/wax'
if (-not $env:USERPROFILE) {
    throw 'USERPROFILE is not set; cannot determine install directory.'
}
$installDir = if ($env:WAX_INSTALL_DIR) {
    $env:WAX_INSTALL_DIR
} else {
    Join-Path $env:USERPROFILE '.local\bin'
}

$script:WebHeaders = @{ 'User-Agent' = 'wax-install-ps1' }

function Get-WebText {
    param([string]$Uri)
    # OutFile matches the release binary download path; .Content is unreliable for
    # application/octet-stream responses under Windows PowerShell 5.1.
    $tmp = Join-Path ([System.IO.Path]::GetTempPath()) ('wax-dl-' + [System.IO.Path]::GetRandomFileName())
    try {
        Invoke-WebRequest -Uri $Uri -OutFile $tmp -UseBasicParsing -Headers $script:WebHeaders
        return Get-Content -LiteralPath $tmp -Raw -Encoding UTF8
    } finally {
        if (Test-Path -LiteralPath $tmp) {
            Remove-Item -LiteralPath $tmp -Force -ErrorAction SilentlyContinue
        }
    }
}

function Read-ExpectedSha256 {
    param([string]$Uri)
    $raw = (Get-WebText -Uri $Uri).Trim()
    if ($raw.Length -ge 1 -and [int][char]$raw[0] -eq 0xFEFF) {
        $raw = $raw.Substring(1).Trim()
    }
    $hash = ($raw -split '\s+' | Where-Object { $_ } | Select-Object -First 1)
    if (-not $hash -or $hash -notmatch '^[0-9a-fA-F]{64}$') {
        throw ('Invalid checksum file at {0} (expected 64 hex chars, got: {1})' -f $Uri, $raw)
    }
    return $hash.ToLowerInvariant()
}

function Install-FromRepo {
    param([string]$Root)
    if (-not $Root) {
        throw 'Install-FromRepo: root path is empty.'
    }
    if (-not (Get-Command cargo -ErrorAction SilentlyContinue)) {
        throw 'cargo not in PATH - install Rust from https://rustup.rs/ or set WAX_USE_RELEASE=1 to download a release binary.'
    }
    Write-Host ('Building wax from local checkout ({0})...' -f $Root)
    Push-Location $Root
    try {
        cargo build --release
    } finally {
        Pop-Location
    }
    $built = Join-Path $Root 'target\release\wax.exe'
    if (-not (Test-Path -LiteralPath $built)) {
        throw ('Build finished but {0} not found.' -f $built)
    }
    New-Item -ItemType Directory -Force -Path $installDir | Out-Null
    $dest = Join-Path $installDir 'wax.exe'
    Copy-Item -LiteralPath $built -Destination $dest -Force
    Write-Host ('Installed to {0}' -f $dest)
    Ensure-WaxPath
}

function Show-WaxPathInstructions {
    $dir = $installDir.TrimEnd('\')
    Write-Host ''
    Write-Host 'wax.exe is installed but not on your PATH. Add this folder:'
    Write-Host ('  {0}' -f $dir)
    Write-Host ''
    Write-Host 'Windows 11 / 10 (Settings):'
    Write-Host '  1. Press Win, search "environment variables"'
    Write-Host '  2. Open "Edit environment variables for your account"'
    Write-Host '  3. Select Path -> Edit -> New'
    Write-Host ('  4. Paste: {0}' -f $dir)
    Write-Host '  5. OK, then open a new PowerShell window'
    Write-Host ''
    Write-Host 'PowerShell (permanent, current user):'
    Write-Host ('  [Environment]::SetEnvironmentVariable(''Path'', [Environment]::GetEnvironmentVariable(''Path'',''User'') + '';' + $dir + ''', ''User'')')
    Write-Host ''
    Write-Host 'PowerShell (this session only):'
    Write-Host ('  $env:PATH += '';' + $dir)
    Write-Host ''
    Write-Host 'Command Prompt (permanent, current user):'
    Write-Host ('  setx PATH "%PATH%;{0}"' -f $dir)
}

function Ensure-WaxPath {
    $dir = $installDir.TrimEnd('\')
    $pathEntries = @($env:PATH -split ';' | ForEach-Object { $_.TrimEnd('\') })
    if ($dir -in $pathEntries) {
        return
    }

    $added = $false
    try {
        $userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
        if (-not $userPath) {
            $userPath = ''
        }
        $userEntries = @($userPath -split ';' | Where-Object { $_ } | ForEach-Object { $_.TrimEnd('\') })
        if ($dir -notin $userEntries) {
            $newUserPath = if ($userPath -and -not $userPath.EndsWith(';')) {
                $userPath + ';' + $dir
            } elseif ($userPath) {
                $userPath + $dir
            } else {
                $dir
            }
            [Environment]::SetEnvironmentVariable('Path', $newUserPath, 'User')
            if ($env:PATH) {
                $env:PATH = $env:PATH.TrimEnd(';') + ';' + $dir
            } else {
                $env:PATH = $dir
            }
            Write-Host ('Added {0} to your user PATH.' -f $dir)
            $added = $true
        }
    } catch {
        Write-Warning ('Could not update user PATH automatically: {0}' -f $_.Exception.Message)
    }

    if ($added) {
        if (-not (Get-Command wax -ErrorAction SilentlyContinue)) {
            Write-Host 'Open a new terminal, or run this in the current session:'
            Write-Host ('  $env:PATH += '';' + $dir)
        }
    } else {
        Show-WaxPathInstructions
    }
}

function Get-WaxWindowsAsset {
    # PROCESSOR_ARCHITEW6432 is set for WOW64 (32-bit PowerShell on 64-bit Windows).
    $procArch = if ($env:PROCESSOR_ARCHITEW6432) {
        $env:PROCESSOR_ARCHITEW6432
    } else {
        $env:PROCESSOR_ARCHITECTURE
    }

    switch ($procArch) {
        'AMD64' { return 'wax-windows-x64.exe' }
        'ARM64' { return 'wax-windows-arm64.exe' }
    }

    $osArch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString()
    switch ($osArch) {
        'X64' { return 'wax-windows-x64.exe' }
        'Arm64' { return 'wax-windows-arm64.exe' }
    }

    $detail = ('PROCESSOR_ARCHITECTURE={0}; PROCESSOR_ARCHITEW6432={1}; OSArchitecture={2}' -f $env:PROCESSOR_ARCHITECTURE, $env:PROCESSOR_ARCHITEW6432, $osArch)
    throw ('Unsupported Windows CPU architecture for pre-built wax ({0}). Clone https://github.com/plyght/wax and run install.ps1 to build locally.' -f $detail)
}

function Install-FromRelease {
    if (-not [Environment]::Is64BitOperatingSystem) {
        Write-Error 'Wax pre-built Windows installers require 64-bit Windows.'
    }

    $asset = Get-WaxWindowsAsset

    $archLabel = if ($asset -match 'arm64') { 'windows/arm64' } else { 'windows/x64' }

    $version = $env:WAX_VERSION
    if (-not $version) {
        $releaseUri = ('https://api.github.com/repos/{0}/releases/latest' -f $Repo)
        $rel = Invoke-RestMethod -Uri $releaseUri -Headers $script:WebHeaders
        $version = $rel.tag_name
        $assetNames = @($rel.assets | ForEach-Object { $_.name })
        if ($assetNames -notcontains $asset) {
            $available = $assetNames -join ', '
            throw ('Latest release ({0}) has no Windows binary ({1}). Available: {2}. Clone https://github.com/plyght/wax and run install.ps1 locally, or set WAX_VERSION to a release with Windows assets.' -f $version, $asset, $available)
        }
    }
    if ($version -notmatch '^v') {
        $version = 'v' + $version
    }

    $base = ('https://github.com/{0}/releases/download/{1}' -f $Repo, $version)
    $tmp = Join-Path ([System.IO.Path]::GetTempPath()) ('wax-install-' + [System.IO.Path]::GetRandomFileName())

    try {
        Write-Host ('Installing wax {0} ({1}) from GitHub Releases...' -f $version, $archLabel)
        $exeUri = ('{0}/{1}' -f $base, $asset)
        try {
            Invoke-WebRequest -Uri $exeUri -OutFile $tmp -UseBasicParsing -Headers $script:WebHeaders
        } catch {
            $dlErr = $_.Exception.Message
            throw ('Failed to download {0} from {1}. Clone the repo and run install.ps1 locally, or set WAX_VERSION. Error: {2}' -f $asset, $exeUri, $dlErr)
        }

        $shaUri = ('{0}/{1}.sha256' -f $base, $asset)
        $expected = $null
        try {
            $expected = Read-ExpectedSha256 -Uri $shaUri
        } catch {
            if ($env:WAX_NO_VERIFY -eq '1') {
                Write-Warning ('WAX_NO_VERIFY=1 set - installing {0} without checksum verification' -f $version)
            } else {
                throw ('Checksum verification failed for {0}: {1}' -f $shaUri, $_.Exception.Message)
            }
        }

        if ($expected) {
            $hash = (Get-FileHash -LiteralPath $tmp -Algorithm SHA256).Hash.ToLowerInvariant()
            if ($hash -ne $expected) {
                throw ('SHA256 mismatch (expected {0}, got {1})' -f $expected, $hash)
            }
            Write-Host 'Checksum verified.'
        }

        New-Item -ItemType Directory -Force -Path $installDir | Out-Null
        $dest = Join-Path $installDir 'wax.exe'
        Move-Item -LiteralPath $tmp -Destination $dest -Force
        Write-Host ('Installed to {0}' -f $dest)

        Ensure-WaxPath
    } finally {
        if ($tmp -and (Test-Path -LiteralPath $tmp)) {
            Remove-Item -LiteralPath $tmp -Force -ErrorAction SilentlyContinue
        }
    }
}

# iex/irm has no $PSScriptRoot - never Join-Path or Test-Path on an empty root.
$repoRoot = $PSScriptRoot
$invokedAsFile = $PSCommandPath -and ((Split-Path -Leaf $PSCommandPath) -eq 'install.ps1')
$useLocalBuild = $false

if ($invokedAsFile -and $repoRoot -and ($env:WAX_USE_RELEASE -ne '1')) {
    $cargoTomlPath = Join-Path $repoRoot 'Cargo.toml'
    if (Test-Path -LiteralPath $cargoTomlPath) {
        $tomlRaw = Get-Content -LiteralPath $cargoTomlPath -Raw
        $q = [char]34
        $needle = [string]::Concat('name = ', $q, 'waxpkg', $q)
        if ($tomlRaw.IndexOf($needle, [System.StringComparison]::Ordinal) -ge 0) {
            $useLocalBuild = $true
        }
    }
}

if ($useLocalBuild) {
    Install-FromRepo -Root $repoRoot
} else {
    Install-FromRelease
}