You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
194 lines
5.4 KiB
194 lines
5.4 KiB
use crate::arch::Arch; |
|
use crate::archive; |
|
use crate::archive::{Error as ExtractError, Extract}; |
|
use crate::directory_portal::DirectoryPortal; |
|
use crate::version::Version; |
|
use log::debug; |
|
use snafu::{ensure, OptionExt, ResultExt, Snafu}; |
|
use std::path::Path; |
|
use std::path::PathBuf; |
|
use url::Url; |
|
|
|
#[derive(Debug, Snafu)] |
|
pub enum Error { |
|
HttpError { |
|
source: crate::http::Error, |
|
}, |
|
IoError { |
|
source: std::io::Error, |
|
}, |
|
#[snafu(display("Can't extract the file: {}", source))] |
|
CantExtractFile { |
|
source: ExtractError, |
|
}, |
|
#[snafu(display("The downloaded archive is empty"))] |
|
TarIsEmpty, |
|
#[snafu(display( |
|
"{} for {} not found upstream.\nYou can `fnm ls-remote` to see available versions or try a different `--arch`.", |
|
version, |
|
arch |
|
))] |
|
VersionNotFound { |
|
version: Version, |
|
arch: Arch, |
|
}, |
|
#[snafu(display("Version already installed at {:?}", path))] |
|
VersionAlreadyInstalled { |
|
path: PathBuf, |
|
}, |
|
} |
|
|
|
#[cfg(unix)] |
|
fn filename_for_version(version: &Version, arch: &Arch) -> String { |
|
format!( |
|
"node-{node_ver}-{platform}-{arch}.tar.xz", |
|
node_ver = &version, |
|
platform = crate::system_info::platform_name(), |
|
arch = arch, |
|
) |
|
} |
|
|
|
#[cfg(windows)] |
|
fn filename_for_version(version: &Version, arch: &Arch) -> String { |
|
format!( |
|
"node-{node_ver}-win-{arch}.zip", |
|
node_ver = &version, |
|
arch = arch, |
|
) |
|
} |
|
|
|
fn download_url(base_url: &Url, version: &Version, arch: &Arch) -> Url { |
|
Url::parse(&format!( |
|
"{}/{}/{}", |
|
base_url.as_str().trim_end_matches('/'), |
|
version, |
|
filename_for_version(version, arch) |
|
)) |
|
.unwrap() |
|
} |
|
|
|
pub fn extract_archive_into<P: AsRef<Path>>( |
|
path: P, |
|
response: crate::http::Response, |
|
) -> Result<(), Error> { |
|
#[cfg(unix)] |
|
let extractor = archive::TarXz::new(response); |
|
#[cfg(windows)] |
|
let extractor = archive::Zip::new(response); |
|
extractor.extract_into(path).context(CantExtractFile)?; |
|
Ok(()) |
|
} |
|
|
|
/// Install a Node package |
|
pub fn install_node_dist<P: AsRef<Path>>( |
|
version: &Version, |
|
node_dist_mirror: &Url, |
|
installations_dir: P, |
|
arch: &Arch, |
|
) -> Result<(), Error> { |
|
let installation_dir = PathBuf::from(installations_dir.as_ref()).join(version.v_str()); |
|
|
|
ensure!( |
|
!installation_dir.exists(), |
|
VersionAlreadyInstalled { |
|
path: installation_dir |
|
} |
|
); |
|
|
|
std::fs::create_dir_all(installations_dir.as_ref()).context(IoError)?; |
|
|
|
let temp_installations_dir = installations_dir.as_ref().join(".downloads"); |
|
std::fs::create_dir_all(&temp_installations_dir).context(IoError)?; |
|
|
|
let portal = DirectoryPortal::new_in(&temp_installations_dir, installation_dir); |
|
|
|
let url = download_url(node_dist_mirror, version, arch); |
|
debug!("Going to call for {}", &url); |
|
let response = crate::http::get(url.as_str()).context(HttpError)?; |
|
|
|
if response.status() == 404 { |
|
return Err(Error::VersionNotFound { |
|
version: version.clone(), |
|
arch: arch.clone(), |
|
}); |
|
} |
|
|
|
debug!("Extracting response..."); |
|
extract_archive_into(&portal, response)?; |
|
debug!("Extraction completed"); |
|
|
|
let installed_directory = std::fs::read_dir(&portal) |
|
.context(IoError)? |
|
.next() |
|
.context(TarIsEmpty)? |
|
.context(IoError)?; |
|
let installed_directory = installed_directory.path(); |
|
|
|
let renamed_installation_dir = portal.join("installation"); |
|
std::fs::rename(installed_directory, renamed_installation_dir).context(IoError)?; |
|
|
|
portal.teleport().context(IoError)?; |
|
|
|
Ok(()) |
|
} |
|
|
|
#[cfg(test)] |
|
mod tests { |
|
use super::*; |
|
use crate::downloader::install_node_dist; |
|
use crate::version::Version; |
|
use pretty_assertions::assert_eq; |
|
use tempfile::tempdir; |
|
|
|
#[test_env_log::test] |
|
fn test_installing_node_12() { |
|
let installations_dir = tempdir().unwrap(); |
|
let node_path = install_in(installations_dir.path()).join("node"); |
|
|
|
let stdout = duct::cmd(node_path.to_str().unwrap(), vec!["--version"]) |
|
.stdout_capture() |
|
.run() |
|
.expect("Can't run Node binary") |
|
.stdout; |
|
|
|
let result = String::from_utf8(stdout).expect("Can't read `node --version` output"); |
|
|
|
assert_eq!(result.trim(), "v12.0.0"); |
|
} |
|
|
|
#[test_env_log::test] |
|
fn test_installing_npm() { |
|
let installations_dir = tempdir().unwrap(); |
|
let npm_path = install_in(installations_dir.path()).join(if cfg!(windows) { |
|
"npm.cmd" |
|
} else { |
|
"npm" |
|
}); |
|
|
|
let stdout = duct::cmd(npm_path.to_str().unwrap(), vec!["--version"]) |
|
.stdout_capture() |
|
.run() |
|
.expect("Can't run npm") |
|
.stdout; |
|
|
|
let result = String::from_utf8(stdout).expect("Can't read npm output"); |
|
|
|
assert_eq!(result.trim(), "6.9.0"); |
|
} |
|
|
|
fn install_in(path: &Path) -> PathBuf { |
|
let version = Version::parse("12.0.0").unwrap(); |
|
let arch = Arch::X64; |
|
let node_dist_mirror = Url::parse("https://nodejs.org/dist/").unwrap(); |
|
install_node_dist(&version, &node_dist_mirror, &path, &arch) |
|
.expect("Can't install Node 12"); |
|
|
|
let mut location_path = path.join(version.v_str()).join("installation"); |
|
|
|
if cfg!(unix) { |
|
location_path.push("bin"); |
|
} |
|
|
|
location_path |
|
} |
|
}
|
|
|