# AdaL CLI Installer for Windows (PowerShell) # Usage: # irm https://adal.sylph.ai/install/windows | iex # latest stable # $v="beta"; irm https://adal.sylph.ai/install/windows | iex # latest beta # $v="1.0.2-beta.1"; irm https://adal.sylph.ai/install/windows | iex # specific version # # Installs to ~/.adal/versions// with adal.cmd in ~/.adal/bin/ # Support both: direct script execution (param) and irm | iex ($v variable) param( [string]$Version = "", [switch]$NoModifyPath ) # When piped via irm | iex, param() is ignored — pick up $v from caller scope # Use Test-Path variable: to avoid throwing if caller has Set-StrictMode active if (-not $Version -and (Test-Path variable:v)) { $Version = $v -replace '^v', '' } if (-not $NoModifyPath -and (Test-Path variable:NoPath) -and $NoPath) { $NoModifyPath = $true } Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" $ProgressPreference = 'SilentlyContinue' # ─── Constants ──────────────────────────────────────────────────────────────── $RELEASES_URL = "https://d35qg8ac0yw4p7.cloudfront.net/cli" $TRACK_URL = if ($env:ADAL_TRACK_URL) { $env:ADAL_TRACK_URL } else { "https://adal.sylph.ai/api/installs/track" } $INSTALL_BASE = "$env:USERPROFILE\.adal" $VERSIONS_DIR = "$INSTALL_BASE\versions" $BIN_DIR = "$INSTALL_BASE\bin" # ─── Platform Detection ────────────────────────────────────────────────────── if (-not [Environment]::Is64BitProcess) { Write-Error "AdaL CLI does not support 32-bit Windows." exit 1 } if ($env:PROCESSOR_ARCHITECTURE -eq "ARM64") { Write-Error "AdaL CLI does not yet support Windows ARM64." exit 1 } $Platform = "win32-x64" # ─── Version Resolution ────────────────────────────────────────────────────── function Resolve-Version { if ($Version) { $script:Version = $Version -replace '^v', '' # Support channel names (e.g., "beta") — resolve to actual version number if ($Version -match '^[a-z]+$') { try { $script:Version = (Invoke-RestMethod -Uri "$RELEASES_URL/$Version" -ErrorAction Stop).Trim() } catch { Write-Error "Channel '$Version' not found." exit 1 } } else { try { $null = Invoke-RestMethod -Uri "$RELEASES_URL/manifests/manifest-$Version.json" -ErrorAction Stop } catch { Write-Error "Version $Version not found." exit 1 } } } else { try { $script:Version = (Invoke-RestMethod -Uri "$RELEASES_URL/latest" -ErrorAction Stop).Trim() } catch { Write-Error "Failed to determine latest version." exit 1 } } } # ─── Check Existing ────────────────────────────────────────────────────────── function Test-ExistingInstall { $versionDir = "$VERSIONS_DIR\$Version" if (Test-Path $versionDir) { Write-Host "Version $Version is already installed." -ForegroundColor DarkGray Setup-Symlinks Cleanup-OldVersions Setup-Path Setup-GitHubActions Print-Success exit 0 } } # ─── Install ────────────────────────────────────────────────────────────────── function Install-Version { Write-Host "" Write-Host "Installing adal v${Version}..." -ForegroundColor DarkGray $filename = "adal-${Version}-${Platform}.zip" $downloadUrl = "$RELEASES_URL/$Version/$filename" $manifestUrl = "$RELEASES_URL/manifests/manifest-${Version}.json" # Fetch manifest for checksum $expectedChecksum = "" try { $manifest = Invoke-RestMethod -Uri $manifestUrl -ErrorAction Stop $platformInfo = $manifest.platforms.$Platform if ($platformInfo) { $expectedChecksum = $platformInfo.checksum } } catch {} # Download zip $tmpDir = New-Item -ItemType Directory -Force -Path "$env:TEMP\adal-install-$(Get-Random)" $zipPath = "$tmpDir\$filename" try { Invoke-WebRequest -Uri $downloadUrl -OutFile $zipPath -ErrorAction Stop } catch { Write-Error "Download failed: $_" Remove-Item -Recurse -Force $tmpDir -ErrorAction SilentlyContinue exit 1 } # Verify checksum if ($expectedChecksum) { $actualChecksum = (Get-FileHash -Path $zipPath -Algorithm SHA256).Hash.ToLower() if ($actualChecksum -ne $expectedChecksum) { Write-Error "Checksum verification failed." Remove-Item -Recurse -Force $tmpDir -ErrorAction SilentlyContinue exit 1 } } # Extract $extractDir = "$tmpDir\extracted" Expand-Archive -Path $zipPath -DestinationPath $extractDir -Force # Move to versions directory New-Item -ItemType Directory -Force -Path $VERSIONS_DIR | Out-Null $versionDir = "$VERSIONS_DIR\$Version" # Find the extracted adal-{version} directory $extractedSubDir = Get-ChildItem -Path $extractDir -Directory | Select-Object -First 1 if ($extractedSubDir) { if (Test-Path $versionDir) { Remove-Item -Recurse -Force $versionDir } Move-Item -Path $extractedSubDir.FullName -Destination $versionDir -Force } else { if (Test-Path $versionDir) { Remove-Item -Recurse -Force $versionDir } Move-Item -Path $extractDir -Destination $versionDir -Force } # Cleanup Remove-Item -Recurse -Force $tmpDir -ErrorAction SilentlyContinue # Track after successful extraction — guarantees the row represents a # finished install/upgrade, not a failed/aborted attempt. Track-Install } # ─── Symlinks ───────────────────────────────────────────────────────────────── function Setup-Symlinks { New-Item -ItemType Directory -Force -Path $BIN_DIR | Out-Null # Update 'current' junction $currentLink = "$VERSIONS_DIR\current" if (Test-Path $currentLink) { cmd /c "rmdir `"$currentLink`"" 2>$null Remove-Item -Force $currentLink -ErrorAction SilentlyContinue } cmd /c "mklink /J `"$currentLink`" `"$VERSIONS_DIR\$Version`"" 2>$null | Out-Null # Create adal.cmd wrapper in bin directory $wrapperPath = "$BIN_DIR\adal.cmd" $wrapperContent = @" @echo off setlocal set "SCRIPT_DIR=%~dp0" "%SCRIPT_DIR%..\versions\current\runtime\bun.exe" "%SCRIPT_DIR%..\versions\current\adal-cli.js" %* "@ Set-Content -Path $wrapperPath -Value $wrapperContent -Encoding ASCII } # ─── PATH Setup ─────────────────────────────────────────────────────────────── function Setup-Path { if ($NoModifyPath) { return } $userPath = [Environment]::GetEnvironmentVariable("Path", "User") if ($userPath -notlike "*$BIN_DIR*") { [Environment]::SetEnvironmentVariable("Path", "$BIN_DIR;$userPath", "User") $env:Path = "$BIN_DIR;$env:Path" } } # ─── Cleanup Old Versions ──────────────────────────────────────────────────── function Cleanup-OldVersions { $keep = 1 if (-not (Test-Path $VERSIONS_DIR)) { return } $versions = @(Get-ChildItem -Path $VERSIONS_DIR -Directory | Where-Object { $_.Name -ne "current" -and $_.Name -match '^\d+\.\d+\.\d+' } | Sort-Object { [Version]($_.Name -replace '-.*$', '') } -Descending) if ($versions.Count -gt $keep) { $toRemove = $versions | Select-Object -Skip $keep foreach ($dir in $toRemove) { Remove-Item -Recurse -Force $dir.FullName -ErrorAction SilentlyContinue } } } # ─── GitHub Actions ─────────────────────────────────────────────────────────── function Setup-GitHubActions { if ($env:GITHUB_ACTIONS -eq "true" -and $env:GITHUB_PATH) { Add-Content -Path $env:GITHUB_PATH -Value $BIN_DIR } } function Print-Success { Write-Host "" Write-Host "AdaL CLI v${Version} installed!" -ForegroundColor Green Write-Host "" Write-Host "Location: $BIN_DIR\adal.cmd" -ForegroundColor DarkGray Write-Host "" Write-Host "Next: Run " -ForegroundColor DarkGray -NoNewline Write-Host "adal" -NoNewline Write-Host " to get started" -ForegroundColor DarkGray Write-Host "" } # ─── Anonymous install tracking ────────────────────────────────────────────── # Fire-and-forget POST so we can count actual installs/upgrades. # Only called from Install-Version, so same-version no-op reinstalls don't # fire (Test-ExistingInstall exits earlier on that path). # Honours $env:ADAL_NO_TRACK = '1' for opt-out. function Track-Install { if ($env:ADAL_NO_TRACK -eq '1') { return } if (-not $TRACK_URL) { return } # Two channels: "stable" = clean semver (X.Y.Z exactly), "beta" = anything else. # Any version with a `-suffix` (-beta, -alpha, -rc, -preview, -dev, etc.) is "beta" # so dev/internal builds can't masquerade as stable in the dashboard. $channel = if ($Version -match '^\d+\.\d+\.\d+$') { "stable" } else { "beta" } # install vs upgrade — does any prior version dir exist? $eventType = "install" if (Test-Path $VERSIONS_DIR) { $existing = Get-ChildItem $VERSIONS_DIR -Directory -ErrorAction SilentlyContinue | ` Where-Object { $_.Name -ne 'current' -and $_.Name -ne $Version } if ($existing) { $eventType = "upgrade" } } $body = @{ platform = "cli" channel = $channel event_type = $eventType version = $Version } | ConvertTo-Json -Compress # Background job; suppress all errors. -TimeoutSec keeps the script from hanging. Start-Job -ScriptBlock { param($url, $body) try { Invoke-RestMethod -Uri $url -Method Post -Body $body ` -ContentType 'application/json' -TimeoutSec 2 | Out-Null } catch { } } -ArgumentList $TRACK_URL, $body | Out-Null } # ─── Main ───────────────────────────────────────────────────────────────────── Resolve-Version Test-ExistingInstall Install-Version Setup-Symlinks Cleanup-OldVersions Setup-Path Setup-GitHubActions Print-Success