@@ -66,7 +66,7 @@ func CheckSDKVersion(dir string, projectType ProjectType, settingsMap map[string
6666 // Find the best result (prefer lock files over source files)
6767 bestResult := findBestResult (results )
6868 if bestResult == nil {
69- return fmt .Errorf ("package %s not found in any project files" , getTargetPackageName ( projectType ))
69+ return fmt .Errorf ("package %s not found in any project files" , projectType . TargetPackageName ( ))
7070 }
7171
7272 if ! bestResult .Satisfied {
@@ -173,8 +173,8 @@ func parsePythonPackageVersion(line string) (string, bool) {
173173 // clean up the version string if it contains multiple constraints
174174 // handle comma-separated version constraints like ">=1.2.5,<2"
175175 if strings .Contains (version , "," ) {
176- parts := strings .Split (version , "," )
177- for _ , part := range parts {
176+ parts := strings .SplitSeq (version , "," )
177+ for part := range parts {
178178 trimmed := strings .TrimSpace (part )
179179 if regexp .MustCompile (`\d` ).MatchString (trimmed ) {
180180 if strings .ContainsAny (trimmed , "=~><" ) {
@@ -596,23 +596,37 @@ func checkUvLock(filePath, minVersion string) VersionCheckResult {
596596 return VersionCheckResult {Error : err }
597597 }
598598
599- // Look for livekit-agents in the lock file
600- pattern := regexp .MustCompile (`(?m)^\s*livekit-agents\s*=\s*"([^"]+)"` )
601- matches := pattern .FindStringSubmatch (string (content ))
602- if matches != nil {
603- version := matches [1 ]
604- satisfied , err := isVersionSatisfied (version , minVersion , SourceTypeLock )
605- return VersionCheckResult {
606- PackageInfo : PackageInfo {
607- Name : "livekit-agents" ,
608- Version : version ,
609- FoundInFile : filePath ,
610- ProjectType : ProjectTypePythonUV ,
611- Ecosystem : "pypi" ,
612- },
613- MinVersion : minVersion ,
614- Satisfied : satisfied ,
615- Error : err ,
599+ type uvLockPackage struct {
600+ Name string `toml:"name"`
601+ Version string `toml:"version"`
602+ }
603+
604+ type uvLockFile struct {
605+ Packages []uvLockPackage `toml:"package"`
606+ }
607+
608+ var uvLock uvLockFile
609+ if err := toml .Unmarshal (content , & uvLock ); err != nil {
610+ return VersionCheckResult {Error : err }
611+ }
612+
613+ // Check for livekit-agents in the packages
614+ for _ , pkg := range uvLock .Packages {
615+ if pkg .Name == "livekit-agents" {
616+ version := pkg .Version
617+ satisfied , err := isVersionSatisfied (version , minVersion , SourceTypeLock )
618+ return VersionCheckResult {
619+ PackageInfo : PackageInfo {
620+ Name : "livekit-agents" ,
621+ Version : version ,
622+ FoundInFile : filePath ,
623+ ProjectType : ProjectTypePythonUV ,
624+ Ecosystem : "pypi" ,
625+ },
626+ MinVersion : minVersion ,
627+ Satisfied : satisfied ,
628+ Error : err ,
629+ }
616630 }
617631 }
618632
@@ -660,10 +674,10 @@ func isVersionSatisfied(version, minVersion string, sourceType SourceType) (bool
660674 case SourceTypeLock :
661675 // For lock files, we have the exact version that was installed
662676 // Check if this exact version is >= the minimum version
663- normalizedVersion := normalizeVersion (version )
677+ normalizedVersion := normalizeVersion (version , sourceType )
664678 v , err := semver .NewVersion (normalizedVersion )
665679 if err != nil {
666- return false , fmt .Errorf ("invalid version format: %s" , version )
680+ return false , fmt .Errorf ("failed to extract base version for %s: %w " , version , err )
667681 }
668682
669683 min , err := semver .NewVersion (minVersion )
@@ -675,81 +689,54 @@ func isVersionSatisfied(version, minVersion string, sourceType SourceType) (bool
675689 return ! v .LessThan (min ), nil
676690
677691 case SourceTypePackage :
678- // For package files, we have a constraint that will be resolved at install time
679- // Check if this constraint would allow installing a version that satisfies the minimum requirement
680- packageConstraint , err := semver .NewConstraint (version )
692+ // For package files, we may have a constraint that will be resolved at install time.
693+
694+ // First, we check if the normalized version is greater than or equal to the minimum version
695+ // This is safe because in < and <= checks, the newest version will always be installed and in
696+ // ^, ~ and >= checks, if the lower bound is greater than the minimum SDK version, we're good.
697+ normalizedVersion := normalizeVersion (version , sourceType )
698+ baseVersion , err := semver .NewVersion (normalizedVersion )
681699 if err != nil {
682- return false , fmt .Errorf ("invalid package constraint format: %s " , version )
700+ return false , fmt .Errorf ("failed to extract base version for %s: %w " , version , err )
683701 }
684702
685703 min , err := semver .NewVersion (minVersion )
686704 if err != nil {
687705 return false , fmt .Errorf ("invalid minimum version format: %s" , minVersion )
688706 }
689707
690- // Check if the package constraint would allow installing a version >= minimum
691- // We do this by checking if there exists a version >= minimum that satisfies the package constraint
692- if packageConstraint .Check (min ) {
693- // The minimum version satisfies the package constraint, so it would be installable
708+ if baseVersion .GreaterThanEqual (min ) {
694709 return true , nil
695710 }
696711
697- // Check if the package constraint allows any version >= minimum
698- // This handles cases like ">=1.5.0" where 1.0.0 doesn't satisfy it, but it would install 1.5.0+ which > 1.0.0
699- // We'll test a few strategic versions to see if any satisfy the package constraint and are >= minimum
700- testVersions := []string {
701- minVersion , // The minimum version itself
702- fmt .Sprintf ("%d.%d.%d" , min .Major ()+ 1 , 0 , 0 ),
703- fmt .Sprintf ("%d.%d.%d" , min .Major (), min .Minor ()+ 1 , 0 ),
704- fmt .Sprintf ("%d.%d.%d" , min .Major (), min .Minor (), min .Patch ()+ 1 ),
705- }
706-
707- // Add more versions to cover edge cases
708- if min .Major () > 0 {
709- testVersions = append (testVersions , fmt .Sprintf ("%d.0.0" , min .Major ()))
710- }
711- if min .Minor () > 0 {
712- testVersions = append (testVersions , fmt .Sprintf ("%d.%d.0" , min .Major (), min .Minor ()))
712+ // Next, we check if min itself satisfies the package constraint. This resolves
713+ // cases in which the range includes min, like ~1.0 when min is 1.0.0.
714+ packageConstraint , err := semver .NewConstraint (version )
715+ if err != nil {
716+ return false , fmt .Errorf ("invalid package constraint format: %s" , version )
713717 }
714-
715- // Add some specific versions that might be common in constraints
716- testVersions = append (testVersions ,
717- fmt .Sprintf ("%d.%d.0" , min .Major (), min .Minor ()+ 2 ),
718- fmt .Sprintf ("%d.%d.0" , min .Major (), min .Minor ()+ 5 ),
719- fmt .Sprintf ("%d.%d.0" , min .Major (), min .Minor ()+ 10 ),
720- )
721-
722- for _ , testVersion := range testVersions {
723- if v , err := semver .NewVersion (testVersion ); err == nil {
724- // Check if this version is >= minimum and satisfies the package constraint
725- if ! v .LessThan (min ) && packageConstraint .Check (v ) {
726- return true , nil
727- }
728- }
718+ if packageConstraint .Check (min ) {
719+ return true , nil
729720 }
730-
721+
722+ // Finally, we need to check if the package constraint allows any version >= minimum.
723+
731724 return false , nil
732725
733726 default :
734727 return false , fmt .Errorf ("unknown source type: %d" , sourceType )
735728 }
736729}
737730
738- // normalizeVersion normalizes version strings for semver parsing
739- func normalizeVersion (version string ) string {
740- // Remove common prefixes and suffixes
731+ // Cleans up version strings for parsing
732+ func normalizeVersion (version string , sourceType SourceType ) string {
733+ // Remove whitespace, quotes, and version range specifiers
741734 version = strings .TrimSpace (version )
742- version = strings .Trim (version , " \" '" )
743-
744- // Handle npm version ranges (^ and ~ are npm-specific, not semver constraints)
745- if strings .HasPrefix (version , "^" ) || strings .HasPrefix (version , "~" ) {
746- version = version [1 :]
747- }
748-
735+ version = strings .Trim (version , `"'^~><=` )
749736 return version
750737}
751738
752- // findBestResult finds the best result from multiple package checks
739+ // Finds the best possible source for version checks
753740func findBestResult (results []VersionCheckResult ) * VersionCheckResult {
754741 if len (results ) == 0 {
755742 return nil
@@ -792,15 +779,3 @@ func findBestResult(results []VersionCheckResult) *VersionCheckResult {
792779
793780 return bestResult
794781}
795-
796- // getTargetPackageName returns the target package name for the project type
797- func getTargetPackageName (projectType ProjectType ) string {
798- switch projectType {
799- case ProjectTypePythonPip , ProjectTypePythonUV :
800- return "livekit-agents"
801- case ProjectTypeNode :
802- return "@livekit/agents"
803- default :
804- return ""
805- }
806- }
0 commit comments