1use crate::pm::{Manifest, read_manifest};
2use crate::report::Report;
3use std::io;
4use std::path::PathBuf;
5use std::process::{Command, Stdio};
6
7#[derive(Debug)]
8pub enum JobError {
9 ReadManifest {
10 path: PathBuf,
11 source: io::Error,
12 },
13 MissingScript {
14 name: String,
15 },
16 SpawnScript {
17 name: String,
18 command: String,
19 source: io::Error,
20 },
21 ScriptFailed {
22 name: String,
23 command: String,
24 status: String,
25 },
26}
27
28impl JobError {
29 pub fn report(&self) -> Report {
30 match self {
31 Self::ReadManifest { path, source } => Report::new("failed to read package.json")
32 .detail(format!("path: {}", path.display()))
33 .detail(format!("cause: {source}")),
34 Self::MissingScript { name } => Report::new(format!("script `{name}` is not defined"))
35 .detail("add it to the `scripts` section in package.json"),
36 Self::SpawnScript {
37 name,
38 command,
39 source,
40 } => Report::new(format!("failed to start script `{name}`"))
41 .detail(format!("command: {command}"))
42 .detail(format!("cause: {source}")),
43 Self::ScriptFailed {
44 name,
45 command,
46 status,
47 } => Report::new(format!("script `{name}` failed"))
48 .detail(format!("command: {command}"))
49 .detail(format!("status: {status}")),
50 }
51 }
52}
53
54pub fn run(name: &str) -> Result<(), JobError> {
55 let manifest_path = std::env::current_dir()
56 .map_err(|source| JobError::ReadManifest {
57 path: PathBuf::from("."),
58 source,
59 })?
60 .join("package.json");
61 let manifest = read_manifest(".").map_err(|source| JobError::ReadManifest {
62 path: manifest_path.clone(),
63 source,
64 })?;
65 run_with_manifest(name, &manifest)
66}
67
68fn run_with_manifest(name: &str, manifest: &Manifest) -> Result<(), JobError> {
69 let Some(scripts) = manifest.scripts.as_ref() else {
70 return Err(JobError::MissingScript {
71 name: name.to_string(),
72 });
73 };
74
75 let Some(command) = scripts.get(name) else {
76 return Err(JobError::MissingScript {
77 name: name.to_string(),
78 });
79 };
80
81 let status = shell_command(command)
82 .stdin(Stdio::inherit())
83 .stdout(Stdio::inherit())
84 .stderr(Stdio::inherit())
85 .status()
86 .map_err(|source| JobError::SpawnScript {
87 name: name.to_string(),
88 command: command.clone(),
89 source,
90 })?;
91
92 if !status.success() {
93 return Err(JobError::ScriptFailed {
94 name: name.to_string(),
95 command: command.clone(),
96 status: status
97 .code()
98 .map(|code| code.to_string())
99 .unwrap_or_else(|| status.to_string()),
100 });
101 }
102
103 Ok(())
104}
105
106fn shell_command(command: &str) -> Command {
107 if cfg!(windows) {
108 let mut shell = Command::new("cmd");
109 shell.arg("/C").arg(command);
110 shell
111 } else {
112 let mut shell = Command::new("sh");
113 shell.arg("-c").arg(command);
114 shell
115 }
116}