summaryrefslogtreecommitdiff
path: root/cli/tools
diff options
context:
space:
mode:
Diffstat (limited to 'cli/tools')
-rw-r--r--cli/tools/task.rs257
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)
}