use std::collections::HashMap;
use std::path::Path;
use anyhow::Result;
use serde::Deserialize;
#[derive(Debug)]
pub(crate) struct DenoTaskPlan {
command: String,
cwd: Option<String>,
has_dependencies: bool,
invokes_deno: bool,
}
impl DenoTaskPlan {
pub(crate) const fn self_executable(&self) -> bool {
!self.has_dependencies && !self.invokes_deno
}
}
pub(crate) fn plan(config_path: &Path, task: &str) -> Option<DenoTaskPlan> {
#[derive(Deserialize)]
struct Partial {
tasks: Option<HashMap<String, serde_json::Value>>,
}
let content = std::fs::read_to_string(config_path).ok()?;
let parsed = json5::from_str::<Partial>(&content).ok()?;
let value = parsed.tasks?.remove(task)?;
let (command, cwd, has_dependencies) = match value {
serde_json::Value::String(command) => (command, None, false),
serde_json::Value::Object(map) => {
let command = map
.get("command")
.and_then(serde_json::Value::as_str)?
.to_string();
let cwd = map
.get("cwd")
.and_then(serde_json::Value::as_str)
.map(str::to_string);
let has_dependencies = map
.get("dependencies")
.and_then(serde_json::Value::as_array)
.is_some_and(|deps| !deps.is_empty());
(command, cwd, has_dependencies)
}
_ => return None,
};
Some(DenoTaskPlan {
invokes_deno: super::shell::mentions_program(&command, "deno"),
command,
cwd,
has_dependencies,
})
}
pub(crate) fn run(plan: &DenoTaskPlan, args: &[String], cwd: &Path) -> Result<i32> {
let effective_cwd = plan
.cwd
.as_ref()
.map_or_else(|| cwd.to_path_buf(), |rel| cwd.join(rel));
super::shell::run(&plan.command, args, &effective_cwd)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tool::test_support::TempDir;
use std::fs;
fn write_config(dir: &TempDir, body: &str) -> std::path::PathBuf {
let path = dir.path().join("deno.json");
fs::write(&path, body).expect("deno.json should be written");
path
}
#[test]
fn plan_string_form_is_self_executable() {
let dir = TempDir::new("deno-exec-string");
let path = write_config(&dir, r#"{ "tasks": { "greet": "echo hi" } }"#);
let plan = plan(&path, "greet").expect("task should plan");
assert!(plan.self_executable());
}
#[test]
fn plan_flags_deno_invocation_as_not_self_executable() {
let dir = TempDir::new("deno-exec-denoword");
let path = write_config(&dir, r#"{ "tasks": { "build": "deno run -A build.ts" } }"#);
let plan = plan(&path, "build").expect("task should plan");
assert!(!plan.self_executable());
}
#[test]
fn plan_flags_dependencies_as_not_self_executable() {
let dir = TempDir::new("deno-exec-deps");
let path = write_config(
&dir,
r#"{ "tasks": { "all": { "command": "echo done", "dependencies": ["build"] } } }"#,
);
let plan = plan(&path, "all").expect("task should plan");
assert!(!plan.self_executable());
}
#[test]
fn plan_returns_none_for_missing_task() {
let dir = TempDir::new("deno-exec-missing");
let path = write_config(&dir, r#"{ "tasks": { "greet": "echo hi" } }"#);
assert!(plan(&path, "absent").is_none());
}
#[test]
fn run_executes_task_without_deno() {
let dir = TempDir::new("deno-exec-run");
let path = write_config(&dir, r#"{ "tasks": { "ok": "exit 0" } }"#);
let plan = plan(&path, "ok").expect("task should plan");
let code = run(&plan, &[], dir.path()).expect("self-exec should run");
assert_eq!(code, 0);
}
}