diff --git a/src/env.rs b/src/env.rs index d99315226..f4f260a47 100644 --- a/src/env.rs +++ b/src/env.rs @@ -94,6 +94,7 @@ pub static MISE_GLOBAL_CONFIG_FILE: Lazy = Lazy::new(|| { .unwrap_or_else(|| MISE_CONFIG_DIR.join("config.toml")) }); pub static MISE_USE_TOML: Lazy = Lazy::new(|| var_is_true("MISE_USE_TOML")); +pub static MISE_LIST_ALL_VERSIONS: Lazy = Lazy::new(|| var_is_true("MISE_LIST_ALL_VERSIONS")); pub static ARGV0: Lazy = Lazy::new(|| ARGS.read().unwrap()[0].to_string()); pub static MISE_BIN_NAME: Lazy<&str> = Lazy::new(|| filename(&ARGV0)); pub static MISE_LOG_FILE: Lazy> = Lazy::new(|| var_path("MISE_LOG_FILE")); diff --git a/src/github.rs b/src/github.rs index 18c062139..07b84f12e 100644 --- a/src/github.rs +++ b/src/github.rs @@ -1,4 +1,7 @@ +use crate::env; +use reqwest::header::HeaderMap; use serde_derive::Deserialize; +use xx::regex; #[derive(Debug, Deserialize)] pub struct GithubRelease { @@ -12,7 +15,18 @@ pub struct GithubRelease { pub fn list_releases(repo: &str) -> eyre::Result> { let url = format!("https://api.github.com/repos/{}/releases", repo); - crate::http::HTTP_FETCH.json(url) + let (mut releases, mut headers) = + crate::http::HTTP_FETCH.json_headers::, _>(url)?; + + if *env::MISE_LIST_ALL_VERSIONS { + while let Some(next) = next_page(&headers) { + let (more, h) = crate::http::HTTP_FETCH.json_headers::, _>(next)?; + releases.extend(more); + headers = h; + } + } + + Ok(releases) } pub fn get_release(repo: &str, tag: &str) -> eyre::Result { @@ -22,3 +36,13 @@ pub fn get_release(repo: &str, tag: &str) -> eyre::Result { ); crate::http::HTTP_FETCH.json(url) } + +fn next_page(headers: &HeaderMap) -> Option { + let link = headers + .get("link") + .map(|l| l.to_str().unwrap_or_default().to_string()) + .unwrap_or_default(); + regex!(r#"<([^>]+)>; rel="next""#) + .captures(&link) + .map(|c| c.get(1).unwrap().as_str().to_string()) +} diff --git a/src/http.rs b/src/http.rs index db1a3a556..a8b908d0d 100644 --- a/src/http.rs +++ b/src/http.rs @@ -5,6 +5,7 @@ use std::time::Duration; use eyre::{bail, Report, Result}; use once_cell::sync::Lazy; +use reqwest::header::HeaderMap; use reqwest::{ClientBuilder, IntoUrl, Response}; use tokio::runtime::Runtime; use url::Url; @@ -98,17 +99,25 @@ impl Client { Ok(text) } - pub fn json(&self, url: U) -> Result + pub fn json_headers(&self, url: U) -> Result<(T, HeaderMap)> where T: serde::de::DeserializeOwned, { let url = url.into_url().unwrap(); let rt = self.runtime()?; - let json = rt.block_on(async { + let (json, headers) = rt.block_on(async { let resp = self.get(url).await?; - Ok::(resp.json().await?) + let headers = resp.headers().clone(); + Ok::<(T, HeaderMap), eyre::Error>((resp.json().await?, headers)) })?; - Ok(json) + Ok((json, headers)) + } + + pub fn json(&self, url: U) -> Result + where + T: serde::de::DeserializeOwned, + { + self.json_headers(url).map(|(json, _)| json) } pub fn download_file(