2025-06-09 17:37:08 +05:30
|
|
|
package packagemanager
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
"github.com/Masterminds/semver"
|
|
|
|
|
"github.com/safedep/dry/packageregistry"
|
|
|
|
|
)
|
|
|
|
|
|
2025-08-06 21:10:51 +05:30
|
|
|
func pypiGetMatchingVersion(packageName, versionConstraint string) (string, error) {
|
2025-06-09 17:37:08 +05:30
|
|
|
// Already a exact version
|
2025-08-29 00:21:09 +05:30
|
|
|
if after, ok := strings.CutPrefix(versionConstraint, "=="); ok {
|
|
|
|
|
return after, nil
|
2025-06-09 17:37:08 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle compatible release operator
|
|
|
|
|
if strings.HasPrefix(versionConstraint, "~=") {
|
2025-08-06 21:10:51 +05:30
|
|
|
versionConstraint = pypiConvertCompatibleRelease(versionConstraint)
|
2025-06-09 17:37:08 +05:30
|
|
|
}
|
2025-08-29 00:21:09 +05:30
|
|
|
|
2025-06-09 17:37:08 +05:30
|
|
|
// Handle empty version constraint
|
|
|
|
|
if versionConstraint == "" {
|
|
|
|
|
// Get latest version
|
|
|
|
|
registry, err := packageregistry.NewPypiAdapter()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", fmt.Errorf("failed to create pypi adapter: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pd, err := registry.PackageDiscovery()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", fmt.Errorf("failed to get package discovery: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pkg, err := pd.GetPackage(packageName)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return pkg.LatestVersion, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
registry, err := packageregistry.NewPypiAdapter()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", fmt.Errorf("failed to create pypi adapter: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pd, err := registry.PackageDiscovery()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", fmt.Errorf("failed to get package discovery: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get package info from PyPI
|
|
|
|
|
pkg, err := pd.GetPackage(packageName)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Parse version constraint
|
|
|
|
|
constraint, err := semver.NewConstraint(versionConstraint)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", fmt.Errorf("invalid version constraint: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get valid versions and find best match
|
|
|
|
|
bestMatch, err := findBestMatchingVersion(pkg.Versions, constraint)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", fmt.Errorf("no version matches constraint %q: %w", versionConstraint, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return bestMatch.Original(), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func findBestMatchingVersion(releases []packageregistry.PackageVersionInfo, constraint *semver.Constraints) (*semver.Version, error) {
|
|
|
|
|
if len(releases) == 0 {
|
|
|
|
|
return nil, fmt.Errorf("no versions available")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var bestMatch *semver.Version
|
|
|
|
|
// We'll iterate once through all versions
|
|
|
|
|
for _, v := range releases {
|
|
|
|
|
ver, err := semver.NewVersion(v.Version)
|
|
|
|
|
if err != nil {
|
|
|
|
|
continue // Skip invalid versions
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update bestMatch if this version is higher and matches constraint
|
|
|
|
|
if constraint.Check(ver) && (bestMatch == nil || ver.GreaterThan(bestMatch)) {
|
|
|
|
|
bestMatch = ver
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if bestMatch == nil {
|
|
|
|
|
return nil, fmt.Errorf("no version matches constraint")
|
|
|
|
|
}
|
|
|
|
|
return bestMatch, nil
|
|
|
|
|
}
|