use crate::backend::backend_type::BackendType;
use crate::backend::Backend;
use crate::cache::{CacheManager, CacheManagerBuilder};
use crate::cli::args::BackendArg;
use crate::cmd::CmdLineRunner;
use crate::config::{Config, SETTINGS};
use crate::install_context::InstallContext;
use crate::timeout;
use crate::toolset::ToolVersion;
use serde_json::Value;
use std::fmt::Debug;
#[derive(Debug)]
pub struct NPMBackend {
ba: BackendArg,
latest_version_cache: CacheManager<Option<String>>,
}
const NPM_PROGRAM: &str = if cfg!(windows) { "npm.cmd" } else { "npm" };
impl Backend for NPMBackend {
fn get_type(&self) -> BackendType {
BackendType::Npm
}
fn ba(&self) -> &BackendArg {
&self.ba
}
fn get_dependencies(&self) -> eyre::Result<Vec<&str>> {
Ok(vec!["node", "bun"])
}
fn _list_remote_versions(&self) -> eyre::Result<Vec<String>> {
timeout::run_with_timeout(
|| {
let raw = cmd!(NPM_PROGRAM, "view", self.tool_name(), "versions", "--json")
.full_env(self.dependency_env()?)
.read()?;
let versions: Vec<String> = serde_json::from_str(&raw)?;
Ok(versions)
},
SETTINGS.fetch_remote_versions_timeout(),
)
}
fn latest_stable_version(&self) -> eyre::Result<Option<String>> {
timeout::run_with_timeout(
|| {
self.latest_version_cache
.get_or_try_init(|| {
let raw =
cmd!(NPM_PROGRAM, "view", self.tool_name(), "dist-tags", "--json")
.full_env(self.dependency_env()?)
.read()?;
let dist_tags: Value = serde_json::from_str(&raw)?;
let latest = match dist_tags["latest"] {
Value::String(ref s) => Some(s.clone()),
_ => self.latest_version(Some("latest".into())).unwrap(),
};
Ok(latest)
})
.cloned()
},
SETTINGS.fetch_remote_versions_timeout(),
)
}
fn install_version_(&self, ctx: &InstallContext, tv: ToolVersion) -> eyre::Result<ToolVersion> {
let config = Config::try_get()?;
if SETTINGS.npm.bun {
CmdLineRunner::new("bun")
.arg("install")
.arg(format!("{}@{}", self.tool_name(), tv.version))
.arg("--cwd")
.arg(tv.install_path())
.arg("--global")
.arg("--trust")
.with_pr(&ctx.pr)
.envs(ctx.ts.env_with_path(&config)?)
.env("BUN_INSTALL_GLOBAL_DIR", tv.install_path())
.env("BUN_INSTALL_BIN", tv.install_path().join("bin"))
.prepend_path(ctx.ts.list_paths())?
.prepend_path(self.dependency_toolset()?.list_paths())?
.execute()?;
} else {
CmdLineRunner::new(NPM_PROGRAM)
.arg("install")
.arg("-g")
.arg(format!("{}@{}", self.tool_name(), tv.version))
.arg("--prefix")
.arg(tv.install_path())
.with_pr(&ctx.pr)
.envs(ctx.ts.env_with_path(&config)?)
.prepend_path(ctx.ts.list_paths())?
.prepend_path(self.dependency_toolset()?.list_paths())?
.execute()?;
}
Ok(tv)
}
#[cfg(windows)]
fn list_bin_paths(
&self,
tv: &crate::toolset::ToolVersion,
) -> eyre::Result<Vec<std::path::PathBuf>> {
Ok(vec![tv.install_path()])
}
}
impl NPMBackend {
pub fn from_arg(ba: BackendArg) -> Self {
Self {
latest_version_cache: CacheManagerBuilder::new(
ba.cache_path.join("latest_version.msgpack.z"),
)
.with_fresh_duration(SETTINGS.fetch_remote_versions_cache())
.build(),
ba,
}
}
}