33namespace Native \Electron \Commands \Bifrost ;
44
55use Carbon \CarbonInterface ;
6+ use Exception ;
67use Illuminate \Console \Command ;
78use Illuminate \Support \Facades \Http ;
89use Native \Electron \Traits \HandlesBifrost ;
910use Symfony \Component \Console \Attribute \AsCommand ;
1011
1112use function Laravel \Prompts \intro ;
12- use function Laravel \Prompts \progress ;
1313
1414#[AsCommand(
1515 name: 'bifrost:download-bundle ' ,
@@ -23,84 +23,136 @@ class DownloadBundleCommand extends Command
2323
2424 public function handle (): int
2525 {
26- if (! $ this ->checkForBifrostToken ()) {
26+ try {
27+ $ this ->validateAuthAndGetUser ();
28+ } catch (Exception $ e ) {
29+ $ this ->error ($ e ->getMessage ());
30+ $ this ->line ('Run: php artisan bifrost:login ' );
31+
2732 return static ::FAILURE ;
2833 }
2934
3035 if (! $ this ->checkForBifrostProject ()) {
3136 return static ::FAILURE ;
3237 }
3338
34- if (! $ this ->checkAuthenticated ()) {
35- $ this ->error ('Invalid API token. Please login again. ' );
36- $ this ->line ('Run: php artisan bifrost:login ' );
39+ intro ('Fetching latest desktop bundle... ' );
3740
38- return static ::FAILURE ;
39- }
41+ try {
42+ $ projectId = config ('nativephp-internal.bifrost.project ' );
43+ $ response = $ this ->makeApiRequest ('GET ' , "api/v1/projects/ {$ projectId }/builds/latest-desktop-bundle " );
4044
41- intro ('Fetching latest desktop bundle... ' );
45+ if ($ response ->failed ()) {
46+ $ this ->handleApiError ($ response );
47+
48+ return static ::FAILURE ;
49+ }
50+
51+ $ buildData = $ response ->json ();
4252
43- $ projectId = config ('nativephp-internal.bifrost.project ' );
44- $ response = Http::acceptJson ()
45- ->withToken (config ('nativephp-internal.bifrost.token ' ))
46- ->get ($ this ->baseUrl ()."api/v1/projects/ {$ projectId }/builds/latest-desktop-bundle " );
53+ if (! isset ($ buildData ['download_url ' ])) {
54+ $ this ->error ('Bundle download URL not found in response. ' );
4755
48- if ($ response ->failed ()) {
49- $ this ->handleApiError ($ response );
56+ return static ::FAILURE ;
57+ }
58+
59+ $ this ->displayBundleInfo ($ buildData );
60+
61+ $ bundlePath = $ this ->prepareBundlePath ();
62+
63+ if (! $ this ->downloadBundle ($ buildData ['download_url ' ], $ bundlePath )) {
64+ return static ::FAILURE ;
65+ }
66+
67+ $ this ->displaySuccessInfo ($ bundlePath );
68+
69+ return static ::SUCCESS ;
70+ } catch (Exception $ e ) {
71+ $ this ->error ('Failed to download bundle: ' .$ e ->getMessage ());
5072
5173 return static ::FAILURE ;
5274 }
75+ }
5376
54- $ buildData = $ response ->json ();
55- $ downloadUrl = $ buildData ['download_url ' ];
56-
77+ private function displayBundleInfo (array $ buildData ): void
78+ {
5779 $ this ->line ('' );
5880 $ this ->info ('Bundle Details: ' );
59- $ this ->line ('Version: ' .$ buildData ['version ' ]);
60- $ this ->line ('Git Commit: ' .substr ($ buildData ['git_commit ' ], 0 , 8 ));
61- $ this ->line ('Git Branch: ' .$ buildData ['git_branch ' ]);
62- $ this ->line ('Created: ' .$ buildData ['created_at ' ]);
81+ $ this ->line ('Version: ' .($ buildData ['version ' ] ?? 'Unknown ' ));
82+ $ this ->line ('Git Commit: ' .substr ($ buildData ['git_commit ' ] ?? '' , 0 , 8 ));
83+ $ this ->line ('Git Branch: ' .($ buildData ['git_branch ' ] ?? 'Unknown ' ));
84+ $ this ->line ('Created: ' .($ buildData ['created_at ' ] ?? 'Unknown ' ));
85+ }
6386
64- // Create build directory if it doesn't exist
87+ private function prepareBundlePath (): string
88+ {
6589 $ buildDir = base_path ('build ' );
6690 if (! is_dir ($ buildDir )) {
6791 mkdir ($ buildDir , 0755 , true );
6892 }
6993
70- $ bundlePath = base_path ('build/__nativephp_app_bundle ' );
94+ return base_path ('build/__nativephp_app_bundle ' );
95+ }
7196
72- // Download the bundle with progress bar
97+ private function downloadBundle (string $ downloadUrl , string $ bundlePath ): bool
98+ {
7399 $ this ->line ('' );
74100 $ this ->info ('Downloading bundle... ' );
75101
76- $ downloadResponse = Http::withOptions ([
77- 'sink ' => $ bundlePath ,
78- 'progress ' => function ($ downloadTotal , $ downloadedBytes ) {
79- if ($ downloadTotal > 0 ) {
80- $ progress = ($ downloadedBytes / $ downloadTotal ) * 100 ;
81- $ this ->output ->write ("\r" .sprintf ('Progress: %.1f%% ' , $ progress ));
82- }
83- },
84- ])->get ($ downloadUrl );
85-
86- if ($ downloadResponse ->failed ()) {
102+ $ progressBar = $ this ->output ->createProgressBar ();
103+ $ progressBar ->setFormat (' %current%/%max% [%bar%] %percent:3s%% %message% ' );
104+
105+ try {
106+ $ downloadResponse = Http::withOptions ([
107+ 'sink ' => $ bundlePath ,
108+ 'progress ' => function ($ downloadTotal , $ downloadedBytes ) use ($ progressBar ) {
109+ if ($ downloadTotal > 0 ) {
110+ $ progressBar ->setMaxSteps ($ downloadTotal );
111+ $ progressBar ->setProgress ($ downloadedBytes );
112+ $ progressBar ->setMessage (sprintf ('%.1f MB ' , $ downloadedBytes / 1024 / 1024 ));
113+ }
114+ },
115+ ])->get ($ downloadUrl );
116+
117+ $ progressBar ->finish ();
87118 $ this ->line ('' );
88- $ this ->error ('Failed to download bundle. ' );
89119
90- if (file_exists ($ bundlePath )) {
91- unlink ($ bundlePath );
120+ if ($ downloadResponse ->failed ()) {
121+ $ this ->error ('Failed to download bundle. ' );
122+ $ this ->cleanupFailedDownload ($ bundlePath );
123+
124+ return false ;
92125 }
93126
94- return static ::FAILURE ;
127+ return true ;
128+ } catch (Exception $ e ) {
129+ $ progressBar ->finish ();
130+ $ this ->line ('' );
131+ $ this ->error ('Download failed: ' .$ e ->getMessage ());
132+ $ this ->cleanupFailedDownload ($ bundlePath );
133+
134+ return false ;
95135 }
136+ }
96137
97- $ this ->line ('' );
138+ private function cleanupFailedDownload (string $ bundlePath ): void
139+ {
140+ if (file_exists ($ bundlePath )) {
141+ unlink ($ bundlePath );
142+ $ this ->line ('Cleaned up partial download. ' );
143+ }
144+ }
145+
146+ private function displaySuccessInfo (string $ bundlePath ): void
147+ {
98148 $ this ->line ('' );
99149 $ this ->info ('Bundle downloaded successfully! ' );
100150 $ this ->line ('Location: ' .$ bundlePath );
101- $ this ->line ('Size: ' .number_format (filesize ($ bundlePath ) / 1024 / 1024 , 2 ).' MB ' );
102151
103- return static ::SUCCESS ;
152+ if (file_exists ($ bundlePath )) {
153+ $ sizeInMB = number_format (filesize ($ bundlePath ) / 1024 / 1024 , 2 );
154+ $ this ->line ("Size: {$ sizeInMB } MB " );
155+ }
104156 }
105157
106158 private function handleApiError ($ response ): void
@@ -113,7 +165,15 @@ private function handleApiError($response): void
113165 $ this ->line ('' );
114166 $ this ->error ('No desktop builds found for this project. ' );
115167 $ this ->line ('' );
116- $ this ->info ('Create a build at: ' .$ this ->baseUrl ().'{team}/desktop/projects/{project} ' );
168+ $ teamSlug = $ this ->getCurrentTeamSlug ();
169+ $ projectId = config ('nativephp-internal.bifrost.project ' );
170+ $ baseUrl = rtrim ($ this ->baseUrl (), '/ ' );
171+
172+ if ($ teamSlug && $ projectId ) {
173+ $ this ->info ("Create a build at: {$ baseUrl }/ {$ teamSlug }/desktop/projects/ {$ projectId }" );
174+ } else {
175+ $ this ->info ("Visit the dashboard: {$ baseUrl }/dashboard " );
176+ }
117177 break ;
118178
119179 case 503 :
@@ -122,14 +182,16 @@ private function handleApiError($response): void
122182 $ diffMessage = $ retryAfter <= 60 ? 'a minute ' : $ diff ->diffForHumans (syntax: CarbonInterface::DIFF_ABSOLUTE );
123183 $ this ->line ('' );
124184 $ this ->warn ('Build is still in progress. ' );
125- $ this ->line (' Please try again in ' . $ diffMessage. ' . ' );
185+ $ this ->line (" Please try again in { $ diffMessage} . " );
126186 break ;
127187
128188 case 500 :
129189 $ this ->line ('' );
130190 $ this ->error ('Latest build has failed or was cancelled. ' );
131191 if (isset ($ data ['build_id ' ])) {
132192 $ this ->line ('Build ID: ' .$ data ['build_id ' ]);
193+ }
194+ if (isset ($ data ['status ' ])) {
133195 $ this ->line ('Status: ' .$ data ['status ' ]);
134196 }
135197 break ;
0 commit comments