@@ -3,8 +3,10 @@ package commands
33import (
44 "crypto/sha256"
55 "encoding/hex"
6+ "encoding/json"
67 "fmt"
78 "io"
9+ "net/http"
810 "os"
911 "os/exec"
1012 "path/filepath"
@@ -66,11 +68,6 @@ func runUpgrade(cmd *cobra.Command, opts *upgradeOptions) error {
6668 installDir = filepath .Dir (exe )
6769 }
6870
69- // Ensure gh is available and authenticated.
70- if err := requireGh (); err != nil {
71- return err
72- }
73-
7471 osName , archName , err := detectPlatform ()
7572 if err != nil {
7673 return err
@@ -79,6 +76,15 @@ func runUpgrade(cmd *cobra.Command, opts *upgradeOptions) error {
7976 asset := fmt .Sprintf ("%s_%s_%s" , upgradeBinary , osName , archName )
8077 tag := opts .tag
8178
79+ // "latest" is not a real tag — resolve it to the actual latest release tag.
80+ if tag == "latest" {
81+ resolved , err := resolveLatestTag ()
82+ if err != nil {
83+ return fmt .Errorf ("resolve latest release: %w" , err )
84+ }
85+ tag = resolved
86+ }
87+
8288 fmt .Fprintf (w , "Downloading %s from %s@%s...\n " , asset , upgradeRepo , tag )
8389
8490 tmpDir , err := os .MkdirTemp ("" , "wherobots-upgrade-*" )
@@ -87,15 +93,15 @@ func runUpgrade(cmd *cobra.Command, opts *upgradeOptions) error {
8793 }
8894 defer os .RemoveAll (tmpDir )
8995
90- if err := ghDownload (tag , asset , tmpDir ); err != nil {
96+ if err := httpDownload (tag , asset , tmpDir ); err != nil {
9197 return fmt .Errorf ("download asset: %w" , err )
9298 }
9399
94100 assetPath := filepath .Join (tmpDir , asset )
95101
96102 if ! opts .skipChecksum {
97103 fmt .Fprintln (w , "Verifying checksum..." )
98- if err := ghDownload (tag , "checksums.txt" , tmpDir ); err != nil {
104+ if err := httpDownload (tag , "checksums.txt" , tmpDir ); err != nil {
99105 return fmt .Errorf ("download checksums: %w" , err )
100106 }
101107 if err := verifyChecksum (assetPath , filepath .Join (tmpDir , "checksums.txt" ), asset ); err != nil {
@@ -113,17 +119,6 @@ func runUpgrade(cmd *cobra.Command, opts *upgradeOptions) error {
113119 return nil
114120}
115121
116- // requireGh checks that the gh CLI is installed and authenticated.
117- func requireGh () error {
118- if _ , err := exec .LookPath ("gh" ); err != nil {
119- return fmt .Errorf ("gh CLI is required; install from https://cli.github.com/" )
120- }
121- if err := exec .Command ("gh" , "auth" , "status" ).Run (); err != nil {
122- return fmt .Errorf ("gh is not authenticated; run: gh auth login" )
123- }
124- return nil
125- }
126-
127122func detectPlatform () (string , string , error ) {
128123 var osName string
129124 switch runtime .GOOS {
@@ -150,15 +145,66 @@ func detectPlatform() (string, string, error) {
150145 return osName , archName , nil
151146}
152147
153- func ghDownload (tag , pattern , dir string ) error {
154- out , err := exec .Command ("gh" , "release" , "download" , tag ,
155- "--repo" , upgradeRepo ,
156- "--pattern" , pattern ,
157- "--dir" , dir ,
158- "--clobber" ,
159- ).CombinedOutput ()
148+ // resolveLatestTag queries the GitHub API for the latest release tag.
149+ func resolveLatestTag () (string , error ) {
150+ url := fmt .Sprintf ("https://api.github.com/repos/%s/releases/latest" , upgradeRepo )
151+ req , err := http .NewRequest (http .MethodGet , url , nil )
152+ if err != nil {
153+ return "" , err
154+ }
155+ req .Header .Set ("User-Agent" , "wherobots-cli" )
156+
157+ resp , err := http .DefaultClient .Do (req )
160158 if err != nil {
161- return fmt .Errorf ("%s: %s" , err , strings .TrimSpace (string (out )))
159+ return "" , fmt .Errorf ("GitHub API request failed: %w" , err )
160+ }
161+ defer resp .Body .Close ()
162+
163+ if resp .StatusCode != http .StatusOK {
164+ return "" , fmt .Errorf ("no release found in %s (HTTP %d)" , upgradeRepo , resp .StatusCode )
165+ }
166+
167+ var release struct {
168+ TagName string `json:"tag_name"`
169+ }
170+ if err := json .NewDecoder (resp .Body ).Decode (& release ); err != nil {
171+ return "" , fmt .Errorf ("failed to parse GitHub API response: %w" , err )
172+ }
173+ tag := strings .TrimSpace (release .TagName )
174+ if tag == "" {
175+ return "" , fmt .Errorf ("no release found in %s" , upgradeRepo )
176+ }
177+ return tag , nil
178+ }
179+
180+ // httpDownload downloads a release asset from GitHub to the given directory.
181+ func httpDownload (tag , filename , dir string ) error {
182+ url := fmt .Sprintf ("https://github.com/%s/releases/download/%s/%s" , upgradeRepo , tag , filename )
183+ req , err := http .NewRequest (http .MethodGet , url , nil )
184+ if err != nil {
185+ return err
186+ }
187+ req .Header .Set ("User-Agent" , "wherobots-cli" )
188+
189+ resp , err := http .DefaultClient .Do (req )
190+ if err != nil {
191+ return fmt .Errorf ("download request failed: %w" , err )
192+ }
193+ defer resp .Body .Close ()
194+
195+ if resp .StatusCode != http .StatusOK {
196+ return fmt .Errorf ("failed to download %s (HTTP %d)" , filename , resp .StatusCode )
197+ }
198+
199+ outPath := filepath .Join (dir , filename )
200+ f , err := os .Create (outPath )
201+ if err != nil {
202+ return fmt .Errorf ("create file %s: %w" , outPath , err )
203+ }
204+ defer f .Close ()
205+
206+ if _ , err := io .Copy (f , resp .Body ); err != nil {
207+ return fmt .Errorf ("write file %s: %w" , outPath , err )
162208 }
163209 return nil
164210}
0 commit comments