Skip to main content

o_/
job.rs

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}