diff --git a/.github/workflows/rust-release.yml b/.github/workflows/rust-release.yml index 2d137de6ff..5d6d3f7ca0 100644 --- a/.github/workflows/rust-release.yml +++ b/.github/workflows/rust-release.yml @@ -494,9 +494,10 @@ jobs: --package codex-responses-api-proxy \ --package codex-sdk - - name: Stage macOS and Linux installer script + - name: Stage installer scripts run: | cp scripts/install/install.sh dist/install.sh + cp scripts/install/install.ps1 dist/install.ps1 - name: Create GitHub Release uses: softprops/action-gh-release@v2 diff --git a/scripts/install/install.ps1 b/scripts/install/install.ps1 new file mode 100644 index 0000000000..40328db700 --- /dev/null +++ b/scripts/install/install.ps1 @@ -0,0 +1,196 @@ +param( + [Parameter(Position=0)] + [string]$Version = "latest" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" +$ProgressPreference = "SilentlyContinue" + +function Write-Step { + param( + [string]$Message + ) + + Write-Host "==> $Message" +} + +function Normalize-Version { + param( + [string]$RawVersion + ) + + if ([string]::IsNullOrWhiteSpace($RawVersion) -or $RawVersion -eq "latest") { + return "latest" + } + + if ($RawVersion.StartsWith("rust-v")) { + return $RawVersion.Substring(6) + } + + if ($RawVersion.StartsWith("v")) { + return $RawVersion.Substring(1) + } + + return $RawVersion +} + +function Get-ReleaseUrl { + param( + [string]$AssetName, + [string]$ResolvedVersion + ) + + return "https://github.com/openai/codex/releases/download/rust-v$ResolvedVersion/$AssetName" +} + +function Path-Contains { + param( + [string]$PathValue, + [string]$Entry + ) + + if ([string]::IsNullOrWhiteSpace($PathValue)) { + return $false + } + + $needle = $Entry.TrimEnd("\") + foreach ($segment in $PathValue.Split(";", [System.StringSplitOptions]::RemoveEmptyEntries)) { + if ($segment.TrimEnd("\") -ieq $needle) { + return $true + } + } + + return $false +} + +function Resolve-Version { + $normalizedVersion = Normalize-Version -RawVersion $Version + if ($normalizedVersion -ne "latest") { + return $normalizedVersion + } + + $release = Invoke-RestMethod -Uri "https://api.github.com/repos/openai/codex/releases/latest" + if (-not $release.tag_name) { + Write-Error "Failed to resolve the latest Codex release version." + exit 1 + } + + return (Normalize-Version -RawVersion $release.tag_name) +} + +if ($env:OS -ne "Windows_NT") { + Write-Error "install.ps1 supports Windows only. Use install.sh on macOS or Linux." + exit 1 +} + +if (-not [Environment]::Is64BitOperatingSystem) { + Write-Error "Codex requires a 64-bit version of Windows." + exit 1 +} + +$architecture = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture +$target = $null +$platformLabel = $null +$npmTag = $null +switch ($architecture) { + "Arm64" { + $target = "aarch64-pc-windows-msvc" + $platformLabel = "Windows (ARM64)" + $npmTag = "win32-arm64" + } + "X64" { + $target = "x86_64-pc-windows-msvc" + $platformLabel = "Windows (x64)" + $npmTag = "win32-x64" + } + default { + Write-Error "Unsupported architecture: $architecture" + exit 1 + } +} + +if ([string]::IsNullOrWhiteSpace($env:CODEX_INSTALL_DIR)) { + $installDir = Join-Path $env:LOCALAPPDATA "Programs\OpenAI\Codex\bin" +} else { + $installDir = $env:CODEX_INSTALL_DIR +} + +$codexPath = Join-Path $installDir "codex.exe" +$installMode = if (Test-Path $codexPath) { "Updating" } else { "Installing" } + +Write-Step "$installMode Codex CLI" +Write-Step "Detected platform: $platformLabel" + +New-Item -ItemType Directory -Force -Path $installDir | Out-Null + +$resolvedVersion = Resolve-Version +Write-Step "Resolved version: $resolvedVersion" +$packageAsset = "codex-npm-$npmTag-$resolvedVersion.tgz" + +$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) ("codex-install-" + [System.Guid]::NewGuid().ToString("N")) +New-Item -ItemType Directory -Force -Path $tempDir | Out-Null + +try { + $archivePath = Join-Path $tempDir $packageAsset + $extractDir = Join-Path $tempDir "extract" + $url = Get-ReleaseUrl -AssetName $packageAsset -ResolvedVersion $resolvedVersion + + Write-Step "Downloading Codex CLI" + Invoke-WebRequest -Uri $url -OutFile $archivePath + + New-Item -ItemType Directory -Force -Path $extractDir | Out-Null + tar -xzf $archivePath -C $extractDir + + $vendorRoot = Join-Path $extractDir "package/vendor/$target" + Write-Step "Installing to $installDir" + $copyMap = @{ + "codex/codex.exe" = "codex.exe" + "codex/codex-command-runner.exe" = "codex-command-runner.exe" + "codex/codex-windows-sandbox-setup.exe" = "codex-windows-sandbox-setup.exe" + "path/rg.exe" = "rg.exe" + } + + foreach ($relativeSource in $copyMap.Keys) { + $sourcePath = Join-Path $vendorRoot $relativeSource + $destinationPath = Join-Path $installDir $copyMap[$relativeSource] + Move-Item -Force $sourcePath $destinationPath + } +} finally { + Remove-Item -Recurse -Force $tempDir -ErrorAction SilentlyContinue +} + +$userPath = [Environment]::GetEnvironmentVariable("Path", "User") +$pathNeedsNewShell = $false +if (-not (Path-Contains -PathValue $userPath -Entry $installDir)) { + if ([string]::IsNullOrWhiteSpace($userPath)) { + $newUserPath = $installDir + } else { + $newUserPath = "$installDir;$userPath" + } + + [Environment]::SetEnvironmentVariable("Path", $newUserPath, "User") + if (-not (Path-Contains -PathValue $env:Path -Entry $installDir)) { + if ([string]::IsNullOrWhiteSpace($env:Path)) { + $env:Path = $installDir + } else { + $env:Path = "$installDir;$env:Path" + } + } + Write-Step "PATH updated for future PowerShell sessions." + $pathNeedsNewShell = $true +} elseif (Path-Contains -PathValue $env:Path -Entry $installDir) { + Write-Step "$installDir is already on PATH." +} else { + Write-Step "PATH is already configured for future PowerShell sessions." + $pathNeedsNewShell = $true +} + +if ($pathNeedsNewShell) { + Write-Step ('Run now: $env:Path = "{0};$env:Path"; codex' -f $installDir) + Write-Step "Or open a new PowerShell window and run: codex" +} else { + Write-Step "Run: codex" +} + +Write-Host "Codex CLI $resolvedVersion installed successfully."