use std::collections::BTreeMap;
use std::{
path::{Path, PathBuf},
sync::Arc,
};
use async_trait::async_trait;
use eyre::Result;
use itertools::Itertools;
use serde::Deserialize;
use versions::Versioning;
use crate::backend::Backend;
use crate::backend::VersionInfo;
use crate::backend::platform_target::PlatformTarget;
use crate::backend::static_helpers::fetch_checksum_from_file;
use crate::cli::args::BackendArg;
use crate::cmd::CmdLineRunner;
use crate::config::Config;
use crate::http::{HTTP, HTTP_FETCH};
use crate::install_context::InstallContext;
use crate::lockfile::PlatformInfo;
use crate::toolset::{ToolRequest, ToolVersion, Toolset};
use crate::ui::progress_report::SingleReport;
use crate::{file, plugins};
#[derive(Debug)]
pub struct DenoPlugin {
ba: Arc<BackendArg>,
}
impl DenoPlugin {
pub fn new() -> Self {
Self {
ba: Arc::new(plugins::core::new_backend_arg("deno")),
}
}
fn deno_bin(&self, tv: &ToolVersion) -> PathBuf {
tv.install_path().join(if cfg!(target_os = "windows") {
"bin/deno.exe"
} else {
"bin/deno"
})
}
fn test_deno(&self, tv: &ToolVersion, pr: &dyn SingleReport) -> Result<()> {
pr.set_message("deno -V".into());
CmdLineRunner::new(self.deno_bin(tv))
.with_pr(pr)
.arg("-V")
.execute()
}
async fn download(&self, tv: &ToolVersion, pr: &dyn SingleReport) -> Result<PathBuf> {
let url = self
.get_tarball_url(tv, &PlatformTarget::from_current())
.await?
.ok_or_else(|| eyre::eyre!("Failed to get deno tarball URL"))?;
let filename = url.split('/').next_back().unwrap();
let tarball_path = tv.download_path().join(filename);
pr.set_message(format!("download {filename}"));
HTTP.download_file(&url, &tarball_path, Some(pr)).await?;
Ok(tarball_path)
}
fn install(&self, tv: &ToolVersion, pr: &dyn SingleReport, tarball_path: &Path) -> Result<()> {
let filename = tarball_path.file_name().unwrap().to_string_lossy();
pr.set_message(format!("extract {filename}"));
file::remove_all(tv.install_path())?;
file::create_dir_all(tv.install_path().join("bin"))?;
file::unzip(tarball_path, &tv.download_path(), &Default::default())?;
file::move_file(
tv.download_path().join(if cfg!(target_os = "windows") {
"deno.exe"
} else {
"deno"
}),
self.deno_bin(tv),
)?;
file::make_executable(self.deno_bin(tv))?;
Ok(())
}
fn verify(&self, tv: &ToolVersion, pr: &dyn SingleReport) -> Result<()> {
self.test_deno(tv, pr)
}
}
#[async_trait]
impl Backend for DenoPlugin {
fn ba(&self) -> &Arc<BackendArg> {
&self.ba
}
async fn security_info(&self) -> Vec<crate::backend::SecurityFeature> {
use crate::backend::SecurityFeature;
vec![SecurityFeature::Checksum {
algorithm: Some("sha256".to_string()),
}]
}
async fn _list_remote_versions(&self, _config: &Arc<Config>) -> Result<Vec<VersionInfo>> {
let versions: DenoVersions = HTTP_FETCH.json("https://deno.com/versions.json").await?;
let versions = versions
.cli
.into_iter()
.filter(|v| v.starts_with('v'))
.map(|v| VersionInfo {
version: v.trim_start_matches('v').to_string(),
..Default::default()
})
.unique_by(|v| v.version.clone())
.sorted_by_cached_key(|v| (Versioning::new(&v.version), v.version.clone()))
.collect();
Ok(versions)
}
async fn _idiomatic_filenames(&self) -> Result<Vec<String>> {
Ok(vec![".deno-version".into(), "package.json".into()])
}
async fn install_version_(
&self,
ctx: &InstallContext,
mut tv: ToolVersion,
) -> Result<ToolVersion> {
let tarball_path = self.download(&tv, ctx.pr.as_ref()).await?;
ctx.pr.next_operation();
self.verify_checksum(ctx, &mut tv, &tarball_path)?;
ctx.pr.next_operation();
self.install(&tv, ctx.pr.as_ref(), &tarball_path)?;
self.verify(&tv, ctx.pr.as_ref())?;
Ok(tv)
}
async fn list_bin_paths(
&self,
_config: &Arc<Config>,
tv: &ToolVersion,
) -> Result<Vec<PathBuf>> {
if let ToolRequest::System { .. } = tv.request {
return Ok(vec![]);
}
let bin_paths = vec![
tv.install_path().join("bin"),
tv.install_path().join(".deno/bin"),
];
Ok(bin_paths)
}
async fn exec_env(
&self,
_config: &Arc<Config>,
_ts: &Toolset,
tv: &ToolVersion,
) -> eyre::Result<BTreeMap<String, String>> {
let map = BTreeMap::from([(
"DENO_INSTALL_ROOT".into(),
tv.install_path().join(".deno").to_string_lossy().into(),
)]);
Ok(map)
}
async fn get_tarball_url(
&self,
tv: &ToolVersion,
target: &PlatformTarget,
) -> Result<Option<String>> {
let arch = match target.arch_name() {
"x64" => "x86_64",
"arm64" => "aarch64",
other => other,
};
let os = match target.os_name() {
"macos" => "apple-darwin",
"linux" => "unknown-linux-gnu",
"windows" => "pc-windows-msvc",
_ => "unknown-linux-gnu",
};
Ok(Some(format!(
"https://dl.deno.land/release/v{}/deno-{}-{}.zip",
tv.version, arch, os
)))
}
async fn resolve_lock_info(
&self,
tv: &ToolVersion,
target: &PlatformTarget,
) -> Result<PlatformInfo> {
let url = self
.get_tarball_url(tv, target)
.await?
.ok_or_else(|| eyre::eyre!("Failed to get deno tarball URL"))?;
let checksum_url = format!("{}.sha256sum", &url);
let checksum = fetch_checksum_from_file(&checksum_url, "sha256").await;
Ok(PlatformInfo {
url: Some(url),
checksum,
size: None,
url_api: None,
conda_deps: None,
..Default::default()
})
}
}
#[derive(Debug, Deserialize)]
struct DenoVersions {
cli: Vec<String>,
}