vx 0.4.1

Universal Development Tool Manager
Documentation
# vx installer script for Windows with multi-channel distribution support
#
# Basic usage:
#   powershell -c "irm https://raw.githubusercontent.com/loonghao/vx/main/install.ps1 | iex"
#
# With specific version:
#   $env:VX_VERSION="0.1.0"; powershell -c "irm https://raw.githubusercontent.com/loonghao/vx/main/install.ps1 | iex"
#
# With GitHub token (to avoid rate limits):
#   $env:GITHUB_TOKEN="your_token"; powershell -c "irm https://raw.githubusercontent.com/loonghao/vx/main/install.ps1 | iex"
#
# Build from source:
#   powershell -c "irm https://raw.githubusercontent.com/loonghao/vx/main/install.ps1 | iex" -BuildFromSource
#
# Alternative package managers:
#   winget install loonghao.vx
#   scoop install vx

param(
    [string]$Version = $env:VX_VERSION,
    [string]$InstallDir = $env:VX_INSTALL_DIR,
    [switch]$BuildFromSource = $false
)

# Default values
if (-not $Version) {
    $Version = "latest"
}

if (-not $InstallDir) {
    $InstallDir = "$env:USERPROFILE\.local\bin"
}

$ErrorActionPreference = "Stop"
# Enable progress bars for better user experience
$ProgressPreference = "Continue"

$RepoOwner = "loonghao"
$RepoName = "vx"
$BaseUrl = "https://github.com/$RepoOwner/$RepoName/releases"

# Logging functions
function Write-Info {
    param([string]$Message)
    Write-Host "[INFO] $Message" -ForegroundColor Blue
}

function Write-Warn {
    param([string]$Message)
    Write-Host "[WARN] $Message" -ForegroundColor Yellow
}

function Write-Error {
    param([string]$Message)
    Write-Host "[ERROR] $Message" -ForegroundColor Red
}

function Write-Success {
    param([string]$Message)
    Write-Host "[SUCCESS] $Message" -ForegroundColor Green
}

function Write-ProgressInfo {
    param(
        [string]$Activity,
        [string]$Status,
        [int]$PercentComplete = -1
    )
    if ($PercentComplete -ge 0) {
        Microsoft.PowerShell.Utility\Write-Progress -Activity $Activity -Status $Status -PercentComplete $PercentComplete
    }
    else {
        Microsoft.PowerShell.Utility\Write-Progress -Activity $Activity -Status $Status
    }
}

# Detect platform and map to release naming convention
function Get-Platform {
    # Detect architecture more accurately
    $arch = switch ([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture) {
        "X64" { "x86_64" }
        "Arm64" { "aarch64" }
        "X86" { "x86" }
        default {
            # Fallback to environment check
            if ([Environment]::Is64BitOperatingSystem) { "x86_64" } else { "x86" }
        }
    }
    return "Windows-msvc-$arch"
}

# Get latest version from GitHub API with optional authentication and fallback
function Get-LatestVersion {
    try {
        Write-ProgressInfo -Activity "Fetching latest version" -Status "Connecting to GitHub API..."
        $apiUrl = "https://api.github.com/repos/$RepoOwner/$RepoName/releases/latest"

        # Prepare headers for authentication if token is available
        $headers = @{}
        $githubToken = $env:GITHUB_TOKEN
        if ($githubToken) {
            $headers["Authorization"] = "Bearer $githubToken"
            Write-Info "Using authenticated GitHub API request"
        }
        else {
            Write-Info "Using unauthenticated GitHub API request (rate limited)"
        }

        # Make API request with optional authentication
        $response = Invoke-RestMethod -Uri $apiUrl -Method Get -Headers $headers -TimeoutSec 10
        Microsoft.PowerShell.Utility\Write-Progress -Activity "Fetching latest version" -Completed
        return $response.tag_name -replace '^v', ''
    }
    catch {
        Microsoft.PowerShell.Utility\Write-Progress -Activity "Fetching latest version" -Completed

        # Check if this is a rate limit error
        $isRateLimit = $_.Exception.Message -like "*rate limit*" -or
        $_.Exception.Message -like "*429*" -or
        $_.Exception.Message -like "*API rate limit exceeded*"

        if ($isRateLimit) {
            Write-Warn "GitHub API rate limit exceeded. Trying alternative methods..."

            # Try jsDelivr API as fallback
            try {
                Write-Info "Attempting to get version from jsDelivr API..."
                $jsdelivrUrl = "https://data.jsdelivr.com/v1/package/gh/$RepoOwner/$RepoName"
                $jsdelivrResponse = Invoke-RestMethod -Uri $jsdelivrUrl -Method Get -TimeoutSec 10
                if ($jsdelivrResponse.versions -and $jsdelivrResponse.versions.Count -gt 0) {
                    $latestVersion = $jsdelivrResponse.versions[0] -replace '^v', ''
                    Write-Success "Got version from jsDelivr: $latestVersion"
                    return $latestVersion
                }
            }
            catch {
                Write-Warn "jsDelivr API also failed: $_"
            }

            # Provide helpful error message with solutions
            Write-Error "Unable to determine latest version automatically due to rate limiting."
            Write-Host ""
            Write-Host "🔧 Solutions:" -ForegroundColor Yellow
            Write-Host "1. Set GITHUB_TOKEN environment variable:" -ForegroundColor Gray
            Write-Host "   `$env:GITHUB_TOKEN='your_token_here'; .\install.ps1" -ForegroundColor Gray
            Write-Host ""
            Write-Host "2. Specify version explicitly:" -ForegroundColor Gray
            Write-Host "   `$env:VX_VERSION='0.1.0'; .\install.ps1" -ForegroundColor Gray
            Write-Host ""
            Write-Host "3. Use package managers:" -ForegroundColor Gray
            Write-Host "   winget install loonghao.vx" -ForegroundColor Gray
            Write-Host "   scoop install vx" -ForegroundColor Gray
            Write-Host ""
            Write-Host "4. Build from source:" -ForegroundColor Gray
            Write-Host "   .\install.ps1 -BuildFromSource" -ForegroundColor Gray
            Write-Host ""
            exit 1
        }

        Write-Error "Failed to get latest version: $_"
        exit 1
    }
}

# Build from source (fallback method)
function Build-FromSource {
    Write-Info "Building vx from source..."

    # Check if Rust is installed
    if (!(Get-Command cargo -ErrorAction SilentlyContinue)) {
        Write-Error "Rust is not installed. Please install Rust first: https://rustup.rs/"
        exit 1
    }

    # Check if we're in the vx repository
    if (!(Test-Path "Cargo.toml")) {
        Write-Error "Not in vx repository. Please clone the repository first:"
        Write-Host "  git clone https://github.com/$RepoOwner/$RepoName.git"
        Write-Host "  cd $RepoName"
        Write-Host "  .\install.ps1 -BuildFromSource"
        exit 1
    }

    # Build the project
    Write-Info "Building vx..."
    Microsoft.PowerShell.Utility\Write-Progress -Activity "Building vx from source" -Status "Compiling Rust code..."
    cargo build --release

    if ($LASTEXITCODE -ne 0) {
        Microsoft.PowerShell.Utility\Write-Progress -Activity "Building vx from source" -Completed
        Write-Error "Build failed!"
        exit 1
    }
    Microsoft.PowerShell.Utility\Write-Progress -Activity "Building vx from source" -Status "Build completed" -PercentComplete 100

    # Create installation directory
    Microsoft.PowerShell.Utility\Write-Progress -Activity "Building vx from source" -Status "Creating installation directory..."
    if (!(Test-Path $InstallDir)) {
        New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
    }

    # Copy the binary
    Microsoft.PowerShell.Utility\Write-Progress -Activity "Building vx from source" -Status "Installing binary..."
    Copy-Item "target\release\vx.exe" "$InstallDir\vx.exe" -Force
    Microsoft.PowerShell.Utility\Write-Progress -Activity "Building vx from source" -Completed
    Write-Success "vx built and installed from source to: $InstallDir"
}

# Download from multiple channels with fallback
function Download-WithFallback {
    param(
        [string]$Version,
        [string]$Platform,
        [string]$ArchiveName,
        [string]$TempDir
    )

    # Define download channels in order of preference
    $channels = @(
        @{
            Name = "GitHub Releases"
            Url  = "$BaseUrl/download/v$Version/$ArchiveName"
        },
        @{
            Name = "jsDelivr CDN"
            Url  = "https://cdn.jsdelivr.net/gh/$RepoOwner/$RepoName@v$Version/$ArchiveName"
        },
        @{
            Name = "Fastly CDN"
            Url  = "https://fastly.jsdelivr.net/gh/$RepoOwner/$RepoName@v$Version/$ArchiveName"
        }
    )

    $archivePath = Join-Path $TempDir $ArchiveName

    foreach ($channel in $channels) {
        try {
            Write-Info "Trying $($channel.Name): $($channel.Url)"
            Microsoft.PowerShell.Utility\Write-Progress -Activity "Installing vx" -Status "Downloading from $($channel.Name)..." -PercentComplete 30

            Invoke-WebRequest -Uri $channel.Url -OutFile $archivePath -UseBasicParsing -TimeoutSec 30

            # Verify download
            if (Test-Path $archivePath) {
                $fileSize = (Get-Item $archivePath).Length
                if ($fileSize -gt 1024) {
                    # At least 1KB
                    Write-Success "Successfully downloaded from $($channel.Name) ($([math]::Round($fileSize/1MB, 2)) MB)"
                    return $archivePath
                }
                else {
                    Write-Warn "Downloaded file too small, trying next channel..."
                    Remove-Item $archivePath -Force -ErrorAction SilentlyContinue
                }
            }
        }
        catch {
            Write-Warn "Failed to download from $($channel.Name): $_"
            Remove-Item $archivePath -Force -ErrorAction SilentlyContinue
        }
    }

    throw "Failed to download from all channels"
}

# Download and install vx from releases with multiple channel support
function Install-FromRelease {
    $platform = Get-Platform

    if ($Version -eq "latest") {
        Write-Info "Fetching latest version..."
        Microsoft.PowerShell.Utility\Write-Progress -Activity "Installing vx" -Status "Fetching latest version..." -PercentComplete 10
        $Version = Get-LatestVersion
        if (-not $Version) {
            Microsoft.PowerShell.Utility\Write-Progress -Activity "Installing vx" -Completed
            Write-Error "Failed to get latest version"
            exit 1
        }
    }

    Write-Info "Installing vx v$Version for $platform..."

    # Construct archive name based on actual release asset naming
    # Format: vx-{OS}-{variant}-{arch}.zip
    $archiveName = "vx-$platform.zip"

    # Create temporary directory
    Microsoft.PowerShell.Utility\Write-Progress -Activity "Installing vx" -Status "Preparing download..." -PercentComplete 20
    $tempDir = New-TemporaryFile | ForEach-Object { Remove-Item $_; New-Item -ItemType Directory -Path $_ }

    try {
        # Download with fallback channels
        $archivePath = Download-WithFallback -Version $Version -Platform $platform -ArchiveName $archiveName -TempDir $tempDir

        # Extract
        Write-Info "Extracting to $InstallDir..."
        Microsoft.PowerShell.Utility\Write-Progress -Activity "Installing vx" -Status "Extracting archive..." -PercentComplete 60
        if (-not (Test-Path $InstallDir)) {
            New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
        }

        Expand-Archive -Path $archivePath -DestinationPath $tempDir -Force

        # Find and copy the binary
        Microsoft.PowerShell.Utility\Write-Progress -Activity "Installing vx" -Status "Installing binary..." -PercentComplete 80
        $binaryPath = Get-ChildItem -Path $tempDir -Name "vx.exe" -Recurse | Select-Object -First 1
        if (-not $binaryPath) {
            Microsoft.PowerShell.Utility\Write-Progress -Activity "Installing vx" -Completed
            Write-Error "vx.exe not found in archive"
            exit 1
        }

        $sourcePath = Join-Path $tempDir $binaryPath
        $destPath = Join-Path $InstallDir "vx.exe"
        Copy-Item -Path $sourcePath -Destination $destPath -Force

        Microsoft.PowerShell.Utility\Write-Progress -Activity "Installing vx" -Status "Installation completed" -PercentComplete 100
        Write-Success "vx v$Version installed to $destPath"
    }
    catch {
        Microsoft.PowerShell.Utility\Write-Progress -Activity "Installing vx" -Completed
        Write-Warn "Failed to download pre-built binary: $_"
        Write-Info "Falling back to building from source..."
        Build-FromSource
        return
    }
    finally {
        # Cleanup
        Microsoft.PowerShell.Utility\Write-Progress -Activity "Installing vx" -Completed
        Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
    }
}

# Update PATH environment variable
function Update-Path {
    param([string]$InstallPath)

    Microsoft.PowerShell.Utility\Write-Progress -Activity "Finalizing installation" -Status "Checking PATH environment..." -PercentComplete 90

    # Check if install directory is in PATH
    $currentPath = [Environment]::GetEnvironmentVariable("PATH", "User")
    if ($currentPath -notlike "*$InstallPath*") {
        Write-Warn "Add $InstallPath to your PATH to use vx from anywhere:"
        Write-Host "  Run this command in an elevated PowerShell:"
        Write-Host "  [Environment]::SetEnvironmentVariable('PATH', `$env:PATH + ';$InstallPath', 'User')"
        Write-Host ""
        Write-Host "Or add it manually through System Properties > Environment Variables"
    }

    Microsoft.PowerShell.Utility\Write-Progress -Activity "Finalizing installation" -Completed
}

# Verify installation
function Test-Installation {
    param([string]$BinaryPath)

    try {
        & $BinaryPath --version | Out-Null
        Write-Success "Installation verified successfully!"
        Write-Host ""
        Write-Host "🎉 vx is ready to use!"
        Write-Host "📖 Try these commands:"
        Write-Host "   vx --help" -ForegroundColor Gray
        Write-Host "   vx list" -ForegroundColor Gray
        Write-Host "   vx npm --version" -ForegroundColor Gray
        Write-Host "   vx uv --version" -ForegroundColor Gray
    }
    catch {
        Write-Error "Installation verification failed: $_"
        exit 1
    }
}

# Main execution function
function Main {
    Write-Info "vx installer"
    Write-Host ""

    # Check PowerShell version
    if ($PSVersionTable.PSVersion.Major -lt 5) {
        Write-Error "PowerShell 5.0 or later is required"
        exit 1
    }

    # Install vx
    if ($BuildFromSource) {
        Build-FromSource
    }
    else {
        Install-FromRelease
    }

    # Update PATH and verify installation
    $binaryPath = Join-Path $InstallDir "vx.exe"
    Update-Path -InstallPath $InstallDir
    Test-Installation -BinaryPath $binaryPath
}

# Run main function
Main