@@ -8,6 +8,7 @@ use directories::ProjectDirs;
8
8
use futures_util:: stream:: StreamExt ;
9
9
use once_cell:: sync:: Lazy ;
10
10
use regex:: Regex ;
11
+ use serde:: Deserialize ;
11
12
use std:: collections:: HashMap ;
12
13
use std:: path:: PathBuf ;
13
14
use tokio:: fs:: File ;
@@ -46,6 +47,57 @@ pub struct HttpClientOptions {
46
47
}
47
48
48
49
impl Application {
50
+ async fn fetch_if_latest ( & self , version : & str ) -> Result < String > {
51
+ Ok ( if version == "latest" {
52
+ match self {
53
+ Application :: WasmOpt => {
54
+ #[ derive( Deserialize ) ]
55
+ struct GitHubRelease {
56
+ tag_name : String ,
57
+ }
58
+ let url = "https://api.github.com/repos/WebAssembly/binaryen/releases/latest" ;
59
+ // let client = reqwest::blocking::Client::new();
60
+ let client = reqwest:: Client :: new ( ) ;
61
+
62
+ // github api requires a user agent
63
+ // https://docs.github.com/en/rest/using-the-rest-api/troubleshooting-the-rest-api?apiVersion=2022-11-28#user-agent-required
64
+ let req_builder = client
65
+ . get ( url)
66
+ . header ( "User-Agent" , "trunk-wasm-opt-checker" ) ;
67
+
68
+ // Send the request
69
+ let res = req_builder
70
+ . send ( )
71
+ . await
72
+ . context ( "Failed to send request to GitHub API" ) ?;
73
+
74
+ if !res. status ( ) . is_success ( ) {
75
+ // Get more details about the error
76
+ let status = res. status ( ) ;
77
+
78
+ let error_text = res
79
+ . text ( )
80
+ . await
81
+ . unwrap_or_else ( |_| "Could not read error response" . to_string ( ) ) ;
82
+
83
+ anyhow:: bail!(
84
+ "GitHub API request failed with status: {status}. Details: {error_text}"
85
+ ) ;
86
+ }
87
+
88
+ let release: GitHubRelease = res
89
+ . json ( )
90
+ . await
91
+ . context ( "Failed to parse GitHub API response" ) ?;
92
+ release. tag_name
93
+ }
94
+ _ => bail ! ( "version 'latest' is not supported for {}" , self . name( ) ) ,
95
+ }
96
+ } else {
97
+ version. to_string ( )
98
+ } )
99
+ }
100
+
49
101
/// Base name of the executable without extension.
50
102
pub ( crate ) fn name ( & self ) -> & str {
51
103
match self {
@@ -296,7 +348,7 @@ pub async fn get_info(
296
348
) -> Result < ToolInformation > {
297
349
tracing:: debug!( "Getting tool" ) ;
298
350
299
- if let Some ( ( path, detected_version) ) = find_system ( app) . await {
351
+ let download_version = if let Some ( ( path, detected_version) ) = find_system ( app) . await {
300
352
// consider system installed version
301
353
302
354
if let Some ( required_version) = version {
@@ -315,8 +367,17 @@ pub async fn get_info(
315
367
app. name( ) ,
316
368
)
317
369
} else {
318
- // a mismatch, so we need to download
319
- tracing:: debug!( "tool version mismatch (required: {required_version}, system: {detected_version})" ) ;
370
+ let required_version = app. fetch_if_latest ( required_version) . await ?;
371
+ if detected_version != required_version {
372
+ // a mismatch, so we need to download
373
+ tracing:: debug!( "tool version mismatch (required: {required_version}, system: {detected_version})" ) ;
374
+ required_version
375
+ } else {
376
+ return Ok ( ToolInformation {
377
+ path,
378
+ version : detected_version,
379
+ } ) ;
380
+ }
320
381
}
321
382
} else {
322
383
// we don't require any specific version
@@ -325,38 +386,43 @@ pub async fn get_info(
325
386
version : detected_version,
326
387
} ) ;
327
388
}
328
- }
329
-
330
- if offline {
389
+ } else if offline {
331
390
return Err ( anyhow ! (
332
391
"couldn't find application {name} (version: {version}), unable to download in offline mode" ,
333
392
name = & app. name( ) ,
334
393
version = version. unwrap_or( "<any>" )
335
394
) ) ;
336
- }
395
+ } else if let Some ( version) = version {
396
+ app. fetch_if_latest ( version) . await ?
397
+ } else {
398
+ tracing:: debug!(
399
+ "no version specified for {}, falling back to default" ,
400
+ app. name( )
401
+ ) ;
402
+ app. default_version ( ) . to_string ( )
403
+ } ;
337
404
338
405
let cache_dir = cache_dir ( ) . await ?;
339
- let version = version. unwrap_or_else ( || app. default_version ( ) ) ;
340
- let app_dir = cache_dir. join ( format ! ( "{}-{}" , app. name( ) , version) ) ;
406
+ let app_dir = cache_dir. join ( format ! ( "{}-{}" , app. name( ) , download_version) ) ;
341
407
let bin_path = app_dir. join ( app. path ( ) ) ;
342
408
343
409
if !is_executable ( & bin_path) . await ? {
344
410
GLOBAL_APP_CACHE
345
411
. lock ( )
346
412
. await
347
- . install_once ( app, version , app_dir, client_options)
413
+ . install_once ( app, & download_version , app_dir, client_options)
348
414
. await ?;
349
415
}
350
416
351
417
tracing:: debug!(
352
- "Using {} ({version }) from: {}" ,
418
+ "Using {} ({download_version }) from: {}" ,
353
419
app. name( ) ,
354
420
bin_path. display( )
355
421
) ;
356
422
357
423
Ok ( ToolInformation {
358
424
path : bin_path,
359
- version : version . to_owned ( ) ,
425
+ version : download_version ,
360
426
} )
361
427
}
362
428
0 commit comments