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            ignore_errors: None,
123            save_output_as: None,
124            verbose: None,
125          })],
126          ..Default::default()
127        }));
128        (k, task)
129      })
130      .collect();
131    Ok(tasks)
132  }
133}
134
135#[cfg(test)]
136mod tests {
137  use super::*;
138
139  #[test]
140  fn test_use_npm_1() -> anyhow::Result<()> {
141    let json = r#"{
142      "name": "test",
143      "version": "1.0.0",
144      "scripts": {
145        "build": "echo 'Building'",
146        "test": "echo 'Testing'"
147      }
148    }"#;
149    let package = serde_json::from_str::<NpmPackage>(json)?;
150    assert_eq!(package.name, Some("test".to_string()));
151    assert_eq!(package.version, Some("1.0.0".to_string()));
152    assert_eq!(
153      package.scripts,
154      Some({
155        let mut map = HashMap::new();
156        map.insert("build".to_string(), "echo 'Building'".to_string());
157        map.insert("test".to_string(), "echo 'Testing'".to_string());
158        map
159      })
160    );
161    Ok(())
162  }
163
164  #[test]
165  fn test_use_npm_2() -> anyhow::Result<()> {
166    let yaml = "true";
167
168    let use_npm = serde_yaml::from_str::<UseNpm>(yaml)?;
169    if let UseNpm::Bool(value) = use_npm {
170      assert!(value);
171    } else {
172      panic!("Invalid value");
173    }
174
175    Ok(())
176  }
177
178  #[test]
179  fn test_use_npm_3() -> anyhow::Result<()> {
180    let yaml = "false";
181
182    let use_npm = serde_yaml::from_str::<UseNpm>(yaml)?;
183    if let UseNpm::Bool(value) = use_npm {
184      assert!(!value);
185    } else {
186      panic!("Invalid value");
187    }
188
189    Ok(())
190  }
191
192  #[test]
193  fn test_use_npm_4() -> anyhow::Result<()> {
194    let yaml = "
195      package_manager: npm
196    ";
197
198    let use_npm = serde_yaml::from_str::<UseNpm>(yaml)?;
199    if let UseNpm::UseNpm(args) = use_npm {
200      assert_eq!(args.package_manager, Some("npm".to_string()));
201    } else {
202      panic!("Invalid value");
203    }
204
205    Ok(())
206  }
207
208  #[test]
209  fn test_use_npm_5() -> anyhow::Result<()> {
210    let yaml = "
211      package_manager: yarn
212      work_dir: /path/to/dir
213    ";
214
215    let use_npm = serde_yaml::from_str::<UseNpm>(yaml)?;
216    if let UseNpm::UseNpm(args) = use_npm {
217      assert_eq!(args.package_manager, Some("yarn".to_string()));
218      assert_eq!(args.work_dir, Some("/path/to/dir".to_string()));
219    } else {
220      panic!("Invalid value");
221    }
222
223    Ok(())
224  }
225}