From ca712910209ac79b2491faf11ab8d32659984239 Mon Sep 17 00:00:00 2001 From: nzhl Date: Sat, 19 Nov 2022 04:08:47 +0800 Subject: [PATCH] feat: support `fnm install --latest` (#859) * feat: support to install the latest version * chore: test & fmt & clippy * docs: update command docs * docs: update command docs * Create warm-rice-appear.md * Update change set to be a minor release As this is a new feature * chore: update workflow to install pnpm * Remove `feat:` from changeset Because GitHub actions approval button is missing * run pnpm like other tasks * Revert "run pnpm like other tasks" This reverts commit f5f2ca29f17a3440d576085ec34d4644d56a48e4. we can revert the yarn changes and do it in a different PR * revert the changes in .github/workflows Co-authored-by: Gal Schlezinger --- .changeset/warm-rice-appear.md | 5 +++ .ci/prepare-version.js | 2 +- .ci/print-command-docs.js | 4 +-- docs/commands.md | 3 ++ src/commands/install.rs | 63 +++++++++++++++++++++++++++++++--- src/user_version.rs | 2 +- src/version.rs | 6 ++-- 7 files changed, 74 insertions(+), 11 deletions(-) create mode 100644 .changeset/warm-rice-appear.md diff --git a/.changeset/warm-rice-appear.md b/.changeset/warm-rice-appear.md new file mode 100644 index 0000000..575462a --- /dev/null +++ b/.changeset/warm-rice-appear.md @@ -0,0 +1,5 @@ +--- +"fnm": minor +--- + +support `fnm install --latest` to install the latest Node.js version diff --git a/.ci/prepare-version.js b/.ci/prepare-version.js index 5d6ddb2..f63ac36 100755 --- a/.ci/prepare-version.js +++ b/.ci/prepare-version.js @@ -17,7 +17,7 @@ const command = cmd.command({ async handler({}) { updateCargoToml(await getPackageVersion()) exec("cargo build --release") - exec("yarn generate-command-docs --binary-path=./target/release/fnm") + exec("pnpm generate-command-docs --binary-path=./target/release/fnm") exec("./.ci/record_screen.sh") }, }) diff --git a/.ci/print-command-docs.js b/.ci/print-command-docs.js index 7b5996a..df61c6f 100755 --- a/.ci/print-command-docs.js +++ b/.ci/print-command-docs.js @@ -42,7 +42,7 @@ const command = cmd.command({ if (gitStatus.state === "dirty") { process.exitCode = 1 console.error( - "The file has changed. Please re-run `yarn generate-command-docs`." + "The file has changed. Please re-run `pnpm generate-command-docs`." ) console.error(`hint: The following diff was found:`) console.error() @@ -76,7 +76,7 @@ async function main(targetFile, fnmPath) { stream.close() - await execa(`yarn`, ["prettier", "--write", targetFile]) + await execa(`pnpm`, ["prettier", "--write", targetFile]) } /** diff --git a/docs/commands.md b/docs/commands.md index 211ecce..4e1644a 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -458,6 +458,9 @@ OPTIONS: -h, --help Print help information + --latest + Install latest version + --log-level The log level of fnm commands diff --git a/src/commands/install.rs b/src/commands/install.rs index 7b37d1c..ee22ebf 100644 --- a/src/commands/install.rs +++ b/src/commands/install.rs @@ -18,25 +18,33 @@ pub struct Install { pub version: Option, /// Install latest LTS - #[clap(long, conflicts_with = "version")] + #[clap(long, conflicts_with_all = &["version", "latest"])] pub lts: bool, + + /// Install latest version + #[clap(long, conflicts_with_all = &["version", "lts"])] + pub latest: bool, } impl Install { fn version(self) -> Result, Error> { match self { - Self { - version: Some(_), - lts: true, - } => Err(Error::TooManyVersionsProvided), Self { version: v, lts: false, + latest: false, } => Ok(v), Self { version: None, lts: true, + latest: false, } => Ok(Some(UserVersion::Full(Version::Lts(LtsType::Latest)))), + Self { + version: None, + lts: false, + latest: true, + } => Ok(Some(UserVersion::Full(Version::Latest))), + _ => Err(Error::TooManyVersionsProvided), } } } @@ -74,6 +82,21 @@ impl super::command::Command for Install { ); picked_version } + UserVersion::Full(Version::Latest) => { + let available_versions: Vec<_> = remote_node_index::list(&config.node_dist_mirror) + .map_err(|source| Error::CantListRemoteVersions { source })?; + let picked_version = available_versions + .last() + .ok_or(Error::CantFindLatest)? + .version + .clone(); + debug!( + "Resolved {} into Node version {}", + Version::Latest.v_str().cyan(), + picked_version.v_str().cyan() + ); + picked_version + } current_version => { let available_versions: Vec<_> = remote_node_index::list(&config.node_dist_mirror) .map_err(|source| Error::CantListRemoteVersions { source })? @@ -153,6 +176,8 @@ pub enum Error { CantFindNodeVersion { requested_version: UserVersion }, #[error("Can't find relevant LTS named {}", lts_type)] CantFindRelevantLts { lts_type: crate::lts::LtsType }, + #[error("Can't find any versions in the upstream version index.")] + CantFindLatest, #[error("The requested version is not installable: {}", version.v_str())] UninstallableVersion { version: Version }, #[error("Too many versions provided. Please don't use --lts with a version string.")] @@ -175,6 +200,7 @@ mod tests { Install { version: UserVersion::from_str("12.0.0").ok(), lts: false, + latest: false, } .apply(&config) .expect("Can't install"); @@ -190,4 +216,31 @@ mod tests { .ok() ); } + + #[test] + fn test_install_latest() { + let base_dir = tempfile::tempdir().unwrap(); + let config = FnmConfig::default().with_base_dir(Some(base_dir.path().to_path_buf())); + + Install { + version: None, + lts: false, + latest: true, + } + .apply(&config) + .expect("Can't install"); + + let available_versions: Vec<_> = + remote_node_index::list(&config.node_dist_mirror).expect("Can't get node version list"); + let latest_version = available_versions.last().unwrap().version.clone(); + + assert!(config.installations_dir().exists()); + assert!(config + .installations_dir() + .join(latest_version.to_string()) + .join("installation") + .canonicalize() + .unwrap() + .exists()); + } } diff --git a/src/user_version.rs b/src/user_version.rs index 1e08618..bf1897e 100644 --- a/src/user_version.rs +++ b/src/user_version.rs @@ -41,7 +41,7 @@ impl UserVersion { } } } - (_, Version::Bypassed | Version::Lts(_) | Version::Alias(_)) => false, + (_, Version::Bypassed | Version::Lts(_) | Version::Alias(_) | Version::Latest) => false, (Self::OnlyMajor(major), Version::Semver(other)) => *major == other.major, (Self::MajorMinor(major, minor), Version::Semver(other)) => { *major == other.major && *minor == other.minor diff --git a/src/version.rs b/src/version.rs index b6c4a13..89d792f 100644 --- a/src/version.rs +++ b/src/version.rs @@ -9,6 +9,7 @@ pub enum Version { Semver(semver::Version), Lts(LtsType), Alias(String), + Latest, Bypassed, } @@ -58,7 +59,7 @@ impl Version { pub fn installation_path(&self, config: &config::FnmConfig) -> std::path::PathBuf { match self { Self::Bypassed => system_version::path(), - v @ (Self::Lts(_) | Self::Alias(_)) => { + v @ (Self::Lts(_) | Self::Alias(_) | Self::Latest) => { config.aliases_dir().join(v.alias_name().unwrap()) } v @ Self::Semver(_) => config @@ -93,6 +94,7 @@ impl std::fmt::Display for Version { Self::Lts(lts) => write!(f, "lts-{}", lts), Self::Semver(semver) => write!(f, "v{}", semver), Self::Alias(alias) => write!(f, "{}", alias), + Self::Latest => write!(f, "latest"), } } } @@ -107,7 +109,7 @@ impl FromStr for Version { impl PartialEq for Version { fn eq(&self, other: &semver::Version) -> bool { match self { - Self::Bypassed | Self::Lts(_) | Self::Alias(_) => false, + Self::Bypassed | Self::Lts(_) | Self::Alias(_) | Self::Latest => false, Self::Semver(v) => v == other, } }