Skip to main content

mk_lib/schema/
use_npm.rs

1use std::fs::File;
2use std::io::BufReader;
3use std::path::PathBuf;
4
5use anyhow::Context as _;
6use hashbrown::HashMap;
7use serde::Deserialize;
8
9use crate::defaults::default_node_package_manager;
10use crate::file::ToUtf8 as _;
11use crate::utils::resolve_path;
12
13use super::{
14  CommandRunner,
15  LocalRun,
16  Task,
17  TaskArgs,
18};
19
20#[derive(Debug, Deserialize)]
21#[serde(rename_all = "camelCase")]
22pub struct NpmPackage {
23  /// The name of the package
24  pub name: Option<String>,
25
26  /// The version of the package
27  pub version: Option<String>,
28
29  /// The path to the package
30  pub scripts: Option<HashMap<String, String>>,
31
32  /// The package manager to use
33  pub package_manager: Option<String>,
34}
35
36#[derive(Debug, Deserialize)]
37pub struct UseNpmArgs {
38  /// The package manager to use
39  #[serde(default)]
40  pub package_manager: Option<String>,
41
42  /// The working directory to run the command in
43  #[serde(default)]
44  pub work_dir: Option<String>,
45}
46
47#[derive(Debug, Deserialize)]
48#[serde(untagged)]
49pub enum UseNpm {
50  Bool(bool),
51  UseNpm(Box<UseNpmArgs>),
52}
53
54impl UseNpm {
55  pub fn capture(&self) -> anyhow::Result<HashMap<String, Task>> {
56    self.capture_in_dir(&PathBuf::from("."))
57  }
58
59  pub fn capture_in_dir(&self, base_dir: &std::path::Path) -> anyhow::Result<HashMap<String, Task>> {
60    match self {
61      UseNpm::Bool(true) => self.capture_tasks_in_dir(base_dir),
62      UseNpm::UseNpm(args) => args.capture_tasks_in_dir(base_dir),
63      _ => Ok(HashMap::new()),
64    }
65  }
66
67  fn capture_tasks_in_dir(&self, base_dir: &std::path::Path) -> anyhow::Result<HashMap<String, Task>> {
68    UseNpmArgs {
69      package_manager: None,
70      work_dir: None,
71    }
72    .capture_tasks_in_dir(base_dir)
73  }
74}
75
76impl UseNpmArgs {
77  pub fn capture_tasks(&self) -> anyhow::Result<HashMap<String, Task>> {
78    self.capture_tasks_in_dir(&PathBuf::from("."))
79  }
80
81  pub fn capture_tasks_in_dir(&self, base_dir: &std::path::Path) -> anyhow::Result<HashMap<String, Task>> {
82    let resolved_work_dir = self
83      .work_dir
84      .as_ref()
85      .map(|work_dir| resolve_path(base_dir, work_dir));
86    let path = self
87      .work_dir
88      .as_ref()
89      .map(|_| resolved_work_dir.clone().unwrap().join("package.json"))
90      .unwrap_or_else(|| base_dir.join("package.json"));
91
92    if !path.exists() || !path.is_file() {
93      return Err(anyhow::anyhow!("package.json does not exist"));
94    }
95
96    let file = File::open(&path).context(format!("Failed to open file - {}", path.to_utf8()?))?;
97    let reader = BufReader::new(file);
98
99    let package: NpmPackage = serde_json::from_reader(reader)?;
100    let package_manager: &str = &self
101      .package_manager
102      .clone()
103      .unwrap_or_else(default_node_package_manager);
104
105    assert!(!package_manager.is_empty());
106
107    let tasks: HashMap<String, Task> = package
108      .scripts
109      .unwrap_or_default()
110      .into_iter()
111      .map(|(k, _)| {
112        let command = format!("{package_manager} run {k}");
113        let task = Task::Task(Box::new(TaskArgs {
114          commands: vec![CommandRunner::LocalRun(LocalRun {
115            command,
116            shell: None,
117            test: None,
118            work_dir: resolved_work_dir
119              .as_ref()
120              .map(|work_dir| work_dir.to_string_lossy().into_owned()),
121            interactive: Some(true),
122            retrigger: None,
123            ignore_errors: None,
124            save_output_as: None,
125            verbose: None,
126          })],
127          ..Default::default()
128        }));
129        (k, task)
130      })
131      .collect();
132    Ok(tasks)
133  }
134}
135
136#[cfg(test)]
137mod tests {
138  use super::*;
139
140  #[test]
141  fn test_use_npm_1() -> anyhow::Result<()> {
142    let json = r#"{
143      "name": "test",
144      "version": "1.0.0",
145      "scripts": {
146        "build": "echo 'Building'",
147        "test": "echo 'Testing'"
148      }
149    }"#;
150    let package = serde_json::from_str::<NpmPackage>(json)?;
151    assert_eq!(package.name, Some("test".to_string()));
152    assert_eq!(package.version, Some("1.0.0".to_string()));
153    assert_eq!(
154      package.scripts,
155      Some({
156        let mut map = HashMap::new();
157        map.insert("build".to_string(), "echo 'Building'".to_string());
158        map.insert("test".to_string(), "echo 'Testing'".to_string());
159        map
160      })
161    );
162    Ok(())
163  }
164
165  #[test]
166  fn test_use_npm_2() -> anyhow::Result<()> {
167    let yaml = "true";
168
169    let use_npm = serde_yaml::from_str::<UseNpm>(yaml)?;
170    if let UseNpm::Bool(value) = use_npm {
171      assert!(value);
172    } else {
173      panic!("Invalid value");
174    }
175
176    Ok(())
177  }
178
179  #[test]
180  fn test_use_npm_3() -> anyhow::Result<()> {
181    let yaml = "false";
182
183    let use_npm = serde_yaml::from_str::<UseNpm>(yaml)?;
184    if let UseNpm::Bool(value) = use_npm {
185      assert!(!value);
186    } else {
187      panic!("Invalid value");
188    }
189
190    Ok(())
191  }
192
193  #[test]
194  fn test_use_npm_4() -> anyhow::Result<()> {
195    let yaml = "
196      package_manager: npm
197    ";
198
199    let use_npm = serde_yaml::from_str::<UseNpm>(yaml)?;
200    if let UseNpm::UseNpm(args) = use_npm {
201      assert_eq!(args.package_manager, Some("npm".to_string()));
202    } else {
203      panic!("Invalid value");
204    }
205
206    Ok(())
207  }
208
209  #[test]
210  fn test_use_npm_5() -> anyhow::Result<()> {
211    let yaml = "
212      package_manager: yarn
213      work_dir: /path/to/dir
214    ";
215
216    let use_npm = serde_yaml::from_str::<UseNpm>(yaml)?;
217    if let UseNpm::UseNpm(args) = use_npm {
218      assert_eq!(args.package_manager, Some("yarn".to_string()));
219      assert_eq!(args.work_dir, Some("/path/to/dir".to_string()));
220    } else {
221      panic!("Invalid value");
222    }
223
224    Ok(())
225  }
226}