1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
use anyhow::Result;
use serde::Deserialize;
use std::process::{Command, Stdio};

#[derive(Deserialize, Debug)]
struct PackageJson {
    name: String,
    version: String,
    main: Option<String>,
    types: Option<String>,
}
pub enum PackageType {
    NPM,
    NPX,
    Package(String),
}
pub struct Package {
    package_type: PackageType,
}

impl Package {
    pub fn new(package_type: PackageType) -> Self {
        Self { package_type }
    }
    pub fn name(&self) -> &str {
        match &self.package_type {
            PackageType::NPM => "npm",
            PackageType::NPX => "npx",
            PackageType::Package(s) => s,
        }
    }
    pub fn install(&self, version: Option<&str>) -> Result<()> {
        if self.installed().is_ok() {
            return Ok(());
        }
        let install = match &self.package_type {
            PackageType::Package(s) => {
                let npm = Self::new(PackageType::NPM);
                npm.installed()?;
                if let Some(version) = version {
                    npm.execute_to_string(&format!(
                        "npm install {}@{} --save",
                        &self.name(),
                        version
                    ))?;
                } else {
                    npm.execute_to_string(&format!(
                        "npm install {} --save --silent",
                        &self.name()
                    ))?;
                }
                Ok(())
            }
            _ => Err(anyhow::format_err!("Cant install this package")),
        };
        install?;
        Ok(())
    }
    pub fn installed(&self) -> Result<()> {
        match self.version() {
            Ok(_) => Ok(()),
            Err(e) => Err(e),
        }
    }
    pub fn version(&self) -> Result<String> {
        let version = match &self.package_type {
            PackageType::NPM => self.execute_to_string(&format!("{} -v", self.name())),
            PackageType::NPX => self.execute_to_string(&format!("{} -v", self.name())),
            PackageType::Package(s) => match self.package_json() {
                Ok(x) => Ok(x.version),
                Err(e) => Err(e),
            },
        }?;
        Ok(version)
    }
    fn package_json(&self) -> Result<PackageJson> {
        let file = std::fs::File::open(format!("./node_modules/{}/package.json", self.name()))?;
        let reader = std::io::BufReader::new(file);
        let json = serde_json::from_reader(reader)?;
        Ok(json)
    }
    pub fn execute_to_stdout(&self, arg: &str) -> Result<()> {
        let mut output = self
            .program()
            .arg("-c")
            .arg(arg)
            .stdout(Stdio::inherit())
            .stderr(Stdio::inherit())
            .spawn()?;

        match output.wait() {
            Ok(_e) => Ok(()),
            Err(e) => Err(e.into()),
        }
    }
    pub fn execute_to_string(&self, arg: &str) -> Result<String> {
        let output = self
            .program()
            .arg("-c")
            .arg(arg)
            .stderr(Stdio::inherit())
            .output()?;

        Ok(std::str::from_utf8(&output.stdout)?.trim().to_string())
    }
    fn program(&self) -> Command {
        if cfg!(target_os = "windows") {
            Command::new("cmd")
        } else {
            Command::new("sh")
        }
    }
}