use std::path::{Component, Path};
use crate::process::{ProcessSpec, run};
use crate::project::ProjectConfig;
use super::ScriptError;
use super::file::ScriptFile;
pub(crate) fn execute(
project: &ProjectConfig,
name: &str,
forwarded: &[String],
) -> Result<(), ScriptError> {
if !super::name::valid(name) {
return Err(ScriptError::InvalidName(name.to_owned()));
}
let file = ScriptFile::load(project.root())?;
let entry = file.scripts.get(name).ok_or_else(|| ScriptError::Unknown {
name: name.to_owned(),
available: file.scripts.keys().cloned().collect(),
})?;
if entry.program.is_empty() {
return Err(ScriptError::MissingProgram(name.to_owned()));
}
let relative = entry.cwd.as_deref().unwrap_or_else(|| Path::new("."));
if relative.is_absolute()
|| relative.components().any(|part| {
matches!(
part,
Component::ParentDir | Component::RootDir | Component::Prefix(_)
)
})
{
return Err(ScriptError::InvalidWorkingDirectory(relative.to_path_buf()));
}
let root =
project
.root()
.canonicalize()
.map_err(|source| ScriptError::ResolveWorkingDirectory {
path: project.root().to_path_buf(),
source,
})?;
let directory_path = project.root().join(relative);
let directory =
directory_path
.canonicalize()
.map_err(|source| ScriptError::ResolveWorkingDirectory {
path: directory_path,
source,
})?;
if !directory.starts_with(&root) {
return Err(ScriptError::InvalidWorkingDirectory(relative.to_path_buf()));
}
let mut specification = ProcessSpec::new(&entry.program, directory)
.args(entry.args.iter())
.args(forwarded.iter());
for (key, value) in &entry.env {
specification = specification.env(key, value);
}
run(&specification)?;
Ok(())
}