1use std::fs::File;
2use std::io::BufReader;
3use std::path::Path;
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::Task;
13
14#[derive(Debug, Deserialize)]
15#[serde(rename_all = "camelCase")]
16pub struct NpmPackage {
17 pub name: String,
19
20 pub version: Option<String>,
22
23 pub scripts: Option<HashMap<String, String>>,
25
26 pub package_manager: Option<String>,
28}
29
30#[derive(Debug, Deserialize)]
32pub struct UseNpmArgs {
33 #[serde(default)]
35 pub package_manager: Option<String>,
36
37 #[serde(default)]
39 pub work_dir: Option<String>,
40}
41
42#[derive(Debug, Deserialize)]
43#[serde(untagged)]
44pub enum UseNpm {
45 Bool(bool),
46 UseNpm(Box<UseNpmArgs>),
47}
48
49impl 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
67impl 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: &str = &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)]
96mod tests {
97 use super::*;
98
99 #[test]
100 fn test_use_npm_1() -> anyhow::Result<()> {
101 let json = r#"{
102 "name": "test",
103 "version": "1.0.0",
104 "scripts": {
105 "build": "echo 'Building'",
106 "test": "echo 'Testing'"
107 }
108 }"#;
109 let package = serde_json::from_str::<NpmPackage>(json)?;
110 assert_eq!(package.name, "test");
111 assert_eq!(package.version, Some("1.0.0".to_string()));
112 assert_eq!(
113 package.scripts,
114 Some({
115 let mut map = HashMap::new();
116 map.insert("build".to_string(), "echo 'Building'".to_string());
117 map.insert("test".to_string(), "echo 'Testing'".to_string());
118 map
119 })
120 );
121 Ok(())
122 }
123
124 #[test]
125 fn test_use_npm_2() -> anyhow::Result<()> {
126 let yaml = "true";
127
128 let use_npm = serde_yaml::from_str::<UseNpm>(yaml)?;
129 if let UseNpm::Bool(value) = use_npm {
130 assert!(value);
131 } else {
132 panic!("Invalid value");
133 }
134
135 Ok(())
136 }
137
138 #[test]
139 fn test_use_npm_3() -> anyhow::Result<()> {
140 let yaml = "false";
141
142 let use_npm = serde_yaml::from_str::<UseNpm>(yaml)?;
143 if let UseNpm::Bool(value) = use_npm {
144 assert!(!value);
145 } else {
146 panic!("Invalid value");
147 }
148
149 Ok(())
150 }
151
152 #[test]
153 fn test_use_npm_4() -> anyhow::Result<()> {
154 let yaml = "
155 package_manager: npm
156 ";
157
158 let use_npm = serde_yaml::from_str::<UseNpm>(yaml)?;
159 if let UseNpm::UseNpm(args) = use_npm {
160 assert_eq!(args.package_manager, Some("npm".to_string()));
161 } else {
162 panic!("Invalid value");
163 }
164
165 Ok(())
166 }
167
168 #[test]
169 fn test_use_npm_5() -> anyhow::Result<()> {
170 let yaml = "
171 package_manager: yarn
172 work_dir: /path/to/dir
173 ";
174
175 let use_npm = serde_yaml::from_str::<UseNpm>(yaml)?;
176 if let UseNpm::UseNpm(args) = use_npm {
177 assert_eq!(args.package_manager, Some("yarn".to_string()));
178 assert_eq!(args.work_dir, Some("/path/to/dir".to_string()));
179 } else {
180 panic!("Invalid value");
181 }
182
183 Ok(())
184 }
185}