mirror of
https://github.com/mii443/wasmer.git
synced 2025-12-09 06:08:29 +00:00
Renamed "wasmer install" to "wasmer add"
This commit is contained in:
227
lib/cli/src/commands/add.rs
Normal file
227
lib/cli/src/commands/add.rs
Normal file
@@ -0,0 +1,227 @@
|
||||
use std::{
|
||||
fmt::{self, Display, Formatter},
|
||||
process::{Command, Stdio},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use anyhow::{Context, Error};
|
||||
use clap::Parser;
|
||||
use wasmer_registry::{Bindings, PartialWapmConfig, ProgrammingLanguage};
|
||||
|
||||
/// Add a WAPM package's bindings to your application.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Add {
|
||||
/// The registry to fetch bindings from.
|
||||
#[clap(long, env = "WAPM_REGISTRY")]
|
||||
registry: Option<String>,
|
||||
/// Add the JavaScript bindings using "npm install".
|
||||
#[clap(long, groups = &["bindings", "js"])]
|
||||
npm: bool,
|
||||
/// Add the JavaScript bindings using "yarn add".
|
||||
#[clap(long, groups = &["bindings", "js"])]
|
||||
yarn: bool,
|
||||
/// Add the package as a dev-dependency.
|
||||
#[clap(long, requires = "js")]
|
||||
dev: bool,
|
||||
/// Add the Python bindings using "pip install".
|
||||
#[clap(long, groups = &["bindings", "py"])]
|
||||
pip: bool,
|
||||
/// The packages to add (e.g. "wasmer/wasmer-pack@0.5.0" or "python/python")
|
||||
#[clap(parse(try_from_str))]
|
||||
packages: Vec<PackageSpecifier>,
|
||||
}
|
||||
|
||||
impl Add {
|
||||
/// Execute [`Add`].
|
||||
pub fn execute(&self) -> Result<(), Error> {
|
||||
anyhow::ensure!(!self.packages.is_empty(), "No packages specified");
|
||||
|
||||
let registry = self
|
||||
.registry()
|
||||
.context("Unable to determine which registry to use")?;
|
||||
|
||||
let bindings = self.lookup_bindings(®istry)?;
|
||||
|
||||
let mut cmd = self.target().command(&bindings);
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
log::debug!("Running {cmd:?}");
|
||||
|
||||
let status = cmd
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.status()
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Unable to start \"{:?}\". Is it installed?",
|
||||
cmd.get_program()
|
||||
)
|
||||
})?;
|
||||
|
||||
anyhow::ensure!(status.success(), "Command failed: {:?}", cmd);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn lookup_bindings(&self, registry: &str) -> Result<Vec<Bindings>, Error> {
|
||||
#[cfg(feature = "debug")]
|
||||
log::debug!("Querying WAPM for the bindings packages");
|
||||
|
||||
let mut bindings_to_add = Vec::new();
|
||||
let language = self.target().language();
|
||||
|
||||
for pkg in &self.packages {
|
||||
let bindings = lookup_bindings_for_package(registry, pkg, &language)
|
||||
.with_context(|| format!("Unable to find bindings for {pkg}"))?;
|
||||
bindings_to_add.push(bindings);
|
||||
}
|
||||
|
||||
Ok(bindings_to_add)
|
||||
}
|
||||
|
||||
fn registry(&self) -> Result<String, Error> {
|
||||
match &self.registry {
|
||||
Some(r) => Ok(r.clone()),
|
||||
None => {
|
||||
let cfg = PartialWapmConfig::from_file()
|
||||
.map_err(Error::msg)
|
||||
.context("Unable to load WAPM's config file")?;
|
||||
Ok(cfg.registry.get_current_registry())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn target(&self) -> Target {
|
||||
match (self.pip, self.npm, self.yarn) {
|
||||
(true, false, false) => Target::Pip,
|
||||
(false, true, false) => Target::Npm { dev: self.dev },
|
||||
(false, false, true) => Target::Yarn { dev: self.dev },
|
||||
_ => unreachable!(
|
||||
"Clap should ensure at least one item in the \"bindings\" group is specified"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn lookup_bindings_for_package(
|
||||
registry: &str,
|
||||
pkg: &PackageSpecifier,
|
||||
language: &ProgrammingLanguage,
|
||||
) -> Result<Bindings, Error> {
|
||||
let all_bindings = wasmer_registry::list_bindings(registry, &pkg.name, pkg.version.as_deref())?;
|
||||
|
||||
match all_bindings.iter().find(|b| b.language == *language) {
|
||||
Some(b) => {
|
||||
#[cfg(feature = "debug")]
|
||||
{
|
||||
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 },
|
||||
}
|
||||
|
||||
impl Target {
|
||||
fn language(self) -> ProgrammingLanguage {
|
||||
match self {
|
||||
Target::Pip => ProgrammingLanguage::PYTHON,
|
||||
Target::Yarn { .. } | Target::Npm { .. } => ProgrammingLanguage::JAVASCRIPT,
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a command which we can run to add packages.
|
||||
///
|
||||
/// This deliberately runs the command using the OS shell instead of
|
||||
/// invoking the tool directly. That way we can handle when a version
|
||||
/// manager (e.g. `nvm` or `asdf`) replaces the tool with a script (e.g.
|
||||
/// `npm.cmd` or `yarn.ps1`).
|
||||
///
|
||||
/// See <https://github.com/wasmerio/wapm-cli/issues/291> for more.
|
||||
fn command(self, packages: &[Bindings]) -> Command {
|
||||
let command_line = match self {
|
||||
Target::Pip => "pip install",
|
||||
Target::Yarn { dev: true } => "yarn add --dev",
|
||||
Target::Yarn { dev: false } => "yarn add",
|
||||
Target::Npm { dev: true } => "npm install --dev",
|
||||
Target::Npm { dev: false } => "npm install",
|
||||
};
|
||||
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);
|
||||
cmd
|
||||
} else {
|
||||
let mut cmd = Command::new("sh");
|
||||
cmd.arg("-c").arg(command_line);
|
||||
cmd
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The full name and optional version number for a WAPM package.
|
||||
#[derive(Debug)]
|
||||
struct PackageSpecifier {
|
||||
/// The package's full name (i.e. `wasmer/wasmer-pack` in
|
||||
/// `wasmer/wasmer-pack@0.5.0`).
|
||||
name: String,
|
||||
version: Option<String>,
|
||||
}
|
||||
|
||||
impl FromStr for PackageSpecifier {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let (name, version) = match s.split_once('@') {
|
||||
Some((name, version)) => (name, Some(version)),
|
||||
None => (s, None),
|
||||
};
|
||||
|
||||
if !name
|
||||
.chars()
|
||||
.all(|c| c.is_ascii_alphanumeric() || "/_-".contains(c))
|
||||
{
|
||||
anyhow::bail!("Invalid package name");
|
||||
}
|
||||
|
||||
Ok(PackageSpecifier {
|
||||
name: name.to_string(),
|
||||
version: version.map(|s| s.to_string()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for PackageSpecifier {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let PackageSpecifier { name, version } = self;
|
||||
|
||||
write!(f, "{name}")?;
|
||||
if let Some(version) = version {
|
||||
write!(f, "@{version}")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user