@@ -8,6 +8,7 @@ use directories::ProjectDirs;
88use futures_util:: stream:: StreamExt ;
99use once_cell:: sync:: Lazy ;
1010use regex:: Regex ;
11+ use serde:: Deserialize ;
1112use std:: collections:: HashMap ;
1213use std:: path:: PathBuf ;
1314use tokio:: fs:: File ;
@@ -46,6 +47,57 @@ pub struct HttpClientOptions {
4647}
4748
4849impl 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+
49101 /// Base name of the executable without extension.
50102 pub ( crate ) fn name ( & self ) -> & str {
51103 match self {
@@ -296,7 +348,7 @@ pub async fn get_info(
296348) -> Result < ToolInformation > {
297349 tracing:: debug!( "Getting tool" ) ;
298350
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 {
300352 // consider system installed version
301353
302354 if let Some ( required_version) = version {
@@ -315,8 +367,17 @@ pub async fn get_info(
315367 app. name( ) ,
316368 )
317369 } 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+ }
320381 }
321382 } else {
322383 // we don't require any specific version
@@ -325,38 +386,43 @@ pub async fn get_info(
325386 version : detected_version,
326387 } ) ;
327388 }
328- }
329-
330- if offline {
389+ } else if offline {
331390 return Err ( anyhow ! (
332391 "couldn't find application {name} (version: {version}), unable to download in offline mode" ,
333392 name = & app. name( ) ,
334393 version = version. unwrap_or( "<any>" )
335394 ) ) ;
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+ } ;
337404
338405 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) ) ;
341407 let bin_path = app_dir. join ( app. path ( ) ) ;
342408
343409 if !is_executable ( & bin_path) . await ? {
344410 GLOBAL_APP_CACHE
345411 . lock ( )
346412 . await
347- . install_once ( app, version , app_dir, client_options)
413+ . install_once ( app, & download_version , app_dir, client_options)
348414 . await ?;
349415 }
350416
351417 tracing:: debug!(
352- "Using {} ({version }) from: {}" ,
418+ "Using {} ({download_version }) from: {}" ,
353419 app. name( ) ,
354420 bin_path. display( )
355421 ) ;
356422
357423 Ok ( ToolInformation {
358424 path : bin_path,
359- version : version . to_owned ( ) ,
425+ version : download_version ,
360426 } )
361427}
362428
0 commit comments