Skip to content

Commit

Permalink
support resolving packages with validator
Browse files Browse the repository at this point in the history
  • Loading branch information
coffee-cup committed Mar 3, 2025
1 parent 040ede8 commit e327710
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 37 deletions.
1 change: 1 addition & 0 deletions core/generate/mise_step_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ func (b *MiseStepBuilder) Build(options *BuildStepOptions) (*plan.Step, error) {
}

// Setup apt commands
// TODO: This should be a separate step
if len(b.SupportingAptPackages) > 0 {
step.AddCommands([]plan.Command{
options.NewAptInstallCommand(b.SupportingAptPackages),
Expand Down
32 changes: 31 additions & 1 deletion core/mise/mise.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func (m *Mise) GetLatestVersion(pkg, version string) (string, error) {
defer unlock()

query := fmt.Sprintf("%s@%s", pkg, strings.TrimSpace(version))
output, err := m.runCmd("latest", "--verbose", query)
output, err := m.runCmd("latest", query)
if err != nil {
return "", err
}
Expand All @@ -61,6 +61,36 @@ func (m *Mise) GetLatestVersion(pkg, version string) (string, error) {
return latestVersion, nil
}

func (m *Mise) GetAllVersions(pkg, version string) ([]string, error) {
_, unlock, err := m.createAndLock(pkg)
if err != nil {
return nil, err
}
defer unlock()

query := fmt.Sprintf("%s@%s", pkg, strings.TrimSpace(version))
output, err := m.runCmd("ls-remote", query)
if err != nil {
return nil, err
}

lines := strings.Split(strings.TrimSpace(output), "\n")
var versions []string
for _, line := range lines {
version := strings.TrimSpace(line)
if version == "" || strings.Contains(version, "RC") {
continue
}
versions = append(versions, version)
}

if len(versions) == 0 {
return nil, fmt.Errorf(ErrMiseGetLatestVersion, version, pkg)
}

return versions, nil
}

// runCmd runs a mise command with the given arguments
func (m *Mise) runCmd(args ...string) (string, error) {
cacheDir := filepath.Join(m.cacheDir, "cache")
Expand Down
134 changes: 105 additions & 29 deletions core/mise/mise_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"os"
"strings"
"testing"

"github.com/stretchr/testify/require"
)

func TestMistGetLatestVersion(t *testing.T) {
Expand All @@ -18,33 +20,107 @@ func TestMistGetLatestVersion(t *testing.T) {
t.Fatalf("failed to create mise: %v", err)
}

t.Run("node version", func(t *testing.T) {
nodeVersion, err := mise.GetLatestVersion("node", "22")
if err != nil {
t.Fatalf("failed to get latest version: %v", err)
}

if !strings.HasPrefix(nodeVersion, "22") {
t.Errorf("Expected Node.js version to start with 22, got %s", nodeVersion)
}
})

// Test Bun version
t.Run("bun version", func(t *testing.T) {
bunVersion, err := mise.GetLatestVersion("bun", "latest")
if err != nil {
t.Errorf("Failed to get Bun version: %v", err)
}
if bunVersion == "" {
t.Error("Expected non-empty Bun version")
}
})

// Test non-existent version
t.Run("non-existent version", func(t *testing.T) {
_, err := mise.GetLatestVersion("node", "999")
if err == nil {
t.Error("Expected error for non-existent version, got nil")
}
})
tests := []struct {
name string
runtime string
version string
wantPrefix string
wantErr bool
}{
{
name: "node latest version",
runtime: "node",
version: "22",
wantPrefix: "22",
},
{
name: "bun latest version",
runtime: "bun",
version: "latest",
},
{
name: "non-existent latest version",
runtime: "node",
version: "999",
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := mise.GetLatestVersion(tt.runtime, tt.version)
if (err != nil) != tt.wantErr {
t.Errorf("GetLatestVersion() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr {
if tt.wantPrefix != "" && !strings.HasPrefix(got, tt.wantPrefix) {
t.Errorf("GetLatestVersion() got = %v, want prefix %v", got, tt.wantPrefix)
}
if got == "" {
t.Error("GetLatestVersion() got empty version")
}
}
})
}
}

func TestMiseGetAllVersions(t *testing.T) {
tempDir, err := os.MkdirTemp("", "mise-test")
if err != nil {
t.Fatalf("failed to create temp dir: %v", err)
}
defer os.RemoveAll(tempDir)

mise, err := New(tempDir)
if err != nil {
t.Fatalf("failed to create mise: %v", err)
}

tests := []struct {
name string
runtime string
version string
versions []string
wantErr bool
}{
{
name: "node all versions",
runtime: "node",
version: "18.20",
versions: []string{"18.20.0", "18.20.1", "18.20.2", "18.20.3", "18.20.4", "18.20.5", "18.20.6", "18.20.7"},
},
{
name: "bun all versions",
runtime: "bun",
version: "0.8",
versions: []string{"0.8.0", "0.8.1"},
},
{
name: "php all versions",
runtime: "php",
version: "7.4.2",
versions: []string{"7.4.2", "7.4.20", "7.4.21", "7.4.22", "7.4.23", "7.4.24", "7.4.25", "7.4.26", "7.4.27", "7.4.28", "7.4.29"},
},
{
name: "non-existent all versions",
runtime: "node",
version: "999",
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := mise.GetAllVersions(tt.runtime, tt.version)
if (err != nil) != tt.wantErr {
t.Errorf("GetAllVersions() error = %v, wantErr %v", err, tt.wantErr)
return
}

if !tt.wantErr {
require.Equal(t, tt.versions, got)
}
})
}
}
41 changes: 35 additions & 6 deletions core/resolver/resolver.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package resolver

import (
"fmt"
"strings"

"github.com/charmbracelet/log"
Expand All @@ -18,9 +19,10 @@ type Resolver struct {
}

type RequestedPackage struct {
Name string
Version string
Source string
Name string
Version string
Source string
IsVersionAvailable func(version string) bool
}

type ResolvedPackage struct {
Expand Down Expand Up @@ -66,10 +68,33 @@ func (r *Resolver) ResolvePackages() (map[string]*ResolvedPackage, error) {

for name, pkg := range r.packages {
fuzzyVersion := resolveToFuzzyVersion(pkg.Version)
latestVersion, err := r.mise.GetLatestVersion(name, fuzzyVersion)

if err != nil {
return nil, err
var latestVersion string

// If there is a custom version validator, we get possible versions and pick the latest one that matches
if pkg.IsVersionAvailable != nil {
versions, err := r.mise.GetAllVersions(name, fuzzyVersion)
if err != nil {
return nil, err
}

for i := len(versions) - 1; i >= 0; i-- {
if pkg.IsVersionAvailable(versions[i]) {
latestVersion = versions[i]
break
}
}

if latestVersion == "" {
return nil, fmt.Errorf("no version available for %s %s", name, pkg.Version)
}
} else {
// Otherwise, we just get the latest version
var err error
latestVersion, err = r.mise.GetLatestVersion(name, fuzzyVersion)
if err != nil {
return nil, err
}
}

log.Debugf("Resolved package version %s %s to %s from %s", name, pkg.Version, latestVersion, pkg.Source)
Expand Down Expand Up @@ -112,3 +137,7 @@ func (r *Resolver) Version(ref PackageRef, version, source string) PackageRef {
func (r *Resolver) SetPreviousVersion(name, version string) {
r.previousVersions[name] = version
}

func (r *Resolver) SetVersionAvailable(ref PackageRef, isVersionAvailable func(version string) bool) {
r.packages[ref.Name].IsVersionAvailable = isVersionAvailable
}
27 changes: 26 additions & 1 deletion core/resolver/resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,16 @@ func TestPackageResolver(t *testing.T) {
resolver.Version(python, "3.12", "PYTHON_VERSION environment variable")
resolver.Version(python, "3.13", ".python-version")

// Set up PHP
php := resolver.Default("php", "7.3")
resolver.SetVersionAvailable(php, func(version string) bool {
return version == "7.3.27"
})

// Resolve all packages
resolvedPackages, err := resolver.ResolvePackages()
require.NoError(t, err)
assert.Equal(t, 4, len(resolvedPackages))
assert.Equal(t, 5, len(resolvedPackages))

// Check Node.js resolution
nodeResolved := resolvedPackages["node"]
Expand All @@ -65,6 +71,12 @@ func TestPackageResolver(t *testing.T) {
require.NotNil(t, pythonResolved)
require.NotNil(t, pythonResolved.ResolvedVersion)
assert.Contains(t, *pythonResolved.ResolvedVersion, "3.13")

// Check PHP resolution
phpResolved := resolvedPackages["php"]
require.NotNil(t, phpResolved)
require.NotNil(t, phpResolved.ResolvedVersion)
assert.Contains(t, *phpResolved.ResolvedVersion, "7.3.27")
}

func TestPackageResolverWithPreviousVersions(t *testing.T) {
Expand All @@ -91,3 +103,16 @@ func TestPackageResolverWithPreviousVersions(t *testing.T) {
assert.Equal(t, "1.23", pkg.Version)
assert.Equal(t, DefaultSource, pkg.Source)
}

func TestResolvingPackagesNotAvailable(t *testing.T) {
resolver, err := NewResolver(mise.TestInstallDir)
require.NoError(t, err)

node := resolver.Default("node", "18.20")
resolver.SetVersionAvailable(node, func(version string) bool {
return version == "100"
})

_, err = resolver.ResolvePackages()
require.Error(t, err)
}

0 comments on commit e327710

Please sign in to comment.