use crate::types::{Task, TaskDefinitionType, TaskRunner};
use std::path::PathBuf;
pub fn parse(path: &PathBuf) -> Result<Vec<Task>, String> {
let contents =
std::fs::read_to_string(path).map_err(|e| format!("Failed to read package.json: {}", e))?;
let json: serde_json::Value = serde_json::from_str(&contents)
.map_err(|e| format!("Failed to parse package.json: {}", e))?;
let parent = path.parent().unwrap_or(path);
let runner = match crate::runners::runners_package_json::detect_package_manager(parent) {
Some(runner) => runner,
None => {
#[cfg(test)]
{
if std::env::var("MOCK_NO_PM").is_ok() {
return Ok(vec![]);
}
}
TaskRunner::NodeNpm
}
};
let mut tasks = Vec::new();
if let Some(scripts) = json.get("scripts") {
if let Some(scripts_obj) = scripts.as_object() {
for (name, cmd) in scripts_obj {
tasks.push(Task {
name: name.clone(),
file_path: path.clone(),
definition_type: TaskDefinitionType::PackageJson,
runner: runner.clone(),
source_name: name.clone(),
description: cmd.as_str().map(|s| s.to_string()),
shadowed_by: None,
disambiguated_name: None,
});
}
}
}
#[cfg(test)]
{
if std::env::var("MOCK_NO_PM").is_ok() {
return Ok(vec![]);
}
}
Ok(tasks)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::environment::{reset_to_real_environment, set_test_environment, TestEnvironment};
use crate::task_shadowing::{enable_mock, reset_mock};
use serial_test::serial;
use std::env;
use std::fs::File;
use std::io::Write;
use tempfile::TempDir;
#[test]
#[serial]
fn test_parse_package_json() {
let temp_dir = TempDir::new().unwrap();
let package_json_path = temp_dir.path().join("package.json");
reset_mock();
enable_mock();
set_test_environment(TestEnvironment::new().with_executable("npm"));
{
let lock_path = temp_dir.path().join("package-lock.json");
let mut lock_file = File::create(&lock_path).unwrap();
lock_file.write_all(b"{}").unwrap();
lock_file.sync_all().unwrap();
assert!(
std::fs::metadata(&lock_path).is_ok(),
"package-lock.json should exist"
);
}
let content = r#"{
"name": "test-package",
"scripts": {
"test": "jest",
"build": "tsc"
}
}"#;
File::create(&package_json_path)
.unwrap()
.write_all(content.as_bytes())
.unwrap();
let tasks = parse(&package_json_path).unwrap();
assert_eq!(tasks.len(), 2);
let test_task = tasks.iter().find(|t| t.name == "test").unwrap();
assert_eq!(test_task.runner, TaskRunner::NodeNpm);
assert_eq!(test_task.description, Some("jest".to_string()));
let build_task = tasks.iter().find(|t| t.name == "build").unwrap();
assert_eq!(build_task.runner, TaskRunner::NodeNpm);
assert_eq!(build_task.description, Some("tsc".to_string()));
reset_mock();
reset_to_real_environment();
}
#[test]
#[serial]
fn test_parse_package_json_no_scripts() {
let temp_dir = TempDir::new().unwrap();
let package_json_path = temp_dir.path().join("package.json");
reset_mock();
enable_mock();
File::create(temp_dir.path().join("package-lock.json")).unwrap();
let content = r#"{
"name": "test-package"
}"#;
File::create(&package_json_path)
.unwrap()
.write_all(content.as_bytes())
.unwrap();
let tasks = parse(&package_json_path).unwrap();
assert!(tasks.is_empty());
reset_mock();
}
#[test]
#[serial]
fn test_parse_package_json_no_package_manager() {
env::set_var("MOCK_NO_PM", "1");
let temp_dir = TempDir::new().unwrap();
let package_json_path = temp_dir.path().join("package.json");
reset_mock();
enable_mock();
File::create(temp_dir.path().join("package-lock.json")).unwrap();
let content = r#"{
"name": "test-package",
"scripts": {
"test": "jest",
"build": "tsc"
}
}"#;
File::create(&package_json_path)
.unwrap()
.write_all(content.as_bytes())
.unwrap();
let tasks = parse(&package_json_path).unwrap();
assert!(tasks.is_empty());
env::remove_var("MOCK_NO_PM");
reset_mock();
}
}