UvPackageProvider.ps1
|
function Get-UvPackageProvider { <# .SYNOPSIS Describe the optional uv package and tool adapter without probing dependencies. .DESCRIPTION Scope defaults to All (Packages and Tools). Packages are outdated system Python packages, with TopLevelOnly defaulting to true. Tools are installed uv tools, upgraded individually with their recorded constraints intact. TopLevelOnly is Boolean and is not supported with Scope Tools. Custom Python interpreters, virtual environments and arbitrary uv arguments are not supported. The core approves each target before calling Update. #> [CmdletBinding()] param() [pscustomobject]@{ Name = 'Uv' Platforms = @('Windows', 'Linux', 'MacOS') RequiredModules = @() RequiredCommands = @('uv') OptionNames = @('Scope', 'TopLevelOnly') TestAvailable = { param([hashtable]$Options) $settings = Resolve-UvProviderOptions -Options $Options if ($settings.Scope -ne 'Tools') { # Tools alone do not need the optional package-command module. $dependency = [pscustomobject]@{ Name = 'Uv' Platforms = @('Windows', 'Linux', 'MacOS') RequiredModules = @('Shmuelie.Utilities') RequiredCommands = @('Shmuelie.Utilities\Get-UvPackages', 'Shmuelie.Utilities\Update-UvPackage') TestAvailable = $null GetTargets = {} Update = {} } Get-PackageProviderAvailability -Descriptor $dependency -Options $Options } else { [pscustomobject]@{ Available = $true; Reason = $null } } } GetTargets = { param([hashtable]$Options) $settings = Resolve-UvProviderOptions -Options $Options if ($settings.Scope -ne 'Tools') { $packages = @(Get-UvProviderPackages -Outdated -TopLevelOnly:$settings.TopLevelOnly -ErrorAction Stop) foreach ($package in $packages) { Assert-UvProviderPackage -Package $package New-PackageUpdateTarget -Target "pip:system:$($package.name)" -PreviousVersion $package.version -ProposedVersion $package.latest_version -Data @{ Kind = 'Package' Name = $package.name } } } if ($settings.Scope -ne 'Packages') { foreach ($tool in @(Get-UvProviderTools)) { New-PackageUpdateTarget -Target "tool:$($tool.name)" -PreviousVersion $tool.version -Data @{ Kind = 'Tool' Name = $tool.name } } } } Update = { param($Target, [hashtable]$Options) Update-UvProviderTarget -Target $Target -Confirm:$false } } } function Resolve-UvProviderOptions { [CmdletBinding()] param([Parameter(Mandatory)][hashtable]$Options) $scope = 'All' if ($Options.ContainsKey('Scope')) { if ($Options.Scope -isnot [string] -or $Options.Scope -notin 'All', 'Packages', 'Tools') { throw "Uv Scope must be All, Packages or Tools; custom Python environments are not supported." } $scope = $Options.Scope } $topLevelOnly = $true if ($Options.ContainsKey('TopLevelOnly')) { if ($Options.TopLevelOnly -isnot [bool]) { throw 'Uv TopLevelOnly must be a Boolean.' } if ($scope -eq 'Tools') { throw 'Uv TopLevelOnly applies only to Packages or All, not Tools.' } $topLevelOnly = $Options.TopLevelOnly } [pscustomobject]@{ Scope = $scope; TopLevelOnly = $topLevelOnly } } function Assert-UvProviderPackage { [CmdletBinding()] param([Parameter(Mandatory)][object]$Package) if ($Package.name -isnot [string] -or $Package.name -cnotmatch '\A[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?\z') { throw 'Uv returned an invalid package name; only Python distribution names are supported.' } foreach ($field in 'version', 'latest_version') { if ($null -ne $Package.$field -and ($Package.$field -isnot [string] -or [string]::IsNullOrWhiteSpace($Package.$field))) { throw "Uv returned an invalid $field for '$($Package.name)'." } } } function Get-UvProviderPackages { [CmdletBinding()] param([switch]$Outdated, [switch]$TopLevelOnly) $module = Get-Module Shmuelie.Utilities -ErrorAction Stop if (-not $module) { throw 'Shmuelie.Utilities must be loaded before uv package discovery.' } # Preference variables do not flow across module boundaries. Set them in a # child scope of Utilities so failed native reads cannot resemble empty data. & $module { param($Outdated, $TopLevelOnly) $PSNativeCommandUseErrorActionPreference = $true $ErrorActionPreference = 'Stop' Shmuelie.Utilities\Get-UvPackages -Outdated:$Outdated -TopLevelOnly:$TopLevelOnly -ErrorAction Stop } $Outdated $TopLevelOnly } function Invoke-UvProviderCommand { [CmdletBinding()] param([Parameter(Mandatory)][string[]]$Arguments) # uv writes normal diagnostics to stderr; use its exit code, not that stream. $PSNativeCommandUseErrorActionPreference = $false $previousExitCode = Get-Variable LASTEXITCODE -Scope Global -ErrorAction Ignore $previousValue = if ($previousExitCode) { $previousExitCode.Value } else { $null } try { $global:LASTEXITCODE = $null $output = @(& uv @Arguments 2>&1) $exitCode = $global:LASTEXITCODE if ($exitCode -isnot [int]) { throw 'uv did not report a numeric exit code.' } if ($exitCode -ne 0) { throw "uv $($Arguments[0]) $($Arguments[1]) failed (exit $exitCode): $($output -join [Environment]::NewLine)" } $output | ForEach-Object { $_.ToString() } } finally { if ($previousExitCode) { $global:LASTEXITCODE = $previousValue } else { Remove-Variable LASTEXITCODE -Scope Global -ErrorAction Ignore } } } function Get-UvProviderTools { [CmdletBinding()] param() # uv tool list has no JSON output option. Accept its documented plain format # strictly, including only tool headings and their entrypoint rows. $lines = @(Invoke-UvProviderCommand -Arguments @('tool', 'list', '--color', 'never', '--no-progress')) $names = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) $hasTool = $false foreach ($line in $lines) { if ([string]::IsNullOrWhiteSpace($line)) { continue } if ($line -ceq 'No tools installed' -and $lines.Count -eq 1) { return } if ($line -cmatch '\A([A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?) v(\S+)\z') { $name = $Matches[1] $version = $Matches[2] if (-not $names.Add(($name -replace '[-_.]+', '-'))) { throw "uv tool list returned duplicate tool '$name'." } $hasTool = $true [pscustomobject]@{ name = $name; version = $version } } elseif (-not ($hasTool -and $line -cmatch '\A- \S+\z')) { throw "Unsupported uv tool list output: $line" } } } function Update-UvProviderTarget { <# .SYNOPSIS Update one approved uv system package or installed tool and observe its version. .DESCRIPTION Private adapter callback. The aggregate command supplies Confirm false after approving this exact target. Native tool diagnostics are verbose; malformed output and failed updates propagate to the core as failures. #> [CmdletBinding(SupportsShouldProcess)] param([Parameter(Mandatory)][psobject]$Target) $name = $Target.Data.Name Assert-UvProviderPackage -Package ([pscustomobject]@{ name = $name }) $kind = $Target.Data.Kind $expectedTarget = switch ($kind) { 'Package' { "pip:system:$name" } 'Tool' { "tool:$name" } default { throw "Unsupported uv target kind '$kind'." } } if ($Target.Target -cne $expectedTarget) { throw 'Uv target identity does not match its package or tool data.' } if (-not $PSCmdlet.ShouldProcess($Target.Target, 'Update uv package or tool')) { return New-PackageUpdateResult -Provider Uv -Target $Target.Target -PreviousVersion $Target.PreviousVersion -Status Skipped -Reason 'Update was not confirmed.' } if ($kind -eq 'Package') { $updates = @(Shmuelie.Utilities\Update-UvPackage -PackageName $name -Confirm:$false -ErrorAction Stop) if ($updates.Count -ne 1 -or $updates[0].PSTypeNames -notcontains 'UvUpdateResult' -or $updates[0].Name -cne $name -or $updates[0].Success -isnot [bool]) { throw "Update-UvPackage returned an invalid or missing result for '$name'." } if (-not $updates[0].Success) { throw "Update-UvPackage failed for '$name'." } $installed = @(Get-UvProviderPackages -ErrorAction Stop) } else { Invoke-UvProviderCommand -Arguments @('tool', 'upgrade', '--color', 'never', '--no-progress', '--', $name) | ForEach-Object { Write-Verbose $_ } $installed = @(Get-UvProviderTools) } $observed = @($installed | Where-Object { ($_.name -replace '[-_.]+', '-') -eq ($name -replace '[-_.]+', '-') }) if ($observed.Count -ne 1) { throw "Cannot observe exactly one installed uv $kind '$name' after update." } Assert-UvProviderPackage -Package $observed[0] $version = $observed[0].version if (-not $version -or -not $Target.PreviousVersion) { throw "Cannot determine a version change for uv $kind '$name'; its previous or observed version is unknown." } $status = if ($version -ceq $Target.PreviousVersion) { 'Unchanged' } else { 'Updated' } New-PackageUpdateResult -Provider Uv -Target $Target.Target -PreviousVersion $Target.PreviousVersion -ResultingVersion $version -Status $status } |