1
use std::fs::File;
2
use std::io::BufReader;
3
use std::path::Path;
4

            
5
use anyhow::Context as _;
6
use hashbrown::HashMap;
7
use serde::Deserialize;
8

            
9
use crate::defaults::default_node_package_manager;
10
use crate::file::ToUtf8 as _;
11

            
12
use super::Task;
13

            
14
#[derive(Debug, Deserialize)]
15
#[serde(rename_all = "camelCase")]
16
pub struct NpmPackage {
17
  /// The name of the package
18
  pub name: String,
19

            
20
  /// The version of the package
21
  pub version: Option<String>,
22

            
23
  /// The path to the package
24
  pub scripts: Option<HashMap<String, String>>,
25

            
26
  /// The package manager to use
27
  pub package_manager: Option<String>,
28
}
29

            
30
// TODO: Make use of the work_dir field
31
#[derive(Debug, Deserialize)]
32
pub struct UseNpmArgs {
33
  /// The package manager to use
34
  #[serde(default)]
35
  pub package_manager: Option<String>,
36

            
37
  /// The working directory to run the command in
38
  #[serde(default)]
39
  pub work_dir: Option<String>,
40
}
41

            
42
#[derive(Debug, Deserialize)]
43
#[serde(untagged)]
44
pub enum UseNpm {
45
  Bool(bool),
46
  UseNpm(Box<UseNpmArgs>),
47
}
48

            
49
impl UseNpm {
50
  pub fn capture(&self) -> anyhow::Result<HashMap<String, Task>> {
51
    match self {
52
      UseNpm::Bool(true) => self.capture_tasks(),
53
      UseNpm::UseNpm(args) => args.capture_tasks(),
54
      _ => Ok(HashMap::new()),
55
    }
56
  }
57

            
58
  fn capture_tasks(&self) -> anyhow::Result<HashMap<String, Task>> {
59
    UseNpmArgs {
60
      package_manager: None,
61
      work_dir: None,
62
    }
63
    .capture_tasks()
64
  }
65
}
66

            
67
impl UseNpmArgs {
68
  pub fn capture_tasks(&self) -> anyhow::Result<HashMap<String, Task>> {
69
    let path = Path::new("package.json");
70
    if !path.exists() || !path.is_file() {
71
      return Err(anyhow::anyhow!("package.json does not exist"));
72
    }
73

            
74
    let file = File::open(path).context(format!("Failed to open file - {}", path.to_utf8()?))?;
75
    let reader = BufReader::new(file);
76

            
77
    let package: NpmPackage = serde_json::from_reader(reader)?;
78
    let package_manager = self
79
      .package_manager
80
      .clone()
81
      .unwrap_or_else(default_node_package_manager);
82

            
83
    assert!(!package_manager.is_empty());
84

            
85
    let tasks: HashMap<String, Task> = package
86
      .scripts
87
      .unwrap_or_default()
88
      .into_iter()
89
      .map(|(k, _)| (k.clone(), Task::String(format!("{package_manager} run {k}"))))
90
      .collect();
91
    Ok(tasks)
92
  }
93
}
94

            
95
#[cfg(test)]
96
mod tests {
97
  use super::*;
98

            
99
  #[test]
100
20
  fn test_use_npm_1() -> anyhow::Result<()> {
101
20
    let json = r#"{
102
20
      "name": "test",
103
20
      "version": "1.0.0",
104
20
      "scripts": {
105
20
        "build": "echo 'Building'",
106
20
        "test": "echo 'Testing'"
107
20
      }
108
20
    }"#;
109
20
    let package = serde_json::from_str::<NpmPackage>(json)?;
110
20
    assert_eq!(package.name, "test");
111
20
    assert_eq!(package.version, Some("1.0.0".to_string()));
112
20
    assert_eq!(
113
20
      package.scripts,
114
20
      Some({
115
20
        let mut map = HashMap::new();
116
20
        map.insert("build".to_string(), "echo 'Building'".to_string());
117
20
        map.insert("test".to_string(), "echo 'Testing'".to_string());
118
20
        map
119
20
      })
120
20
    );
121
20
    Ok(())
122
20
  }
123

            
124
  #[test]
125
20
  fn test_use_npm_2() -> anyhow::Result<()> {
126
20
    let yaml = "true";
127

            
128
20
    let use_npm = serde_yaml::from_str::<UseNpm>(yaml)?;
129
20
    if let UseNpm::Bool(value) = use_npm {
130
20
      assert!(value);
131
    } else {
132
      panic!("Invalid value");
133
    }
134

            
135
20
    Ok(())
136
20
  }
137

            
138
  #[test]
139
20
  fn test_use_npm_3() -> anyhow::Result<()> {
140
20
    let yaml = "false";
141

            
142
20
    let use_npm = serde_yaml::from_str::<UseNpm>(yaml)?;
143
20
    if let UseNpm::Bool(value) = use_npm {
144
20
      assert!(!value);
145
    } else {
146
      panic!("Invalid value");
147
    }
148

            
149
20
    Ok(())
150
20
  }
151

            
152
  #[test]
153
20
  fn test_use_npm_4() -> anyhow::Result<()> {
154
20
    let yaml = "
155
20
      package_manager: npm
156
20
    ";
157

            
158
20
    let use_npm = serde_yaml::from_str::<UseNpm>(yaml)?;
159
20
    if let UseNpm::UseNpm(args) = use_npm {
160
20
      assert_eq!(args.package_manager, Some("npm".to_string()));
161
    } else {
162
      panic!("Invalid value");
163
    }
164

            
165
20
    Ok(())
166
20
  }
167

            
168
  #[test]
169
20
  fn test_use_npm_5() -> anyhow::Result<()> {
170
20
    let yaml = "
171
20
      package_manager: yarn
172
20
      work_dir: /path/to/dir
173
20
    ";
174

            
175
20
    let use_npm = serde_yaml::from_str::<UseNpm>(yaml)?;
176
20
    if let UseNpm::UseNpm(args) = use_npm {
177
20
      assert_eq!(args.package_manager, Some("yarn".to_string()));
178
20
      assert_eq!(args.work_dir, Some("/path/to/dir".to_string()));
179
    } else {
180
      panic!("Invalid value");
181
    }
182

            
183
20
    Ok(())
184
20
  }
185
}