1use std::collections::HashMap;
4use std::path::Path;
5
6pub fn get_script_command(script_name: &str, package_json_path: &Path) -> Result<String, String> {
8 let s = std::fs::read_to_string(package_json_path)
9 .map_err(|e| format!("Could not read package.json: {}", e))?;
10 let v: serde_json::Value =
11 serde_json::from_str(&s).map_err(|e| format!("Invalid package.json: {}", e))?;
12 let scripts = v
13 .get("scripts")
14 .and_then(|s| s.as_object())
15 .ok_or_else(|| "package.json has no \"scripts\" object.".to_string())?;
16 let cmd = scripts
17 .get(script_name)
18 .and_then(|c| c.as_str())
19 .map(String::from)
20 .ok_or_else(|| format!("Missing script \"{}\" in package.json.", script_name))?;
21 if cmd.trim().is_empty() {
22 return Err(format!("Script \"{}\" is empty.", script_name));
23 }
24 Ok(cmd)
25}
26
27pub fn run_script(
29 script_name: &str,
30 cwd: &Path,
31 extra_env: Option<HashMap<String, String>>,
32) -> Result<std::process::ExitStatus, String> {
33 let package_json_path = cwd.join("package.json");
34 if !package_json_path.exists() {
35 return Err("No package.json found in current directory.".to_string());
36 }
37 let script_cmd = get_script_command(script_name, &package_json_path)?;
38
39 let bin_dir = cwd.join("node_modules").join(".bin");
40 let path_env = std::env::var("PATH").unwrap_or_default();
41 let new_path = if bin_dir.exists() {
42 let bin_str = bin_dir.to_string_lossy();
43 #[cfg(unix)]
44 let sep = ":";
45 #[cfg(windows)]
46 let sep = ";";
47 format!("{}{}{}", bin_str, sep, path_env)
48 } else {
49 path_env
50 };
51
52 let mut env: HashMap<String, String> = extra_env.unwrap_or_default();
53 env.insert("PATH".to_string(), new_path);
54
55 if let Ok(s) = std::fs::read_to_string(&package_json_path) {
56 if let Ok(pkg_json) = serde_json::from_str::<serde_json::Value>(&s) {
57 if let Some(name) = pkg_json.get("name").and_then(|n| n.as_str()) {
58 env.insert("npm_package_name".to_string(), name.to_string());
59 }
60 if let Some(ver) = pkg_json.get("version").and_then(|v| v.as_str()) {
61 env.insert("npm_package_version".to_string(), ver.to_string());
62 }
63 }
64 }
65
66 #[cfg(unix)]
67 let (shell, shell_arg, script_arg) = {
68 let sh = std::env::var("SHELL").unwrap_or_else(|_| "sh".to_string());
69 (sh, "-c", script_cmd)
70 };
71
72 #[cfg(windows)]
73 let (shell, shell_arg, script_arg) = {
74 ("cmd".to_string(), "/c", script_cmd)
75 };
76
77 let status = std::process::Command::new(&shell)
78 .arg(shell_arg)
79 .arg(script_arg)
80 .current_dir(cwd)
81 .envs(env)
82 .status()
83 .map_err(|e| format!("Failed to run script: {}", e))?;
84
85 Ok(status)
86}