shimexe 0.5.5

A modern, cross-platform executable shim manager with environment variable expansion and TOML configuration support
# shimexe installer script for Windows
# Usage: powershell -c "irm https://raw.githubusercontent.com/loonghao/shimexe/main/scripts/install.ps1 | iex"
# Usage with version: $env:SHIMEXE_VERSION="0.3.0"; powershell -c "irm https://raw.githubusercontent.com/loonghao/shimexe/main/scripts/install.ps1 | iex"

param(
    [string]$Version = $env:SHIMEXE_VERSION,
    [string]$InstallDir = $env:SHIMEXE_INSTALL_DIR
)

# Default values
if (-not $Version) { $Version = "latest" }
if (-not $InstallDir) { $InstallDir = "$env:USERPROFILE\.local\bin" }

$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"

$RepoOwner = "loonghao"
$RepoName = "shimexe"
$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
}

# Detect platform and map to release naming convention
function Get-Platform {
    $arch = if ([Environment]::Is64BitOperatingSystem) { "x86_64" } else { "i686" }
    return "$arch-pc-windows-msvc"
}

# Map platform to release file naming convention
function Get-ReleaseFileName {
    param([string]$Platform)

    # Map from Rust target triple to release file naming
    switch ($Platform) {
        "x86_64-pc-windows-msvc" { return "shimexe-Windows-msvc-x86_64.zip" }
        "i686-pc-windows-msvc" { return "shimexe-Windows-msvc-i686.zip" }
        "aarch64-pc-windows-msvc" { return "shimexe-Windows-msvc-arm64.zip" }
        default { return "shimexe-$Platform.zip" }
    }
}

# Get latest shimexe version from GitHub API with retry and fallback
function Get-LatestVersion {
    $maxRetries = 3
    $retryDelay = 2

    for ($i = 1; $i -le $maxRetries; $i++) {
        try {
            Write-Info "Attempting to get latest shimexe version (attempt $i/$maxRetries)..."
            # Use releases endpoint to get all releases and filter for shimexe-v*
            $apiUrl = "https://api.github.com/repos/$RepoOwner/$RepoName/releases"

            # Add headers to avoid rate limiting issues
            $headers = @{
                'User-Agent' = 'shimexe-installer/1.0'
                'Accept'     = 'application/vnd.github.v3+json'
            }

            $response = Invoke-RestMethod -Uri $apiUrl -Method Get -Headers $headers -TimeoutSec 10

            # Find all shimexe releases (both v* and shimexe-v* formats) and sort by version
            $shimexeReleases = @()
            foreach ($release in $response) {
                # Match both v0.3.3 and shimexe-v0.1.2 formats
                if ($release.tag_name -match "^(shimexe-)?v([0-9]+\.[0-9]+\.[0-9]+)$") {
                    $shimexeReleases += @{
                        Version       = [System.Version]$matches[2]
                        TagName       = $release.tag_name
                        VersionString = $matches[2]
                    }
                }
            }

            # Sort by version and get the latest
            if ($shimexeReleases.Count -gt 0) {
                $latest = $shimexeReleases | Sort-Object { $_.Version } -Descending | Select-Object -First 1
                $version = $latest.VersionString
                Write-Info "Found latest shimexe version: v$version"
                return $version
            }

            Write-Warn "No shimexe releases found in API response"
        }
        catch {
            $errorMessage = $_.Exception.Message
            Write-Warn "Attempt $i failed: $errorMessage"

            # Check if it's a rate limit error
            if ($errorMessage -like "*rate limit*" -or $errorMessage -like "*403*") {
                Write-Warn "GitHub API rate limit detected"
                if ($i -lt $maxRetries) {
                    Write-Info "Waiting $retryDelay seconds before retry..."
                    Start-Sleep -Seconds $retryDelay
                    $retryDelay *= 2  # Exponential backoff
                }
            }
            elseif ($i -lt $maxRetries) {
                Write-Info "Retrying in $retryDelay seconds..."
                Start-Sleep -Seconds $retryDelay
            }
        }
    }

    # Fallback: try to get version from releases page HTML
    Write-Warn "API failed, trying fallback method..."
    try {
        $releasesUrl = "https://github.com/$RepoOwner/$RepoName/releases"
        $response = Invoke-WebRequest -Uri $releasesUrl -UseBasicParsing -TimeoutSec 10

        # Extract version from page content (look for both v* and shimexe-v* patterns)
        if ($response.Content -match 'releases/tag/(shimexe-)?v([0-9]+\.[0-9]+\.[0-9]+)') {
            $version = $matches[2]
            Write-Info "Found shimexe version via fallback: v$version"
            return $version
        }
    }
    catch {
        Write-Warn "Fallback method also failed: $_"
    }

    Write-Error "Failed to get latest version after all attempts. Please specify a version manually using: `$env:SHIMEXE_VERSION='x.y.z'"
    Write-Host "Example: `$env:SHIMEXE_VERSION='0.3.1'; irm https://raw.githubusercontent.com/loonghao/shimexe/main/scripts/install.ps1 | iex"
    exit 1
}

# Download and install shimexe
function Install-Shimexe {
    $platform = Get-Platform
    
    if ($Version -eq "latest") {
        Write-Info "Fetching latest version..."
        $Version = Get-LatestVersion
        if (-not $Version) {
            Write-Error "Failed to get latest version"
            exit 1
        }
    }
    
    Write-Info "Installing shimexe v$Version for $platform..."

    # Construct download URL using correct release file naming
    $archiveName = Get-ReleaseFileName -Platform $platform
    $downloadUrl = "$BaseUrl/download/v$Version/$archiveName"
    
    # Create temporary directory
    $tempDir = New-TemporaryFile | ForEach-Object { Remove-Item $_; New-Item -ItemType Directory -Path $_ }
    
    try {
        # Download
        Write-Info "Downloading from $downloadUrl..."
        $archivePath = Join-Path $tempDir $archiveName
        Invoke-WebRequest -Uri $downloadUrl -OutFile $archivePath -UseBasicParsing
        
        # Extract
        Write-Info "Extracting to $InstallDir..."
        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
        $binaryPath = Get-ChildItem -Path $tempDir -Name "shimexe.exe" -Recurse | Select-Object -First 1
        
        if (-not $binaryPath) {
            Write-Error "shimexe.exe not found in archive"
            exit 1
        }
        
        $sourcePath = Join-Path $tempDir $binaryPath
        $destPath = Join-Path $InstallDir "shimexe.exe"
        Copy-Item -Path $sourcePath -Destination $destPath -Force
        
        Write-Success "shimexe v$Version installed to $destPath"
        
        # Check if install directory is in PATH
        $currentPath = [Environment]::GetEnvironmentVariable("PATH", "User")
        if ($currentPath -notlike "*$InstallDir*") {
            Write-Warn "Add $InstallDir to your PATH to use shimexe from anywhere:"
            Write-Host "  Run this command in an elevated PowerShell:"
            Write-Host "  [Environment]::SetEnvironmentVariable('PATH', `$env:PATH + ';$InstallDir', 'User')"
            Write-Host ""
            Write-Host "Or add it manually through System Properties > Environment Variables"
        }
        
        # Verify installation
        try {
            & $destPath --version | Out-Null
            Write-Success "Installation verified successfully!"
            Write-Host ""
            Write-Host "Get started:" -ForegroundColor Yellow
            Write-Host "  shimexe --help" -ForegroundColor Cyan
            Write-Host "  shimexe init --examples" -ForegroundColor Cyan
            Write-Host ""
            Write-Host "Add your first shim:" -ForegroundColor Yellow
            Write-Host "  # Option 1: Auto-configure PATH (recommended)" -ForegroundColor Gray
            Write-Host "  shimexe add my-tool --path /path/to/tool --add-system-path" -ForegroundColor Cyan
            Write-Host "  my-tool --version  # Use directly" -ForegroundColor Green
            Write-Host ""
            Write-Host "  # Option 2: Use shimexe run (no PATH setup)" -ForegroundColor Gray
            Write-Host "  shimexe add my-tool --path /path/to/tool" -ForegroundColor Cyan
            Write-Host "  shimexe run my-tool --version  # Use via shimexe" -ForegroundColor Green
            Write-Host ""
            Write-Host "Default shim directory: $env:USERPROFILE\.shimexe" -ForegroundColor Gray
        }
        catch {
            Write-Error "Installation verification failed: $_"
            exit 1
        }
    }
    finally {
        # Cleanup
        Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
    }
}

# Main execution
function Main {
    Write-Info "shimexe installer"
    Write-Host ""
    
    # Check PowerShell version
    if ($PSVersionTable.PSVersion.Major -lt 5) {
        Write-Error "PowerShell 5.0 or later is required"
        exit 1
    }
    
    Install-Shimexe
}

# Run main function
Main