# Extension Version Resolution # Resolves versions from OCI tags, Gitea releases, and local sources use ../utils/logging.nu * use ../oci/client.nu * # Resolve version from version specification export def resolve-version [ extension_type: string extension_name: string version_spec: string source_type: string = "auto" ]: nothing -> string { match $source_type { "oci" => (resolve-oci-version $extension_type $extension_name $version_spec) "gitea" => (resolve-gitea-version $extension_type $extension_name $version_spec) "local" => "local" "auto" => { # Try OCI first, then Gitea, then local if (is-oci-available) { resolve-oci-version $extension_type $extension_name $version_spec } else if (is-gitea-available) { resolve-gitea-version $extension_type $extension_name $version_spec } else { "local" } } _ => $version_spec } } # Resolve version from OCI registry tags export def resolve-oci-version [ extension_type: string extension_name: string version_spec: string ]: nothing -> string { let result = (do { let config = (get-oci-config) let token = (load-oci-token $config.auth_token_path) # Get all available tags from OCI registry let tags = (oci-get-artifact-tags $config.registry $config.namespace $extension_name --auth-token $token ) if ($tags | is-empty) { log-warn $"No tags found for ($extension_name) in OCI registry" return $version_spec } # Filter to valid semver tags let versions = ($tags | where ($it | is-semver) | sort-by-semver ) if ($versions | is-empty) { log-warn $"No valid semver versions found for ($extension_name)" return ($tags | last) } # Resolve version spec match $version_spec { "*" | "latest" => { log-debug $"Resolved 'latest' to ($versions | last)" $versions | last } _ => { if ($version_spec | str starts-with "^") { # Caret: compatible with version (same major) resolve-caret-constraint $version_spec $versions } else if ($version_spec | str starts-with "~") { # Tilde: approximately equivalent (same minor) resolve-tilde-constraint $version_spec $versions } else if ($version_spec | str contains "-") { # Range: version1-version2 resolve-range-constraint $version_spec $versions } else if ($version_spec | str contains ">") or ($version_spec | str contains "<") { # Comparison operators resolve-comparison-constraint $version_spec $versions } else { # Exact version if $version_spec in $versions { $version_spec } else { log-warn $"Exact version ($version_spec) not found, using latest" $versions | last } } } } } | complete) if $result.exit_code == 0 { $result.stdout } else { log-error $"Failed to resolve OCI version: ($result.stderr)" $version_spec } } # Resolve version from Gitea releases export def resolve-gitea-version [ extension_type: string extension_name: string version_spec: string ]: nothing -> string { # TODO: Implement Gitea version resolution log-warn "Gitea version resolution not yet implemented" $version_spec } # Resolve caret constraint (^1.2.3 -> >=1.2.3 <2.0.0) def resolve-caret-constraint [ version_spec: string versions: list ]: nothing -> string { let version = ($version_spec | str replace "^" "" | str replace "v" "") let parts = ($version | split row ".") let major = ($parts | get 0 | into int) # Get all versions with same major let compatible = ($versions | where {|v| let v_clean = ($v | str replace "v" "") let v_parts = ($v_clean | split row ".") let v_major = ($v_parts | get 0 | into int) $v_major == $major and (compare-semver $v_clean $version) >= 0 } ) if ($compatible | is-empty) { log-warn $"No compatible versions found for ($version_spec)" $versions | last } else { $compatible | last } } # Resolve tilde constraint (~1.2.3 -> >=1.2.3 <1.3.0) def resolve-tilde-constraint [ version_spec: string versions: list ]: nothing -> string { let version = ($version_spec | str replace "~" "" | str replace "v" "") let parts = ($version | split row ".") let major = ($parts | get 0 | into int) let minor = ($parts | get 1 | into int) # Get all versions with same major.minor let compatible = ($versions | where {|v| let v_clean = ($v | str replace "v" "") let v_parts = ($v_clean | split row ".") let v_major = ($v_parts | get 0 | into int) let v_minor = ($v_parts | get 1 | into int) $v_major == $major and $v_minor == $minor and (compare-semver $v_clean $version) >= 0 } ) if ($compatible | is-empty) { log-warn $"No compatible versions found for ($version_spec)" $versions | last } else { $compatible | last } } # Resolve range constraint (1.2.3-1.5.0) def resolve-range-constraint [ version_spec: string versions: list ]: nothing -> string { let range_parts = ($version_spec | split row "-") let min_version = ($range_parts | get 0 | str trim | str replace "v" "") let max_version = ($range_parts | get 1 | str trim | str replace "v" "") let in_range = ($versions | where {|v| let v_clean = ($v | str replace "v" "") (compare-semver $v_clean $min_version) >= 0 and (compare-semver $v_clean $max_version) <= 0 } ) if ($in_range | is-empty) { log-warn $"No versions found in range ($version_spec)" $versions | last } else { $in_range | last } } # Resolve comparison constraint (>=1.2.3, <2.0.0, etc.) def resolve-comparison-constraint [ version_spec: string versions: list ]: nothing -> string { # TODO: Implement comparison operators log-warn "Comparison operators not yet implemented, using latest" $versions | last } # Check if string is valid semver export def is-semver []: string -> bool { $in =~ '^v?\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?(\+[a-zA-Z0-9.]+)?$' } # Compare semver versions (-1 if a < b, 0 if equal, 1 if a > b) export def compare-semver [a: string, b: string]: nothing -> int { let a_clean = ($a | str replace "v" "") let b_clean = ($b | str replace "v" "") # Split into parts let a_parts = ($a_clean | split row "-" | get 0 | split row ".") let b_parts = ($b_clean | split row "-" | get 0 | split row ".") # Compare major.minor.patch for i in 0..2 { let a_num = ($a_parts | get -o $i | default "0" | into int) let b_num = ($b_parts | get -o $i | default "0" | into int) if $a_num < $b_num { return (-1) } else if $a_num > $b_num { return 1 } } # If base versions equal, check pre-release let a_prerelease = ($a_clean | split row "-" | get -o 1 | default "") let b_prerelease = ($b_clean | split row "-" | get -o 1 | default "") if ($a_prerelease | is-empty) and ($b_prerelease | is-not-empty) { return 1 # Release > pre-release } else if ($a_prerelease | is-not-empty) and ($b_prerelease | is-empty) { return (-1) # Pre-release < release } else if ($a_prerelease | is-empty) and ($b_prerelease | is-empty) { return 0 # Both releases, equal } else { # Compare pre-release strings lexicographically if $a_prerelease < $b_prerelease { -1 } else if $a_prerelease > $b_prerelease { 1 } else { 0 } } } # Sort versions by semver export def sort-by-semver []: list -> list { $in | sort-by --custom {|a, b| compare-semver $a $b } } # Get latest version from list export def get-latest-version [versions: list]: nothing -> string { $versions | where ($it | is-semver) | sort-by-semver | last } # Check if version satisfies constraint export def satisfies-constraint [ version: string constraint: string ]: nothing -> bool { match $constraint { "*" | "latest" => true _ => { if ($constraint | str starts-with "^") { satisfies-caret $version $constraint } else if ($constraint | str starts-with "~") { satisfies-tilde $version $constraint } else if ($constraint | str contains "-") { satisfies-range $version $constraint } else { # Exact match ($version | str replace "v" "") == ($constraint | str replace "v" "") } } } } # Check if version satisfies caret constraint def satisfies-caret [version: string, constraint: string]: nothing -> bool { let version_clean = ($version | str replace "v" "") let constraint_clean = ($constraint | str replace "^" "" | str replace "v" "") let v_parts = ($version_clean | split row ".") let c_parts = ($constraint_clean | split row ".") let v_major = ($v_parts | get 0 | into int) let c_major = ($c_parts | get 0 | into int) $v_major == $c_major and (compare-semver $version_clean $constraint_clean) >= 0 } # Check if version satisfies tilde constraint def satisfies-tilde [version: string, constraint: string]: nothing -> bool { let version_clean = ($version | str replace "v" "") let constraint_clean = ($constraint | str replace "~" "" | str replace "v" "") let v_parts = ($version_clean | split row ".") let c_parts = ($constraint_clean | split row ".") let v_major = ($v_parts | get 0 | into int) let v_minor = ($v_parts | get 1 | into int) let c_major = ($c_parts | get 0 | into int) let c_minor = ($c_parts | get 1 | into int) $v_major == $c_major and $v_minor == $c_minor and (compare-semver $version_clean $constraint_clean) >= 0 } # Check if version satisfies range constraint def satisfies-range [version: string, constraint: string]: nothing -> bool { let version_clean = ($version | str replace "v" "") let range_parts = ($constraint | split row "-") let min = ($range_parts | get 0 | str trim | str replace "v" "") let max = ($range_parts | get 1 | str trim | str replace "v" "") (compare-semver $version_clean $min) >= 0 and (compare-semver $version_clean $max) <= 0 } # Check if Gitea is available def is-gitea-available []: nothing -> bool { # TODO: Implement Gitea availability check false }