Skip to content

Commit 8dd7b10

Browse files
authored
feat(lifecycle): add Node.js lifecycle status to list-all command (#238)
1 parent f108878 commit 8dd7b10

12 files changed

Lines changed: 584 additions & 12 deletions

File tree

src/cmd/list.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,20 @@ func getVersionStatus(version, globalVersion, localVersion string) string {
160160
return status
161161
}
162162

163+
// getVersionStatusWithLifecycle returns a status string that combines the
164+
// global/local indicators with a color-coded lifecycle label (e.g., "Active LTS").
165+
func getVersionStatusWithLifecycle(version, globalVersion, localVersion, lifecycleStatus string) string {
166+
base := getVersionStatus(version, globalVersion, localVersion)
167+
colored := tui.RenderLifecycleStatus(lifecycleStatus)
168+
if colored == "" {
169+
return base
170+
}
171+
if base == "" {
172+
return colored
173+
}
174+
return base + " · " + colored
175+
}
176+
163177
// isVersionActive returns true if this version is the currently active one
164178
func isVersionActive(version, globalVersion, localVersion string) bool {
165179
isGlobal := version == globalVersion

src/cmd/list_test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package cmd
2+
3+
import (
4+
"testing"
5+
6+
"github.com/CodingWithCalvin/dtvem.cli/src/internal/tui"
7+
)
8+
9+
func TestGetVersionStatusWithLifecycle(t *testing.T) {
10+
tests := []struct {
11+
name string
12+
version string
13+
globalVersion string
14+
localVersion string
15+
lifecycleStatus string
16+
want string
17+
}{
18+
{
19+
name: "lifecycle only",
20+
version: "22.14.0",
21+
globalVersion: "",
22+
localVersion: "",
23+
lifecycleStatus: "Active LTS",
24+
want: tui.RenderLifecycleStatus("Active LTS"),
25+
},
26+
{
27+
name: "global only",
28+
version: "22.14.0",
29+
globalVersion: "22.14.0",
30+
localVersion: "",
31+
lifecycleStatus: "",
32+
want: globalIndicator + " global",
33+
},
34+
{
35+
name: "global with lifecycle",
36+
version: "22.14.0",
37+
globalVersion: "22.14.0",
38+
localVersion: "",
39+
lifecycleStatus: "Active LTS",
40+
want: globalIndicator + " global" + " · " + tui.RenderLifecycleStatus("Active LTS"),
41+
},
42+
{
43+
name: "local with lifecycle",
44+
version: "22.14.0",
45+
globalVersion: "",
46+
localVersion: "22.14.0",
47+
lifecycleStatus: "Maintenance LTS",
48+
want: localIndicator + " local" + " · " + tui.RenderLifecycleStatus("Maintenance LTS"),
49+
},
50+
{
51+
name: "no status at all",
52+
version: "22.14.0",
53+
globalVersion: "",
54+
localVersion: "",
55+
lifecycleStatus: "",
56+
want: "",
57+
},
58+
}
59+
60+
for _, tt := range tests {
61+
t.Run(tt.name, func(t *testing.T) {
62+
got := getVersionStatusWithLifecycle(tt.version, tt.globalVersion, tt.localVersion, tt.lifecycleStatus)
63+
if got != tt.want {
64+
t.Errorf("getVersionStatusWithLifecycle() = %q, want %q", got, tt.want)
65+
}
66+
})
67+
}
68+
}

src/cmd/listall.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ Examples:
9999
}
100100

101101
// Create table for this page
102-
table := tui.NewTable("", "Version", "Status", "Notes")
102+
table := tui.NewTable("", "Version", "Status")
103103
table.SetTitle(provider.DisplayName())
104104

105105
for i := 0; i < pageSize; i++ {
@@ -112,10 +112,10 @@ Examples:
112112
marker = tui.CheckMark
113113
}
114114

115-
// Get status (global/local indicators)
116-
status := getVersionStatus(version, globalVersion, localVersion)
115+
// Build status: combine global/local indicators with lifecycle
116+
status := getVersionStatusWithLifecycle(version, globalVersion, localVersion, v.LifecycleStatus)
117117

118-
table.AddRow(marker, version, status, v.Notes)
118+
table.AddRow(marker, version, status)
119119
}
120120

121121
fmt.Println()
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Package lifecycle provides types for runtime version lifecycle status.
2+
// Providers can implement StatusProvider to surface lifecycle information
3+
// (e.g., LTS, EOL) in version listings.
4+
package lifecycle
5+
6+
// Status represents a version's position in its runtime's release lifecycle.
7+
type Status string
8+
9+
const (
10+
// Current indicates an actively developed release line (typically the latest).
11+
Current Status = "Current"
12+
13+
// ActiveLTS indicates a release receiving active long-term support.
14+
ActiveLTS Status = "Active LTS"
15+
16+
// MaintenanceLTS indicates a release receiving only critical fixes.
17+
MaintenanceLTS Status = "Maintenance LTS"
18+
19+
// EOL indicates a release that has reached end of life.
20+
EOL Status = "EOL"
21+
)
22+
23+
// StatusProvider returns lifecycle status for a given version string.
24+
// Providers that track release schedules (e.g., Node.js) can implement
25+
// this interface so list-all displays lifecycle information.
26+
type StatusProvider interface {
27+
// VersionStatus returns the lifecycle status label for the given version,
28+
// or an empty string if the status is unknown.
29+
VersionStatus(version string) string
30+
}

src/internal/runtime/version.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,10 @@ func (iv InstalledVersion) String() string {
5353
// AvailableVersion represents a version available for installation
5454
type AvailableVersion struct {
5555
Version
56-
DownloadURL string
57-
Size int64
58-
Checksum string
59-
Notes string // Optional notes (e.g., "LTS", "Latest", "Stable")
56+
DownloadURL string
57+
Size int64
58+
Checksum string
59+
LifecycleStatus string // Optional lifecycle label (e.g., "Active LTS", "EOL")
6060
}
6161

6262
// DetectedVersion represents a runtime version found on the system

src/internal/tui/styles.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,3 +201,26 @@ func GetCrossMark() string {
201201
initStyles()
202202
return CrossMark
203203
}
204+
205+
// RenderLifecycleStatus renders a lifecycle status label with color coding.
206+
// Current → Cyan, Active LTS → Green, Maintenance LTS → Yellow, EOL → Red.
207+
// Unknown labels are returned unstyled.
208+
func RenderLifecycleStatus(status string) string {
209+
if status == "" {
210+
return ""
211+
}
212+
initStyles()
213+
214+
switch status {
215+
case "Current":
216+
return lipgloss.NewStyle().Foreground(colorPrimary).Render(status)
217+
case "Active LTS":
218+
return lipgloss.NewStyle().Foreground(colorSuccess).Bold(true).Render(status)
219+
case "Maintenance LTS":
220+
return lipgloss.NewStyle().Foreground(colorWarning).Render(status)
221+
case "EOL":
222+
return lipgloss.NewStyle().Foreground(colorError).Render(status)
223+
default:
224+
return status
225+
}
226+
}
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
{
2+
"v0.8": {
3+
"start": "2012-06-25",
4+
"end": "2014-07-31"
5+
},
6+
"v0.10": {
7+
"start": "2013-03-11",
8+
"end": "2016-10-31"
9+
},
10+
"v0.12": {
11+
"start": "2015-02-06",
12+
"end": "2016-12-31"
13+
},
14+
"v4": {
15+
"start": "2015-09-08",
16+
"lts": "2015-10-12",
17+
"maintenance": "2017-04-01",
18+
"end": "2018-04-30",
19+
"codename": "Argon"
20+
},
21+
"v5": {
22+
"start": "2015-10-29",
23+
"maintenance": "2016-04-30",
24+
"end": "2016-06-30"
25+
},
26+
"v6": {
27+
"start": "2016-04-26",
28+
"lts": "2016-10-18",
29+
"maintenance": "2018-04-30",
30+
"end": "2019-04-30",
31+
"codename": "Boron"
32+
},
33+
"v7": {
34+
"start": "2016-10-25",
35+
"maintenance": "2017-04-30",
36+
"end": "2017-06-30"
37+
},
38+
"v8": {
39+
"start": "2017-05-30",
40+
"lts": "2017-10-31",
41+
"maintenance": "2019-01-01",
42+
"end": "2019-12-31",
43+
"codename": "Carbon"
44+
},
45+
"v9": {
46+
"start": "2017-10-01",
47+
"maintenance": "2018-04-01",
48+
"end": "2018-06-30"
49+
},
50+
"v10": {
51+
"start": "2018-04-24",
52+
"lts": "2018-10-30",
53+
"maintenance": "2020-05-19",
54+
"end": "2021-04-30",
55+
"codename": "Dubnium"
56+
},
57+
"v11": {
58+
"start": "2018-10-23",
59+
"maintenance": "2019-04-22",
60+
"end": "2019-06-01"
61+
},
62+
"v12": {
63+
"start": "2019-04-23",
64+
"lts": "2019-10-21",
65+
"maintenance": "2020-11-30",
66+
"end": "2022-04-30",
67+
"codename": "Erbium"
68+
},
69+
"v13": {
70+
"start": "2019-10-22",
71+
"maintenance": "2020-04-01",
72+
"end": "2020-06-01"
73+
},
74+
"v14": {
75+
"start": "2020-04-21",
76+
"lts": "2020-10-27",
77+
"maintenance": "2021-10-19",
78+
"end": "2023-04-30",
79+
"codename": "Fermium"
80+
},
81+
"v15": {
82+
"start": "2020-10-20",
83+
"maintenance": "2021-04-01",
84+
"end": "2021-06-01"
85+
},
86+
"v16": {
87+
"start": "2021-04-20",
88+
"lts": "2021-10-26",
89+
"maintenance": "2022-10-18",
90+
"end": "2023-09-11",
91+
"codename": "Gallium"
92+
},
93+
"v17": {
94+
"start": "2021-10-19",
95+
"maintenance": "2022-04-01",
96+
"end": "2022-06-01"
97+
},
98+
"v18": {
99+
"start": "2022-04-19",
100+
"lts": "2022-10-25",
101+
"maintenance": "2023-10-18",
102+
"end": "2025-04-30",
103+
"codename": "Hydrogen"
104+
},
105+
"v19": {
106+
"start": "2022-10-18",
107+
"maintenance": "2023-04-01",
108+
"end": "2023-06-01"
109+
},
110+
"v20": {
111+
"start": "2023-04-18",
112+
"lts": "2023-10-24",
113+
"maintenance": "2024-10-22",
114+
"end": "2026-04-30",
115+
"codename": "Iron"
116+
},
117+
"v21": {
118+
"start": "2023-10-17",
119+
"maintenance": "2024-04-01",
120+
"end": "2024-06-01"
121+
},
122+
"v22": {
123+
"start": "2024-04-24",
124+
"lts": "2024-10-29",
125+
"maintenance": "2025-10-21",
126+
"end": "2027-04-30",
127+
"codename": "Jod"
128+
},
129+
"v23": {
130+
"start": "2024-10-16",
131+
"maintenance": "2025-04-01",
132+
"end": "2025-06-01"
133+
},
134+
"v24": {
135+
"start": "2025-05-06",
136+
"lts": "2025-10-28",
137+
"maintenance": "2026-10-20",
138+
"end": "2028-04-30",
139+
"codename": "Krypton"
140+
},
141+
"v25": {
142+
"start": "2025-10-15",
143+
"maintenance": "2026-04-01",
144+
"end": "2026-06-01"
145+
},
146+
"v26": {
147+
"start": "2026-04-22",
148+
"lts": "2026-10-28",
149+
"maintenance": "2027-10-20",
150+
"end": "2029-04-30",
151+
"codename": ""
152+
}
153+
}

0 commit comments

Comments
 (0)