Renamed "wasmer install" to "wasmer add"

This commit is contained in:
Michael-F-Bryan
2022-11-17 20:20:26 +08:00
parent 02e61302f4
commit 8d46d34c58
3 changed files with 18 additions and 20 deletions

227
lib/cli/src/commands/add.rs Normal file
View 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(&registry)?;
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(())
}
}