Browse Source

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 f5f2ca29f1.
we can revert the yarn changes and do it in a different PR

* revert the changes in .github/workflows

Co-authored-by: Gal Schlezinger <gal@spitfire.co.il>
remotes/origin/clean-multishell-on-shell-exit
nzhl 2 years ago committed by GitHub
parent
commit
ca71291020
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      .changeset/warm-rice-appear.md
  2. 2
      .ci/prepare-version.js
  3. 4
      .ci/print-command-docs.js
  4. 3
      docs/commands.md
  5. 63
      src/commands/install.rs
  6. 2
      src/user_version.rs
  7. 6
      src/version.rs

5
.changeset/warm-rice-appear.md

@ -0,0 +1,5 @@
---
"fnm": minor
---
support `fnm install --latest` to install the latest Node.js version

2
.ci/prepare-version.js

@ -17,7 +17,7 @@ const command = cmd.command({
async handler({}) { async handler({}) {
updateCargoToml(await getPackageVersion()) updateCargoToml(await getPackageVersion())
exec("cargo build --release") 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") exec("./.ci/record_screen.sh")
}, },
}) })

4
.ci/print-command-docs.js

@ -42,7 +42,7 @@ const command = cmd.command({
if (gitStatus.state === "dirty") { if (gitStatus.state === "dirty") {
process.exitCode = 1 process.exitCode = 1
console.error( 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(`hint: The following diff was found:`)
console.error() console.error()
@ -76,7 +76,7 @@ async function main(targetFile, fnmPath) {
stream.close() stream.close()
await execa(`yarn`, ["prettier", "--write", targetFile]) await execa(`pnpm`, ["prettier", "--write", targetFile])
} }
/** /**

3
docs/commands.md

@ -458,6 +458,9 @@ OPTIONS:
-h, --help -h, --help
Print help information Print help information
--latest
Install latest version
--log-level <LOG_LEVEL> --log-level <LOG_LEVEL>
The log level of fnm commands The log level of fnm commands

63
src/commands/install.rs

@ -18,25 +18,33 @@ pub struct Install {
pub version: Option<UserVersion>, pub version: Option<UserVersion>,
/// Install latest LTS /// Install latest LTS
#[clap(long, conflicts_with = "version")] #[clap(long, conflicts_with_all = &["version", "latest"])]
pub lts: bool, pub lts: bool,
/// Install latest version
#[clap(long, conflicts_with_all = &["version", "lts"])]
pub latest: bool,
} }
impl Install { impl Install {
fn version(self) -> Result<Option<UserVersion>, Error> { fn version(self) -> Result<Option<UserVersion>, Error> {
match self { match self {
Self {
version: Some(_),
lts: true,
} => Err(Error::TooManyVersionsProvided),
Self { Self {
version: v, version: v,
lts: false, lts: false,
latest: false,
} => Ok(v), } => Ok(v),
Self { Self {
version: None, version: None,
lts: true, lts: true,
latest: false,
} => Ok(Some(UserVersion::Full(Version::Lts(LtsType::Latest)))), } => 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 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 => { current_version => {
let available_versions: Vec<_> = remote_node_index::list(&config.node_dist_mirror) let available_versions: Vec<_> = remote_node_index::list(&config.node_dist_mirror)
.map_err(|source| Error::CantListRemoteVersions { source })? .map_err(|source| Error::CantListRemoteVersions { source })?
@ -153,6 +176,8 @@ pub enum Error {
CantFindNodeVersion { requested_version: UserVersion }, CantFindNodeVersion { requested_version: UserVersion },
#[error("Can't find relevant LTS named {}", lts_type)] #[error("Can't find relevant LTS named {}", lts_type)]
CantFindRelevantLts { lts_type: crate::lts::LtsType }, 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())] #[error("The requested version is not installable: {}", version.v_str())]
UninstallableVersion { version: Version }, UninstallableVersion { version: Version },
#[error("Too many versions provided. Please don't use --lts with a version string.")] #[error("Too many versions provided. Please don't use --lts with a version string.")]
@ -175,6 +200,7 @@ mod tests {
Install { Install {
version: UserVersion::from_str("12.0.0").ok(), version: UserVersion::from_str("12.0.0").ok(),
lts: false, lts: false,
latest: false,
} }
.apply(&config) .apply(&config)
.expect("Can't install"); .expect("Can't install");
@ -190,4 +216,31 @@ mod tests {
.ok() .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());
}
} }

2
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::OnlyMajor(major), Version::Semver(other)) => *major == other.major,
(Self::MajorMinor(major, minor), Version::Semver(other)) => { (Self::MajorMinor(major, minor), Version::Semver(other)) => {
*major == other.major && *minor == other.minor *major == other.major && *minor == other.minor

6
src/version.rs

@ -9,6 +9,7 @@ pub enum Version {
Semver(semver::Version), Semver(semver::Version),
Lts(LtsType), Lts(LtsType),
Alias(String), Alias(String),
Latest,
Bypassed, Bypassed,
} }
@ -58,7 +59,7 @@ impl Version {
pub fn installation_path(&self, config: &config::FnmConfig) -> std::path::PathBuf { pub fn installation_path(&self, config: &config::FnmConfig) -> std::path::PathBuf {
match self { match self {
Self::Bypassed => system_version::path(), 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()) config.aliases_dir().join(v.alias_name().unwrap())
} }
v @ Self::Semver(_) => config v @ Self::Semver(_) => config
@ -93,6 +94,7 @@ impl std::fmt::Display for Version {
Self::Lts(lts) => write!(f, "lts-{}", lts), Self::Lts(lts) => write!(f, "lts-{}", lts),
Self::Semver(semver) => write!(f, "v{}", semver), Self::Semver(semver) => write!(f, "v{}", semver),
Self::Alias(alias) => write!(f, "{}", alias), Self::Alias(alias) => write!(f, "{}", alias),
Self::Latest => write!(f, "latest"),
} }
} }
} }
@ -107,7 +109,7 @@ impl FromStr for Version {
impl PartialEq<semver::Version> for Version { impl PartialEq<semver::Version> for Version {
fn eq(&self, other: &semver::Version) -> bool { fn eq(&self, other: &semver::Version) -> bool {
match self { match self {
Self::Bypassed | Self::Lts(_) | Self::Alias(_) => false, Self::Bypassed | Self::Lts(_) | Self::Alias(_) | Self::Latest => false,
Self::Semver(v) => v == other, Self::Semver(v) => v == other,
} }
} }

Loading…
Cancel
Save