use super::AsyncCliCommand;
use crate::config::WasmerEnv;
use anyhow::{Context, Error};
use clap::Parser;
use std::process::{Command, Stdio};
use wasmer_backend_api::{
types::{Bindings, ProgrammingLanguage},
WasmerClient,
};
use wasmer_config::package::NamedPackageIdent;
#[derive(Debug, Parser)]
pub struct CmdAdd {
#[clap(flatten)]
env: WasmerEnv,
#[clap(long, groups = &["bindings", "js"])]
npm: bool,
#[clap(long, groups = &["bindings", "js"])]
yarn: bool,
#[clap(long, groups = &["bindings", "js"])]
pnpm: bool,
#[clap(long, requires = "js")]
dev: bool,
#[clap(long, groups = &["bindings", "py"])]
pip: bool,
packages: Vec<NamedPackageIdent>,
}
#[async_trait::async_trait]
impl AsyncCliCommand for CmdAdd {
type Output = ();
async fn run_async(self) -> Result<Self::Output, anyhow::Error> {
anyhow::ensure!(!self.packages.is_empty(), "No packages specified");
let client = self.env.client_unauthennticated()?;
let bindings = self.lookup_bindings(&client).await?;
let mut cmd = self.target()?.command(&bindings)?;
cmd.stdin(Stdio::null())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit());
println!("Running: {cmd:?}");
let status = cmd.status().with_context(|| {
format!(
"Unable to start \"{:?}\". Is it installed?",
cmd.get_program()
)
})?;
anyhow::ensure!(status.success(), "Command failed: {:?}", cmd);
Ok(())
}
}
impl CmdAdd {
async fn lookup_bindings(&self, client: &WasmerClient) -> Result<Vec<Bindings>, Error> {
println!("Querying Wasmer for package bindings");
let mut bindings_to_add = Vec::new();
let language = self.target()?.language();
for pkg in &self.packages {
let bindings = lookup_bindings_for_package(client, pkg, &language)
.await
.with_context(|| format!("Unable to find bindings for {pkg}"))?;
bindings_to_add.push(bindings);
}
Ok(bindings_to_add)
}
fn target(&self) -> Result<Target, Error> {
match (self.pip, self.npm, self.yarn, self.pnpm) {
(false, false, false, false) => Err(anyhow::anyhow!(
"at least one of --npm, --pip, --yarn or --pnpm has to be specified"
)),
(true, false, false, false) => Ok(Target::Pip),
(false, true, false, false) => Ok(Target::Npm { dev: self.dev }),
(false, false, true, false) => Ok(Target::Yarn { dev: self.dev }),
(false, false, false, true) => Ok(Target::Pnpm { dev: self.dev }),
_ => Err(anyhow::anyhow!(
"only one of --npm, --pip or --yarn has to be specified"
)),
}
}
}
async fn lookup_bindings_for_package(
client: &WasmerClient,
pkg: &NamedPackageIdent,
language: &ProgrammingLanguage,
) -> Result<Bindings, Error> {
let all_bindings = wasmer_backend_api::query::list_bindings(
client,
&pkg.name,
pkg.version_opt().map(|v| v.to_string()).as_deref(),
)
.await?;
match all_bindings.iter().find(|b| b.language == *language) {
Some(b) => {
let Bindings { url, generator, .. } = b;
log::debug!("Found {pkg} bindings generated by {generator:?} at {url}");
Ok(b.clone())
}
None => {
if all_bindings.is_empty() {
anyhow::bail!("The package doesn't contain any bindings");
} else {
todo!();
}
}
}
}
#[derive(Debug, Copy, Clone)]
enum Target {
Pip,
Yarn { dev: bool },
Npm { dev: bool },
Pnpm { dev: bool },
}
impl Target {
fn language(self) -> ProgrammingLanguage {
match self {
Target::Pip => ProgrammingLanguage::Python,
Target::Pnpm { .. } | Target::Yarn { .. } | Target::Npm { .. } => {
ProgrammingLanguage::Javascript
}
}
}
fn command(self, packages: &[Bindings]) -> Result<Command, Error> {
let command_line = match self {
Target::Pip => {
if Command::new("pip").arg("--version").output().is_ok() {
"pip install"
} else if Command::new("pip3").arg("--version").output().is_ok() {
"pip3 install"
} else if Command::new("python").arg("--version").output().is_ok() {
"python -m pip install"
} else if Command::new("python3").arg("--version").output().is_ok() {
"python3 -m pip install"
} else {
return Err(anyhow::anyhow!(
"neither pip, pip3, python or python3 installed"
));
}
}
Target::Yarn { dev } => {
if Command::new("yarn").arg("--version").output().is_err() {
return Err(anyhow::anyhow!("yarn not installed"));
}
if dev {
"yarn add --dev"
} else {
"yarn add"
}
}
Target::Npm { dev } => {
if Command::new("npm").arg("--version").output().is_err() {
return Err(anyhow::anyhow!("npm not installed"));
}
if dev {
"npm install --dev"
} else {
"npm install"
}
}
Target::Pnpm { dev } => {
if Command::new("pnpm").arg("--version").output().is_err() {
return Err(anyhow::anyhow!("pnpm not installed"));
}
if dev {
"pnpm add --dev"
} else {
"pnpm add"
}
}
};
let mut command_line = command_line.to_string();
for pkg in packages {
command_line.push(' ');
command_line.push_str(&pkg.url);
}
if cfg!(windows) {
let mut cmd = Command::new("cmd");
cmd.arg("/C").arg(command_line);
Ok(cmd)
} else {
let mut cmd = Command::new("sh");
cmd.arg("-c").arg(command_line);
Ok(cmd)
}
}
}