diff options
Diffstat (limited to 'cli/tools')
-rw-r--r-- | cli/tools/task.rs | 257 |
1 files changed, 201 insertions, 56 deletions
diff --git a/cli/tools/task.rs b/cli/tools/task.rs index 0b611b9d3..523f2bc88 100644 --- a/cli/tools/task.rs +++ b/cli/tools/task.rs @@ -8,18 +8,16 @@ use crate::util::fs::canonicalize_path; use deno_core::anyhow::bail; use deno_core::anyhow::Context; use deno_core::error::AnyError; -use std::collections::BTreeMap; +use deno_core::futures; +use deno_core::futures::future::LocalBoxFuture; +use deno_graph::npm::NpmPackageNv; +use deno_task_shell::ExecuteResult; +use deno_task_shell::ShellCommand; +use deno_task_shell::ShellCommandContext; +use indexmap::IndexMap; use std::collections::HashMap; use std::path::PathBuf; - -fn print_available_tasks(tasks_config: BTreeMap<String, String>) { - eprintln!("{}", colors::green("Available tasks:")); - - for name in tasks_config.keys() { - eprintln!("- {}", colors::cyan(name)); - eprintln!(" {}", tasks_config[name]) - } -} +use std::rc::Rc; pub async fn execute_script( flags: Flags, @@ -27,62 +25,209 @@ pub async fn execute_script( ) -> Result<i32, AnyError> { let ps = ProcState::build(flags).await?; let tasks_config = ps.options.resolve_tasks_config()?; - let config_file_url = ps.options.maybe_config_file_specifier().unwrap(); - let config_file_path = if config_file_url.scheme() == "file" { - config_file_url.to_file_path().unwrap() - } else { - bail!("Only local configuration files are supported") + let maybe_package_json = ps.options.maybe_package_json(); + let package_json_scripts = maybe_package_json + .as_ref() + .and_then(|p| p.scripts.clone()) + .unwrap_or_default(); + + let task_name = match &task_flags.task { + Some(task) => task, + None => { + print_available_tasks(&tasks_config, &package_json_scripts); + return Ok(1); + } }; - if task_flags.task.is_empty() { - print_available_tasks(tasks_config); - return Ok(1); + if let Some(script) = tasks_config.get(task_name) { + let config_file_url = ps.options.maybe_config_file_specifier().unwrap(); + let config_file_path = if config_file_url.scheme() == "file" { + config_file_url.to_file_path().unwrap() + } else { + bail!("Only local configuration files are supported") + }; + let cwd = match task_flags.cwd { + Some(path) => canonicalize_path(&PathBuf::from(path))?, + None => config_file_path.parent().unwrap().to_owned(), + }; + let script = get_script_with_args(script, &ps); + output_task(task_name, &script); + let seq_list = deno_task_shell::parser::parse(&script) + .with_context(|| format!("Error parsing script '{task_name}'."))?; + let env_vars = collect_env_vars(); + let exit_code = + deno_task_shell::execute(seq_list, env_vars, &cwd, Default::default()) + .await; + Ok(exit_code) + } else if let Some(script) = package_json_scripts.get(task_name) { + let cwd = match task_flags.cwd { + Some(path) => canonicalize_path(&PathBuf::from(path))?, + None => maybe_package_json + .as_ref() + .unwrap() + .path + .parent() + .unwrap() + .to_owned(), + }; + let script = get_script_with_args(script, &ps); + output_task(task_name, &script); + let seq_list = deno_task_shell::parser::parse(&script) + .with_context(|| format!("Error parsing script '{task_name}'."))?; + let npx_commands = resolve_npm_commands(&ps)?; + let env_vars = collect_env_vars(); + let exit_code = + deno_task_shell::execute(seq_list, env_vars, &cwd, npx_commands).await; + Ok(exit_code) + } else { + eprintln!("Task not found: {task_name}"); + print_available_tasks(&tasks_config, &package_json_scripts); + Ok(1) } +} - let cwd = match task_flags.cwd { - Some(path) => canonicalize_path(&PathBuf::from(path))?, - None => config_file_path.parent().unwrap().to_owned(), - }; - let task_name = task_flags.task; - let maybe_script = tasks_config.get(&task_name); +fn get_script_with_args(script: &str, ps: &ProcState) -> String { + let additional_args = ps + .options + .argv() + .iter() + // surround all the additional arguments in double quotes + // and santize any command substition + .map(|a| format!("\"{}\"", a.replace('"', "\\\"").replace('$', "\\$"))) + .collect::<Vec<_>>() + .join(" "); + let script = format!("{script} {additional_args}"); + script.trim().to_owned() +} - if let Some(script) = maybe_script { - let additional_args = ps - .options - .argv() +fn output_task(task_name: &str, script: &str) { + log::info!( + "{} {} {}", + colors::green("Task"), + colors::cyan(&task_name), + script, + ); +} + +fn collect_env_vars() -> HashMap<String, String> { + // get the starting env vars (the PWD env var will be set by deno_task_shell) + let mut env_vars = std::env::vars().collect::<HashMap<String, String>>(); + const INIT_CWD_NAME: &str = "INIT_CWD"; + if !env_vars.contains_key(INIT_CWD_NAME) { + if let Ok(cwd) = std::env::current_dir() { + // if not set, set an INIT_CWD env var that has the cwd + env_vars + .insert(INIT_CWD_NAME.to_string(), cwd.to_string_lossy().to_string()); + } + } + env_vars +} + +fn print_available_tasks( + // order can be important, so these use an index map + tasks_config: &IndexMap<String, String>, + package_json_scripts: &IndexMap<String, String>, +) { + eprintln!("{}", colors::green("Available tasks:")); + + let mut had_task = false; + for (is_deno, (key, value)) in tasks_config.iter().map(|e| (true, e)).chain( + package_json_scripts .iter() - // surround all the additional arguments in double quotes - // and santize any command substition - .map(|a| format!("\"{}\"", a.replace('"', "\\\"").replace('$', "\\$"))) - .collect::<Vec<_>>() - .join(" "); - let script = format!("{script} {additional_args}"); - let script = script.trim(); - log::info!( - "{} {} {}", - colors::green("Task"), - colors::cyan(&task_name), - script, + .filter(|(key, _)| !tasks_config.contains_key(*key)) + .map(|e| (false, e)), + ) { + eprintln!( + "- {}{}", + colors::cyan(key), + if is_deno { + "".to_string() + } else { + format!(" {}", colors::italic_gray("(package.json)")) + } ); - let seq_list = deno_task_shell::parser::parse(script) - .with_context(|| format!("Error parsing script '{task_name}'."))?; + eprintln!(" {value}"); + had_task = true; + } + if !had_task { + eprintln!(" {}", colors::red("No tasks found in configuration file")); + } +} + +struct NpxCommand; - // get the starting env vars (the PWD env var will be set by deno_task_shell) - let mut env_vars = std::env::vars().collect::<HashMap<String, String>>(); - const INIT_CWD_NAME: &str = "INIT_CWD"; - if !env_vars.contains_key(INIT_CWD_NAME) { - if let Ok(cwd) = std::env::current_dir() { - // if not set, set an INIT_CWD env var that has the cwd - env_vars - .insert(INIT_CWD_NAME.to_string(), cwd.to_string_lossy().to_string()); +impl ShellCommand for NpxCommand { + fn execute( + &self, + mut context: ShellCommandContext, + ) -> LocalBoxFuture<'static, ExecuteResult> { + if let Some(first_arg) = context.args.get(0).cloned() { + if let Some(command) = context.state.resolve_command(&first_arg) { + let context = ShellCommandContext { + args: context.args.iter().skip(1).cloned().collect::<Vec<_>>(), + ..context + }; + command.execute(context) + } else { + let _ = context + .stderr + .write_line(&format!("npx: could not resolve command '{first_arg}'")); + Box::pin(futures::future::ready(ExecuteResult::from_exit_code(1))) } + } else { + let _ = context.stderr.write_line("npx: missing command"); + Box::pin(futures::future::ready(ExecuteResult::from_exit_code(1))) } + } +} - let exit_code = deno_task_shell::execute(seq_list, env_vars, &cwd).await; - Ok(exit_code) - } else { - eprintln!("Task not found: {task_name}"); - print_available_tasks(tasks_config); - Ok(1) +#[derive(Clone)] +struct NpmPackageBinCommand { + name: String, + npm_package: NpmPackageNv, +} + +impl ShellCommand for NpmPackageBinCommand { + fn execute( + &self, + context: ShellCommandContext, + ) -> LocalBoxFuture<'static, ExecuteResult> { + let mut args = vec![ + "run".to_string(), + "-A".to_string(), + if self.npm_package.name == self.name { + format!("npm:{}", self.npm_package) + } else { + format!("npm:{}/{}", self.npm_package, self.name) + }, + ]; + args.extend(context.args); + let executable_command = + deno_task_shell::ExecutableCommand::new("deno".to_string()); + executable_command.execute(ShellCommandContext { args, ..context }) + } +} + +fn resolve_npm_commands( + ps: &ProcState, +) -> Result<HashMap<String, Rc<dyn ShellCommand>>, AnyError> { + let mut result = HashMap::new(); + let snapshot = ps.npm_resolver.snapshot(); + for id in snapshot.top_level_packages() { + let bin_commands = + crate::node::node_resolve_binary_commands(&id.nv, &ps.npm_resolver)?; + for bin_command in bin_commands { + result.insert( + bin_command.to_string(), + Rc::new(NpmPackageBinCommand { + name: bin_command, + npm_package: id.nv.clone(), + }) as Rc<dyn ShellCommand>, + ); + } + } + if !result.contains_key("npx") { + result.insert("npx".to_string(), Rc::new(NpxCommand)); } + Ok(result) } |