Skip to content

Commit 1bb5f37

Browse files
authored
Merge branch 'main' into fix/windows-system-path
2 parents c691266 + 9741f9e commit 1bb5f37

4 files changed

Lines changed: 237 additions & 74 deletions

File tree

src/cmd/current.go

Lines changed: 95 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ import (
88
"github.com/spf13/cobra"
99
)
1010

11+
// runtimeStatus holds the status of a configured runtime
12+
type runtimeStatus struct {
13+
provider runtime.Provider
14+
version string
15+
installed bool
16+
}
17+
1118
var currentCmd = &cobra.Command{
1219
Use: "current [runtime]",
1320
Short: "Show the currently active version(s)",
@@ -21,43 +28,102 @@ Examples:
2128
Args: cobra.MaximumNArgs(1),
2229
Run: func(cmd *cobra.Command, args []string) {
2330
if len(args) == 0 {
24-
// Show all active versions
25-
ui.Header("Currently active versions:")
26-
providers := runtime.GetAll()
31+
showAllVersions()
32+
} else {
33+
showSingleVersion(args[0])
34+
}
35+
},
36+
}
2737

28-
if len(providers) == 0 {
29-
ui.Info("No runtime providers registered")
30-
return
31-
}
38+
// showAllVersions displays all configured runtimes and prompts to install missing ones
39+
func showAllVersions() {
40+
providers := runtime.GetAll()
41+
42+
if len(providers) == 0 {
43+
ui.Info("No runtime providers registered")
44+
return
45+
}
46+
47+
// Collect status for all configured runtimes
48+
var configured []runtimeStatus
49+
for _, provider := range providers {
50+
version, err := provider.CurrentVersion()
51+
if err != nil {
52+
// Not configured - skip it
53+
continue
54+
}
55+
installed, _ := provider.IsInstalled(version)
56+
configured = append(configured, runtimeStatus{
57+
provider: provider,
58+
version: version,
59+
installed: installed,
60+
})
61+
}
62+
63+
if len(configured) == 0 {
64+
ui.Info("No runtimes configured")
65+
return
66+
}
3267

33-
for _, provider := range providers {
34-
version, err := provider.CurrentVersion()
35-
if err != nil {
36-
fmt.Printf(" %s: %v\n", provider.DisplayName(), err)
68+
// Display all configured versions
69+
ui.Header("Currently active versions:")
70+
var missing []runtimeStatus
71+
for _, rs := range configured {
72+
if rs.installed {
73+
fmt.Printf(" %s: %s\n", ui.Highlight(rs.provider.DisplayName()), ui.HighlightVersion(rs.version))
74+
} else {
75+
ui.Warning("%s: %s (not installed)", rs.provider.DisplayName(), rs.version)
76+
missing = append(missing, rs)
77+
}
78+
}
79+
80+
// Prompt to install missing versions
81+
if len(missing) > 0 {
82+
fmt.Println()
83+
if ui.PromptInstallMissing(missing) {
84+
for _, rs := range missing {
85+
ui.Info("Installing %s %s...", rs.provider.DisplayName(), rs.version)
86+
if err := rs.provider.Install(rs.version); err != nil {
87+
ui.Error("Failed to install %s %s: %v", rs.provider.DisplayName(), rs.version, err)
3788
} else {
38-
fmt.Printf(" %s: %s\n", ui.Highlight(provider.DisplayName()), ui.HighlightVersion(version))
89+
ui.Success("%s %s installed successfully", rs.provider.DisplayName(), rs.version)
3990
}
4091
}
41-
} else {
42-
// Show specific runtime version
43-
runtimeName := args[0]
44-
45-
provider, err := runtime.Get(runtimeName)
46-
if err != nil {
47-
ui.Error("%v", err)
48-
ui.Info("Available runtimes: %v", runtime.List())
49-
return
50-
}
92+
}
93+
}
94+
}
5195

52-
version, err := provider.CurrentVersion()
53-
if err != nil {
54-
ui.Error("%v", err)
55-
return
56-
}
96+
// showSingleVersion displays a single runtime version and prompts to install if missing
97+
func showSingleVersion(runtimeName string) {
98+
provider, err := runtime.Get(runtimeName)
99+
if err != nil {
100+
ui.Error("%v", err)
101+
ui.Info("Available runtimes: %v", runtime.List())
102+
return
103+
}
104+
105+
version, err := provider.CurrentVersion()
106+
if err != nil {
107+
ui.Error("%v", err)
108+
return
109+
}
57110

58-
fmt.Printf("%s: %s\n", ui.Highlight(provider.DisplayName()), ui.HighlightVersion(version))
111+
installed, _ := provider.IsInstalled(version)
112+
if installed {
113+
fmt.Printf("%s: %s\n", ui.Highlight(provider.DisplayName()), ui.HighlightVersion(version))
114+
return
115+
}
116+
117+
// Not installed - show with warning and prompt
118+
ui.Warning("%s: %s (not installed)", provider.DisplayName(), version)
119+
fmt.Println()
120+
if ui.PromptInstall(provider.DisplayName(), version) {
121+
if err := provider.Install(version); err != nil {
122+
ui.Error("Failed to install %s %s: %v", provider.DisplayName(), version, err)
123+
return
59124
}
60-
},
125+
ui.Success("%s %s installed successfully", provider.DisplayName(), version)
126+
}
61127
}
62128

63129
func init() {

src/cmd/list.go

Lines changed: 74 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,41 +7,96 @@ import (
77
)
88

99
var listCmd = &cobra.Command{
10-
Use: "list <runtime>",
11-
Short: "List installed versions of a runtime",
12-
Long: `List all installed versions of a specific runtime.
10+
Use: "list [runtime]",
11+
Short: "List installed versions",
12+
Long: `List all installed versions of a specific runtime, or all runtimes if none specified.
1313
1414
Examples:
15-
dtvem list python
16-
dtvem list node`,
17-
Args: cobra.ExactArgs(1),
15+
dtvem list # List all installed versions
16+
dtvem list python # List installed Python versions
17+
dtvem list node # List installed Node.js versions`,
18+
Args: cobra.MaximumNArgs(1),
1819
Run: func(cmd *cobra.Command, args []string) {
19-
runtimeName := args[0]
20-
21-
provider, err := runtime.Get(runtimeName)
22-
if err != nil {
23-
ui.Error("%v", err)
24-
ui.Info("Available runtimes: %v", runtime.List())
25-
return
20+
if len(args) == 0 {
21+
listAllRuntimes()
22+
} else {
23+
listSingleRuntime(args[0])
2624
}
25+
},
26+
}
27+
28+
// listAllRuntimes lists installed versions for all runtimes
29+
func listAllRuntimes() {
30+
providers := runtime.GetAll()
2731

28-
ui.Header("Installed %s versions:", provider.DisplayName())
32+
if len(providers) == 0 {
33+
ui.Info("No runtime providers registered")
34+
return
35+
}
2936

37+
ui.Header("Installed versions:")
38+
39+
hasAny := false
40+
for _, provider := range providers {
3041
versions, err := provider.ListInstalled()
3142
if err != nil {
32-
ui.Error("%v", err)
33-
return
43+
ui.Error(" %s: %v", provider.DisplayName(), err)
44+
continue
3445
}
3546

3647
if len(versions) == 0 {
37-
ui.Info("No versions installed")
38-
return
48+
continue
3949
}
4050

51+
hasAny = true
52+
globalVersion, _ := provider.GlobalVersion()
53+
54+
ui.Printf(" %s:\n", ui.Highlight(provider.DisplayName()))
4155
for _, v := range versions {
56+
if v.String() == globalVersion {
57+
ui.Printf(" %s (global)\n", ui.HighlightVersion(v.String()))
58+
} else {
59+
ui.Printf(" %s\n", ui.HighlightVersion(v.String()))
60+
}
61+
}
62+
}
63+
64+
if !hasAny {
65+
ui.Info("No versions installed")
66+
}
67+
}
68+
69+
// listSingleRuntime lists installed versions for a specific runtime
70+
func listSingleRuntime(runtimeName string) {
71+
provider, err := runtime.Get(runtimeName)
72+
if err != nil {
73+
ui.Error("%v", err)
74+
ui.Info("Available runtimes: %v", runtime.List())
75+
return
76+
}
77+
78+
ui.Header("Installed %s versions:", provider.DisplayName())
79+
80+
versions, err := provider.ListInstalled()
81+
if err != nil {
82+
ui.Error("%v", err)
83+
return
84+
}
85+
86+
if len(versions) == 0 {
87+
ui.Info("No versions installed")
88+
return
89+
}
90+
91+
globalVersion, _ := provider.GlobalVersion()
92+
93+
for _, v := range versions {
94+
if v.String() == globalVersion {
95+
ui.Printf(" %s (global)\n", ui.HighlightVersion(v.String()))
96+
} else {
4297
ui.Printf(" %s\n", ui.HighlightVersion(v.String()))
4398
}
44-
},
99+
}
45100
}
46101

47102
func init() {

src/cmd/shim/main.go

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -194,33 +194,10 @@ func findInSystemPath(execName string) string {
194194
return ""
195195
}
196196

197-
// shouldAutoInstall prompts the user to install a missing version
198-
// Returns true if the user wants to install, false otherwise
197+
// shouldAutoInstall prompts the user to install a missing version.
198+
// Delegates to ui.PromptInstall for consistent behavior across CLI and shim.
199199
func shouldAutoInstall(displayName, version string) bool {
200-
// Check if running in non-interactive mode (CI/automation)
201-
if os.Getenv("DTVEM_AUTO_INSTALL") == "false" {
202-
return false
203-
}
204-
205-
// If DTVEM_AUTO_INSTALL=true, auto-install without prompting
206-
if os.Getenv("DTVEM_AUTO_INSTALL") == "true" {
207-
return true
208-
}
209-
210-
// Interactive prompt
211-
ui.Warning("%s %s is not installed", displayName, version)
212-
ui.Info("Install it now? [Y/n]: ")
213-
214-
var response string
215-
_, _ = fmt.Scanln(&response)
216-
response = strings.ToLower(strings.TrimSpace(response))
217-
218-
// Default to "yes" if empty response
219-
if response == "" || response == constants.ResponseY || response == constants.ResponseYes {
220-
return true
221-
}
222-
223-
return false
200+
return ui.PromptInstall(displayName, version)
224201
}
225202

226203
// getShimName returns the name of this shim binary

src/internal/ui/output.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package ui
33

44
import (
55
"fmt"
6+
"os"
7+
"strings"
68

79
"github.com/fatih/color"
810
)
@@ -78,3 +80,66 @@ func Highlight(text string) string {
7880
func HighlightVersion(version string) string {
7981
return color.New(color.FgMagenta, color.Bold).Sprint(version)
8082
}
83+
84+
// PromptInstall prompts the user to install a missing version.
85+
// Returns true if the user wants to install, false otherwise.
86+
// Respects DTVEM_AUTO_INSTALL environment variable:
87+
// - "true": auto-install without prompting
88+
// - "false": never prompt, return false
89+
// - unset: prompt interactively
90+
func PromptInstall(displayName, version string) bool {
91+
// Check if running in non-interactive mode (CI/automation)
92+
if os.Getenv("DTVEM_AUTO_INSTALL") == "false" {
93+
return false
94+
}
95+
96+
// If DTVEM_AUTO_INSTALL=true, auto-install without prompting
97+
if os.Getenv("DTVEM_AUTO_INSTALL") == "true" {
98+
return true
99+
}
100+
101+
// Interactive prompt
102+
Printf("Install %s %s now? [Y/n]: ", displayName, version)
103+
104+
var response string
105+
_, _ = fmt.Scanln(&response)
106+
response = strings.ToLower(strings.TrimSpace(response))
107+
108+
// Default to "yes" if empty response
109+
return response == "" || response == "y" || response == "yes"
110+
}
111+
112+
// MissingRuntime represents a runtime that needs to be installed
113+
type MissingRuntime interface {
114+
DisplayName() string
115+
Version() string
116+
}
117+
118+
// PromptInstallMissing prompts the user to install multiple missing versions.
119+
// Returns true if the user wants to install, false otherwise.
120+
// Respects DTVEM_AUTO_INSTALL environment variable.
121+
func PromptInstallMissing[T any](missing []T) bool {
122+
if len(missing) == 0 {
123+
return false
124+
}
125+
126+
// Check if running in non-interactive mode (CI/automation)
127+
if os.Getenv("DTVEM_AUTO_INSTALL") == "false" {
128+
return false
129+
}
130+
131+
// If DTVEM_AUTO_INSTALL=true, auto-install without prompting
132+
if os.Getenv("DTVEM_AUTO_INSTALL") == "true" {
133+
return true
134+
}
135+
136+
// Interactive prompt
137+
Printf("Install missing version(s)? [Y/n]: ")
138+
139+
var response string
140+
_, _ = fmt.Scanln(&response)
141+
response = strings.ToLower(strings.TrimSpace(response))
142+
143+
// Default to "yes" if empty response
144+
return response == "" || response == "y" || response == "yes"
145+
}

0 commit comments

Comments
 (0)