11package cmd
22
33import (
4- "encoding/json"
54 "fmt"
6- "io"
7- "net/http"
85 "regexp"
96 "strings"
107
8+ "github.com/stacktodate/stacktodate-cli/cmd/lib/cache"
119 "github.com/stacktodate/stacktodate-cli/cmd/lib/detectors"
1210)
1311
@@ -23,14 +21,6 @@ type DetectedInfo struct {
2321 Docker []detectors.Candidate
2422}
2523
26- type EOLProduct struct {
27- Cycle string `json:"cycle"`
28- Release string `json:"release"`
29- LTS interface {} `json:"lts"` // Can be bool or string (date)
30- Support interface {} `json:"support"` // Can be bool or string (date)
31- EOL interface {} `json:"eol"` // Can be bool or string (date)
32- }
33-
3424// cleanVersion removes version operators and extracts the core version
3525// Examples: ~> 7.1.0 -> 7.1.0, >= 18.0.0 -> 18.0.0, <= 3.11 -> 3.11
3626func cleanVersion (version string ) string {
@@ -64,7 +54,7 @@ func cleanCandidateVersions(candidates []detectors.Candidate) []detectors.Candid
6454 return candidates
6555}
6656
67- // truncateCandidateVersions truncates all candidate versions to match endoflife.date API cycles
57+ // truncateCandidateVersions truncates all candidate versions to match stacktodate.club API cycles
6858func truncateCandidateVersions (candidates []detectors.Candidate , product string ) []detectors.Candidate {
6959 for i := range candidates {
7060 candidates [i ].Value = truncateVersionToEOLCycle (product , candidates [i ].Value )
@@ -145,7 +135,7 @@ func DetectProjectInfo() DetectedInfo {
145135 }
146136 }
147137
148- // Truncate versions to match endoflife.date API cycles
138+ // Truncate versions to match stacktodate.club API cycles
149139 info .Ruby = truncateCandidateVersions (info .Ruby , "ruby" )
150140 info .Rails = truncateCandidateVersions (info .Rails , "rails" )
151141 info .Node = truncateCandidateVersions (info .Node , "nodejs" )
@@ -155,39 +145,49 @@ func DetectProjectInfo() DetectedInfo {
155145 return info
156146}
157147
158- // truncateVersionToEOLCycle truncates a version to match the format used by endoflife.date API
159- // It tries to find the best matching cycle by progressively truncating the version
160- // Examples: 3.11.0 -> 3.11, 18.0.0 -> 18, 7.1.0 -> 7.1
161- func truncateVersionToEOLCycle (product , version string ) string {
162- if product == "" || version == "" {
163- return version
148+ // mapProductNameToCacheKey maps internal product names to stacktodate.club API keys
149+ func mapProductNameToCacheKey (product string ) string {
150+ mapping := map [string ]string {
151+ "ruby" : "ruby" ,
152+ "rails" : "rails" ,
153+ "nodejs" : "nodejs" ,
154+ "go" : "go" ,
155+ "python" : "python" ,
164156 }
165157
166- url := fmt .Sprintf ("https://endoflife.date/api/%s.json" , product )
167- resp , err := http .Get (url )
168- if err != nil {
169- return version
158+ if key , exists := mapping [product ]; exists {
159+ return key
170160 }
171- defer resp .Body .Close ()
161+ return product
162+ }
172163
173- if resp .StatusCode != http .StatusOK {
164+ // truncateVersionToEOLCycle truncates a version to match the format used by stacktodate.club API
165+ // It tries to find the best matching cycle by progressively truncating the version
166+ // Examples: 3.11.0 -> 3.11, 18.0.0 -> 18, 7.1.0 -> 7.1
167+ func truncateVersionToEOLCycle (product , version string ) string {
168+ if product == "" || version == "" {
174169 return version
175170 }
176171
177- body , err := io .ReadAll (resp .Body )
172+ // Get products from cache (auto-fetches if needed or stale)
173+ products , err := cache .GetProducts ()
178174 if err != nil {
175+ // Graceful fallback: return original version if cache fetch fails
179176 return version
180177 }
181178
182- var products []EOLProduct
183- if err := json .Unmarshal (body , & products ); err != nil {
179+ // Map product name to cache key
180+ cacheKey := mapProductNameToCacheKey (product )
181+ cachedProduct := cache .GetProductByKey (cacheKey , products )
182+ if cachedProduct == nil {
183+ // Product not found in cache, return original version
184184 return version
185185 }
186186
187187 // Build a set of available cycles for quick lookup
188188 cycles := make (map [string ]bool )
189- for _ , p := range products {
190- cycles [p . Cycle ] = true
189+ for _ , release := range cachedProduct . Releases {
190+ cycles [release . ReleaseCycle ] = true
191191 }
192192
193193 // If exact match exists, return as-is
@@ -197,15 +197,15 @@ func truncateVersionToEOLCycle(product, version string) string {
197197
198198 // Split version into parts
199199 parts := strings .Split (version , "." )
200-
200+
201201 // Try major.minor (e.g., 3.11 from 3.11.0)
202202 if len (parts ) >= 2 {
203203 majorMinor := parts [0 ] + "." + parts [1 ]
204204 if cycles [majorMinor ] {
205205 return majorMinor
206206 }
207207 }
208-
208+
209209 // Try major only (e.g., 18 from 18.0.0)
210210 if len (parts ) >= 1 {
211211 major := parts [0 ]
@@ -223,40 +223,30 @@ func getEOLStatus(product, version string) string {
223223 return ""
224224 }
225225
226- url := fmt .Sprintf ("https://endoflife.date/api/%s.json" , product )
227- resp , err := http .Get (url )
228- if err != nil {
229- return ""
230- }
231- defer resp .Body .Close ()
232-
233- if resp .StatusCode != http .StatusOK {
234- return ""
235- }
236-
237- body , err := io .ReadAll (resp .Body )
226+ // Get products from cache (auto-fetches if needed or stale)
227+ products , err := cache .GetProducts ()
238228 if err != nil {
229+ // Graceful fallback: return empty string if cache fetch fails
239230 return ""
240231 }
241232
242- var products []EOLProduct
243- if err := json .Unmarshal (body , & products ); err != nil {
233+ // Map product name to cache key
234+ cacheKey := mapProductNameToCacheKey (product )
235+ cachedProduct := cache .GetProductByKey (cacheKey , products )
236+ if cachedProduct == nil {
237+ // Product not found in cache, return empty string
244238 return ""
245239 }
246240
247- // Find the matching cycle
248- for _ , p := range products {
249- if p . Cycle == version {
250- // Check if EOL is false (bool) or empty
251- if eolBool , ok := p .EOL .( bool ); ok && ! eolBool {
241+ // Find the matching release cycle
242+ for _ , release := range cachedProduct . Releases {
243+ if release . ReleaseCycle == version {
244+ // Check if EOL is empty (still supported)
245+ if release .EOL == "" {
252246 return " (supported)"
253247 }
254- if eolStr , ok := p .EOL .(string ); ok {
255- if eolStr == "" || eolStr == "false" {
256- return " (supported)"
257- }
258- return fmt .Sprintf (" (EOL: %s)" , eolStr )
259- }
248+ // Return EOL date
249+ return fmt .Sprintf (" (EOL: %s)" , release .EOL )
260250 }
261251 }
262252
0 commit comments