mirror of
https://github.com/Alishahryar1/free-claude-code.git
synced 2026-06-01 22:09:04 +02:00
fix: update uv with install manager
This commit is contained in:
+183
-1
@@ -14,6 +14,7 @@ $ErrorActionPreference = "Stop"
|
||||
|
||||
$RepoGitUrl = "git+https://github.com/Alishahryar1/free-claude-code.git"
|
||||
$PythonVersion = "3.14.0"
|
||||
$MinUvVersion = "0.11.0"
|
||||
$UvInstallUrl = "https://astral.sh/uv/install.ps1"
|
||||
|
||||
function Show-Usage {
|
||||
@@ -103,6 +104,184 @@ function Assert-CommandAvailable {
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-ProbeCommand {
|
||||
param(
|
||||
[string] $FilePath,
|
||||
[string[]] $Arguments = @()
|
||||
)
|
||||
|
||||
$previousErrorActionPreference = $ErrorActionPreference
|
||||
$ErrorActionPreference = "Continue"
|
||||
|
||||
try {
|
||||
$output = & $FilePath @Arguments 2>$null
|
||||
return [pscustomobject] @{
|
||||
ExitCode = $LASTEXITCODE
|
||||
Output = ($output | Out-String)
|
||||
}
|
||||
}
|
||||
catch {
|
||||
return [pscustomobject] @{
|
||||
ExitCode = 1
|
||||
Output = ""
|
||||
}
|
||||
}
|
||||
finally {
|
||||
$ErrorActionPreference = $previousErrorActionPreference
|
||||
}
|
||||
}
|
||||
|
||||
function Get-InstalledUvVersion {
|
||||
$version = ""
|
||||
|
||||
$selfVersionProbe = Invoke-ProbeCommand -FilePath "uv" -Arguments @("self", "version", "--short")
|
||||
if ($selfVersionProbe.ExitCode -eq 0) {
|
||||
$version = $selfVersionProbe.Output.Trim()
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($version)) {
|
||||
$versionProbe = Invoke-ProbeCommand -FilePath "uv" -Arguments @("--version")
|
||||
if (($versionProbe.ExitCode -eq 0) -and ($versionProbe.Output -match '^uv\s+([^\s]+)')) {
|
||||
$version = $Matches[1]
|
||||
}
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($version)) {
|
||||
throw "Unable to determine uv version."
|
||||
}
|
||||
|
||||
return $version
|
||||
}
|
||||
|
||||
function Test-UvVersionAtLeast {
|
||||
param(
|
||||
[string] $Version,
|
||||
[string] $Minimum
|
||||
)
|
||||
|
||||
$normalizedVersion = $Version -replace '[-+].*$', ''
|
||||
$normalizedMinimum = $Minimum -replace '[-+].*$', ''
|
||||
return ([version] $normalizedVersion) -ge ([version] $normalizedMinimum)
|
||||
}
|
||||
|
||||
function Test-UvVersionSatisfiesMinimum {
|
||||
$version = Get-InstalledUvVersion
|
||||
return Test-UvVersionAtLeast -Version $version -Minimum $MinUvVersion
|
||||
}
|
||||
|
||||
function Assert-MinUvVersion {
|
||||
if ($DryRun) {
|
||||
return
|
||||
}
|
||||
|
||||
$version = Get-InstalledUvVersion
|
||||
if (-not (Test-UvVersionAtLeast -Version $version -Minimum $MinUvVersion)) {
|
||||
throw "uv $MinUvVersion or newer is required; found uv $version. Upgrade uv with its installer or package manager, then rerun this installer."
|
||||
}
|
||||
}
|
||||
|
||||
function Test-UvSelfUpdateSupported {
|
||||
$probe = Invoke-ProbeCommand -FilePath "uv" -Arguments @("self", "update", "--dry-run")
|
||||
return $probe.ExitCode -eq 0
|
||||
}
|
||||
|
||||
function Test-UvInstalledByScoop {
|
||||
if (-not (Get-Command scoop -ErrorAction SilentlyContinue)) {
|
||||
return $false
|
||||
}
|
||||
|
||||
$probe = Invoke-ProbeCommand -FilePath "scoop" -Arguments @("list", "uv")
|
||||
return ($probe.ExitCode -eq 0) -and ($probe.Output -match '(^|\s)uv(\s|$)')
|
||||
}
|
||||
|
||||
function Test-UvInstalledByWinget {
|
||||
if (-not (Get-Command winget -ErrorAction SilentlyContinue)) {
|
||||
return $false
|
||||
}
|
||||
|
||||
$probe = Invoke-ProbeCommand -FilePath "winget" -Arguments @("list", "--id", "astral-sh.uv", "-e")
|
||||
return ($probe.ExitCode -eq 0) -and ($probe.Output -match 'astral-sh\.uv')
|
||||
}
|
||||
|
||||
function Test-UvInstalledByPipx {
|
||||
if (-not (Get-Command pipx -ErrorAction SilentlyContinue)) {
|
||||
return $false
|
||||
}
|
||||
|
||||
$probe = Invoke-ProbeCommand -FilePath "pipx" -Arguments @("list")
|
||||
return ($probe.ExitCode -eq 0) -and ($probe.Output -match '(?m)\bpackage uv\b')
|
||||
}
|
||||
|
||||
function Test-UvInstalledInActiveVirtualenv {
|
||||
if ([string]::IsNullOrWhiteSpace($env:VIRTUAL_ENV)) {
|
||||
return $false
|
||||
}
|
||||
|
||||
$uvCommand = Get-Command uv -ErrorAction SilentlyContinue
|
||||
if (-not $uvCommand) {
|
||||
return $false
|
||||
}
|
||||
|
||||
$uvPath = [IO.Path]::GetFullPath($uvCommand.Source)
|
||||
$venvPath = ([IO.Path]::GetFullPath($env:VIRTUAL_ENV)).TrimEnd(
|
||||
[IO.Path]::DirectorySeparatorChar,
|
||||
[IO.Path]::AltDirectorySeparatorChar
|
||||
)
|
||||
$nativePrefix = "$venvPath$([IO.Path]::DirectorySeparatorChar)"
|
||||
$alternatePrefix = "$venvPath$([IO.Path]::AltDirectorySeparatorChar)"
|
||||
|
||||
return $uvPath.StartsWith($nativePrefix, [StringComparison]::OrdinalIgnoreCase) -or
|
||||
$uvPath.StartsWith($alternatePrefix, [StringComparison]::OrdinalIgnoreCase)
|
||||
}
|
||||
|
||||
function Update-ExistingUv {
|
||||
if (Test-UvSelfUpdateSupported) {
|
||||
Invoke-InstallCommand -FilePath "uv" -Arguments @("self", "update")
|
||||
return
|
||||
}
|
||||
|
||||
if (Test-UvInstalledByScoop) {
|
||||
Invoke-InstallCommand -FilePath "scoop" -Arguments @("update", "uv")
|
||||
return
|
||||
}
|
||||
|
||||
if (Test-UvInstalledByWinget) {
|
||||
Invoke-InstallCommand -FilePath "winget" -Arguments @(
|
||||
"upgrade",
|
||||
"--id",
|
||||
"astral-sh.uv",
|
||||
"-e",
|
||||
"--accept-package-agreements",
|
||||
"--accept-source-agreements"
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (Test-UvInstalledByPipx) {
|
||||
Invoke-InstallCommand -FilePath "pipx" -Arguments @("upgrade", "uv")
|
||||
return
|
||||
}
|
||||
|
||||
if (Test-UvInstalledInActiveVirtualenv) {
|
||||
Invoke-InstallCommand -FilePath "python" -Arguments @("-m", "pip", "install", "--upgrade", "uv")
|
||||
return
|
||||
}
|
||||
|
||||
if (Test-UvVersionSatisfiesMinimum) {
|
||||
Write-Host "uv is already installed and satisfies >=$MinUvVersion; skipping automatic uv update because the install source was not detected."
|
||||
return
|
||||
}
|
||||
|
||||
$version = "unknown"
|
||||
try {
|
||||
$version = Get-InstalledUvVersion
|
||||
}
|
||||
catch {
|
||||
$version = "unknown"
|
||||
}
|
||||
throw "uv $MinUvVersion or newer is required; found uv $version. The existing uv install source was not detected. Upgrade uv manually with the package manager that installed it, then rerun this installer."
|
||||
}
|
||||
|
||||
function Install-ClaudeIfMissing {
|
||||
if (Get-Command claude -ErrorAction SilentlyContinue) {
|
||||
Write-Host "Claude Code already found on PATH; skipping install."
|
||||
@@ -117,7 +296,8 @@ function Install-OrUpdateUv {
|
||||
Add-UvToPath
|
||||
|
||||
if (Get-Command uv -ErrorAction SilentlyContinue) {
|
||||
Invoke-InstallCommand -FilePath "uv" -Arguments @("self", "update")
|
||||
Update-ExistingUv
|
||||
Assert-MinUvVersion
|
||||
return
|
||||
}
|
||||
|
||||
@@ -127,6 +307,8 @@ function Install-OrUpdateUv {
|
||||
if ((-not $DryRun) -and (-not (Get-Command uv -ErrorAction SilentlyContinue))) {
|
||||
throw "uv was installed, but it is not available on PATH. Open a new terminal or add uv's bin directory to PATH."
|
||||
}
|
||||
|
||||
Assert-MinUvVersion
|
||||
}
|
||||
|
||||
function Get-PackageSpec {
|
||||
|
||||
+104
-1
@@ -3,6 +3,7 @@ set -eu
|
||||
|
||||
REPO_GIT_URL="git+https://github.com/Alishahryar1/free-claude-code.git"
|
||||
PYTHON_VERSION="3.14.0"
|
||||
MIN_UV_VERSION="0.11.0"
|
||||
UV_INSTALL_URL="https://astral.sh/uv/install.sh"
|
||||
|
||||
dry_run=0
|
||||
@@ -99,6 +100,105 @@ require_command() {
|
||||
fi
|
||||
}
|
||||
|
||||
current_uv_version() {
|
||||
version=$(uv self version --short 2>/dev/null || true)
|
||||
if [ -z "$version" ]; then
|
||||
version=$(uv --version 2>/dev/null | sed 's/^uv //; s/ .*//' || true)
|
||||
fi
|
||||
|
||||
[ -n "$version" ] || return 1
|
||||
printf '%s\n' "$version"
|
||||
}
|
||||
|
||||
version_ge() {
|
||||
current=${1%%[-+]*}
|
||||
minimum=${2%%[-+]*}
|
||||
|
||||
old_ifs=$IFS
|
||||
IFS=.
|
||||
set -- $current
|
||||
current_major=${1:-0}
|
||||
current_minor=${2:-0}
|
||||
current_patch=${3:-0}
|
||||
set -- $minimum
|
||||
minimum_major=${1:-0}
|
||||
minimum_minor=${2:-0}
|
||||
minimum_patch=${3:-0}
|
||||
IFS=$old_ifs
|
||||
|
||||
[ "$current_major" -gt "$minimum_major" ] && return 0
|
||||
[ "$current_major" -lt "$minimum_major" ] && return 1
|
||||
[ "$current_minor" -gt "$minimum_minor" ] && return 0
|
||||
[ "$current_minor" -lt "$minimum_minor" ] && return 1
|
||||
[ "$current_patch" -ge "$minimum_patch" ]
|
||||
}
|
||||
|
||||
uv_version_satisfies_minimum() {
|
||||
version=$(current_uv_version) || return 1
|
||||
version_ge "$version" "$MIN_UV_VERSION"
|
||||
}
|
||||
|
||||
validate_uv_version() {
|
||||
[ "$dry_run" -eq 1 ] && return 0
|
||||
|
||||
version=$(current_uv_version) || fail "Unable to determine uv version."
|
||||
if ! version_ge "$version" "$MIN_UV_VERSION"; then
|
||||
fail "uv $MIN_UV_VERSION or newer is required; found uv $version. Upgrade uv with its installer or package manager, then rerun this installer."
|
||||
fi
|
||||
}
|
||||
|
||||
uv_self_update_supported() {
|
||||
uv self update --dry-run >/dev/null 2>&1
|
||||
}
|
||||
|
||||
uv_installed_by_homebrew() {
|
||||
command -v brew >/dev/null 2>&1 && brew list --versions uv >/dev/null 2>&1
|
||||
}
|
||||
|
||||
uv_installed_by_pipx() {
|
||||
command -v pipx >/dev/null 2>&1 && pipx list 2>/dev/null | grep -Eq '(^|[[:space:]])package uv([[:space:]]|$)'
|
||||
}
|
||||
|
||||
uv_installed_in_active_virtualenv() {
|
||||
[ -n "${VIRTUAL_ENV:-}" ] || return 1
|
||||
|
||||
uv_path=$(command -v uv)
|
||||
case "$uv_path" in
|
||||
"$VIRTUAL_ENV"/*) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
update_existing_uv() {
|
||||
if uv_self_update_supported; then
|
||||
run uv self update
|
||||
return 0
|
||||
fi
|
||||
|
||||
if uv_installed_by_homebrew; then
|
||||
run brew upgrade uv
|
||||
return 0
|
||||
fi
|
||||
|
||||
if uv_installed_by_pipx; then
|
||||
run pipx upgrade uv
|
||||
return 0
|
||||
fi
|
||||
|
||||
if uv_installed_in_active_virtualenv; then
|
||||
run python -m pip install --upgrade uv
|
||||
return 0
|
||||
fi
|
||||
|
||||
if uv_version_satisfies_minimum; then
|
||||
printf 'uv is already installed and satisfies >=%s; skipping automatic uv update because the install source was not detected.\n' "$MIN_UV_VERSION"
|
||||
return 0
|
||||
fi
|
||||
|
||||
version=$(current_uv_version 2>/dev/null || printf 'unknown')
|
||||
fail "uv $MIN_UV_VERSION or newer is required; found uv $version. The existing uv install source was not detected. Upgrade uv manually with the package manager that installed it, then rerun this installer."
|
||||
}
|
||||
|
||||
install_claude_if_missing() {
|
||||
if command -v claude >/dev/null 2>&1; then
|
||||
printf 'Claude Code already found on PATH; skipping install.\n'
|
||||
@@ -113,7 +213,8 @@ install_or_update_uv() {
|
||||
add_uv_to_path
|
||||
|
||||
if command -v uv >/dev/null 2>&1; then
|
||||
run uv self update
|
||||
update_existing_uv
|
||||
validate_uv_version
|
||||
return 0
|
||||
fi
|
||||
|
||||
@@ -123,6 +224,8 @@ install_or_update_uv() {
|
||||
if [ "$dry_run" -eq 0 ] && ! command -v uv >/dev/null 2>&1; then
|
||||
fail "uv was installed, but it is not available on PATH. Open a new terminal or add uv's bin directory to PATH."
|
||||
fi
|
||||
|
||||
validate_uv_version
|
||||
}
|
||||
|
||||
parse_args() {
|
||||
|
||||
@@ -42,17 +42,51 @@ def test_install_sh_installs_claude_only_when_missing() -> None:
|
||||
|
||||
|
||||
def test_install_sh_installs_missing_uv_without_self_update() -> None:
|
||||
body = _braced_body(_script_text("install.sh"), "install_or_update_uv()")
|
||||
text = _script_text("install.sh")
|
||||
body = _braced_body(text, "install_or_update_uv()")
|
||||
|
||||
assert "if command -v uv >/dev/null 2>&1; then" in body
|
||||
assert body.count("run uv self update") == 1
|
||||
assert "update_existing_uv" in body
|
||||
assert "run uv self update" not in body
|
||||
|
||||
update_index = body.index("run uv self update")
|
||||
return_index = body.index("return 0", update_index)
|
||||
update_index = body.index("update_existing_uv")
|
||||
validate_existing_index = body.index("validate_uv_version", update_index)
|
||||
installer_index = body.index("run_uv_installer")
|
||||
validate_installed_index = body.index("validate_uv_version", installer_index)
|
||||
verification_index = body.index('if [ "$dry_run" -eq 0 ] && ! command -v uv')
|
||||
|
||||
assert update_index < return_index < installer_index < verification_index
|
||||
assert update_index < validate_existing_index < installer_index
|
||||
assert installer_index < verification_index < validate_installed_index
|
||||
|
||||
|
||||
def test_install_sh_updates_uv_with_detected_source() -> None:
|
||||
text = _script_text("install.sh")
|
||||
update_body = _braced_body(text, "update_existing_uv()")
|
||||
|
||||
assert "uv self update --dry-run" in text
|
||||
assert update_body.count("run uv self update") == 1
|
||||
assert update_body.index("uv_self_update_supported") < update_body.index(
|
||||
"run uv self update"
|
||||
)
|
||||
|
||||
assert "brew list --versions uv" in text
|
||||
assert "run brew upgrade uv" in update_body
|
||||
assert "pipx list" in text
|
||||
assert "run pipx upgrade uv" in update_body
|
||||
assert "VIRTUAL_ENV" in text
|
||||
assert "run python -m pip install --upgrade uv" in update_body
|
||||
assert "uv_version_satisfies_minimum" in update_body
|
||||
assert "install source was not detected" in update_body
|
||||
|
||||
|
||||
def test_install_sh_validates_minimum_uv_version() -> None:
|
||||
text = _script_text("install.sh")
|
||||
validate_body = _braced_body(text, "validate_uv_version()")
|
||||
|
||||
assert 'MIN_UV_VERSION="0.11.0"' in text
|
||||
assert "uv self version --short" in text
|
||||
assert "version_ge" in validate_body
|
||||
assert "uv $MIN_UV_VERSION or newer is required" in validate_body
|
||||
|
||||
|
||||
def test_install_ps1_installs_claude_only_when_missing() -> None:
|
||||
@@ -76,15 +110,59 @@ def test_install_ps1_installs_claude_only_when_missing() -> None:
|
||||
|
||||
|
||||
def test_install_ps1_installs_missing_uv_without_self_update() -> None:
|
||||
body = _braced_body(_script_text("install.ps1"), "function Install-OrUpdateUv")
|
||||
text = _script_text("install.ps1")
|
||||
body = _braced_body(text, "function Install-OrUpdateUv")
|
||||
self_update = 'Invoke-InstallCommand -FilePath "uv" -Arguments @("self", "update")'
|
||||
|
||||
assert "if (Get-Command uv -ErrorAction SilentlyContinue)" in body
|
||||
assert body.count(self_update) == 1
|
||||
assert "Update-ExistingUv" in body
|
||||
assert self_update not in body
|
||||
|
||||
update_index = body.index(self_update)
|
||||
return_index = body.index("return", update_index)
|
||||
update_index = body.index("Update-ExistingUv")
|
||||
validate_existing_index = body.index("Assert-MinUvVersion", update_index)
|
||||
installer_index = body.index("Invoke-UvInstaller")
|
||||
verification_index = body.index("if ((-not $DryRun)")
|
||||
validate_installed_index = body.index("Assert-MinUvVersion", installer_index)
|
||||
|
||||
assert update_index < return_index < installer_index < verification_index
|
||||
assert update_index < validate_existing_index < installer_index
|
||||
assert installer_index < verification_index < validate_installed_index
|
||||
|
||||
|
||||
def test_install_ps1_updates_uv_with_detected_source() -> None:
|
||||
text = _script_text("install.ps1")
|
||||
update_body = _braced_body(text, "function Update-ExistingUv")
|
||||
self_update = 'Invoke-InstallCommand -FilePath "uv" -Arguments @("self", "update")'
|
||||
|
||||
assert '"self", "update", "--dry-run"' in text
|
||||
assert update_body.count(self_update) == 1
|
||||
assert update_body.index("Test-UvSelfUpdateSupported") < update_body.index(
|
||||
self_update
|
||||
)
|
||||
|
||||
assert (
|
||||
'Invoke-InstallCommand -FilePath "scoop" -Arguments @("update", "uv")'
|
||||
in update_body
|
||||
)
|
||||
assert '"winget"' in update_body
|
||||
assert '"astral-sh.uv"' in update_body
|
||||
assert '"--accept-package-agreements"' in update_body
|
||||
assert (
|
||||
'Invoke-InstallCommand -FilePath "pipx" -Arguments @("upgrade", "uv")'
|
||||
in update_body
|
||||
)
|
||||
assert (
|
||||
'Invoke-InstallCommand -FilePath "python" -Arguments @("-m", "pip", "install", "--upgrade", "uv")'
|
||||
in update_body
|
||||
)
|
||||
assert "Test-UvVersionSatisfiesMinimum" in update_body
|
||||
assert "install source was not detected" in update_body
|
||||
|
||||
|
||||
def test_install_ps1_validates_minimum_uv_version() -> None:
|
||||
text = _script_text("install.ps1")
|
||||
validate_body = _braced_body(text, "function Assert-MinUvVersion")
|
||||
|
||||
assert '$MinUvVersion = "0.11.0"' in text
|
||||
assert '"self", "version", "--short"' in text
|
||||
assert "[version]" in text
|
||||
assert "uv $MinUvVersion or newer is required" in validate_body
|
||||
|
||||
Reference in New Issue
Block a user