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