wasmer_cli/commands/
add.rs1use super::AsyncCliCommand;
2use crate::config::WasmerEnv;
3use anyhow::{Context, Error};
4use clap::Parser;
5use std::process::{Command, Stdio};
6use wasmer_backend_api::{
7 WasmerClient,
8 types::{Bindings, ProgrammingLanguage},
9};
10use wasmer_config::package::NamedPackageIdent;
11
12#[derive(Debug, Parser)]
14pub struct CmdAdd {
15 #[clap(flatten)]
16 env: WasmerEnv,
17 #[clap(long, groups = &["bindings", "js"])]
19 npm: bool,
20 #[clap(long, groups = &["bindings", "js"])]
22 yarn: bool,
23 #[clap(long, groups = &["bindings", "js"])]
25 pnpm: bool,
26 #[clap(long, requires = "js")]
28 dev: bool,
29 #[clap(long, groups = &["bindings", "py"])]
31 pip: bool,
32 packages: Vec<NamedPackageIdent>,
34}
35
36#[async_trait::async_trait]
37impl AsyncCliCommand for CmdAdd {
38 type Output = ();
39
40 async fn run_async(self) -> Result<Self::Output, anyhow::Error> {
42 anyhow::ensure!(!self.packages.is_empty(), "No packages specified");
43
44 let client = self.env.client_unauthennticated()?;
45 let bindings = self.lookup_bindings(&client).await?;
46
47 let mut cmd = self.target()?.command(&bindings)?;
48 cmd.stdin(Stdio::null())
49 .stdout(Stdio::inherit())
50 .stderr(Stdio::inherit());
51
52 println!("Running: {cmd:?}");
53
54 let status = cmd.status().with_context(|| {
55 format!(
56 "Unable to start \"{:?}\". Is it installed?",
57 cmd.get_program()
58 )
59 })?;
60
61 anyhow::ensure!(status.success(), "Command failed: {cmd:?}");
62
63 Ok(())
64 }
65}
66
67impl CmdAdd {
68 async fn lookup_bindings(&self, client: &WasmerClient) -> Result<Vec<Bindings>, Error> {
69 println!("Querying Wasmer for package bindings");
70
71 let mut bindings_to_add = Vec::new();
72 let language = self.target()?.language();
73
74 for pkg in &self.packages {
75 let bindings = lookup_bindings_for_package(client, pkg, &language)
76 .await
77 .with_context(|| format!("Unable to find bindings for {pkg}"))?;
78 bindings_to_add.push(bindings);
79 }
80
81 Ok(bindings_to_add)
82 }
83
84 fn target(&self) -> Result<Target, Error> {
85 match (self.pip, self.npm, self.yarn, self.pnpm) {
86 (false, false, false, false) => Err(anyhow::anyhow!(
87 "at least one of --npm, --pip, --yarn or --pnpm has to be specified"
88 )),
89 (true, false, false, false) => Ok(Target::Pip),
90 (false, true, false, false) => Ok(Target::Npm { dev: self.dev }),
91 (false, false, true, false) => Ok(Target::Yarn { dev: self.dev }),
92 (false, false, false, true) => Ok(Target::Pnpm { dev: self.dev }),
93 _ => Err(anyhow::anyhow!(
94 "only one of --npm, --pip or --yarn has to be specified"
95 )),
96 }
97 }
98}
99
100async fn lookup_bindings_for_package(
101 client: &WasmerClient,
102 pkg: &NamedPackageIdent,
103 language: &ProgrammingLanguage,
104) -> Result<Bindings, Error> {
105 let all_bindings = wasmer_backend_api::query::list_bindings(
106 client,
107 &pkg.name,
108 pkg.version_opt().map(|v| v.to_string()).as_deref(),
109 )
110 .await?;
111
112 match all_bindings.iter().find(|b| b.language == *language) {
113 Some(b) => {
114 let Bindings { url, generator, .. } = b;
115 log::debug!("Found {pkg} bindings generated by {generator:?} at {url}");
116
117 Ok(b.clone())
118 }
119 None => {
120 if all_bindings.is_empty() {
121 anyhow::bail!("The package doesn't contain any bindings");
122 } else {
123 todo!();
124 }
125 }
126 }
127}
128
129#[derive(Debug, Copy, Clone)]
130enum Target {
131 Pip,
132 Yarn { dev: bool },
133 Npm { dev: bool },
134 Pnpm { dev: bool },
135}
136
137impl Target {
138 fn language(self) -> ProgrammingLanguage {
139 match self {
140 Target::Pip => ProgrammingLanguage::Python,
141 Target::Pnpm { .. } | Target::Yarn { .. } | Target::Npm { .. } => {
142 ProgrammingLanguage::Javascript
143 }
144 }
145 }
146
147 fn command(self, packages: &[Bindings]) -> Result<Command, Error> {
156 let command_line = match self {
157 Target::Pip => {
158 if Command::new("pip").arg("--version").output().is_ok() {
159 "pip install"
160 } else if Command::new("pip3").arg("--version").output().is_ok() {
161 "pip3 install"
162 } else if Command::new("python").arg("--version").output().is_ok() {
163 "python -m pip install"
164 } else if Command::new("python3").arg("--version").output().is_ok() {
165 "python3 -m pip install"
166 } else {
167 return Err(anyhow::anyhow!(
168 "neither pip, pip3, python or python3 installed"
169 ));
170 }
171 }
172 Target::Yarn { dev } => {
173 if Command::new("yarn").arg("--version").output().is_err() {
174 return Err(anyhow::anyhow!("yarn not installed"));
175 }
176 if dev { "yarn add --dev" } else { "yarn add" }
177 }
178 Target::Npm { dev } => {
179 if Command::new("npm").arg("--version").output().is_err() {
180 return Err(anyhow::anyhow!("npm not installed"));
181 }
182 if dev {
183 "npm install --dev"
184 } else {
185 "npm install"
186 }
187 }
188 Target::Pnpm { dev } => {
189 if Command::new("pnpm").arg("--version").output().is_err() {
190 return Err(anyhow::anyhow!("pnpm not installed"));
191 }
192 if dev { "pnpm add --dev" } else { "pnpm add" }
193 }
194 };
195 let mut command_line = command_line.to_string();
196
197 for pkg in packages {
198 command_line.push(' ');
199 command_line.push_str(&pkg.url);
200 }
201
202 if cfg!(windows) {
203 let mut cmd = Command::new("cmd");
204 cmd.arg("/C").arg(command_line);
205 Ok(cmd)
206 } else {
207 let mut cmd = Command::new("sh");
208 cmd.arg("-c").arg(command_line);
209 Ok(cmd)
210 }
211 }
212}