diff options
author | Bartek IwaĆczuk <biwanczuk@gmail.com> | 2022-03-11 02:56:14 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-03-10 20:56:14 -0500 |
commit | 47f22777beb7eb98a07fa56fbbd40ab5c1fa231e (patch) | |
tree | aa61065f07df3c1ad5a28326801720593a8cdd19 /cli/tools/task.rs | |
parent | 808f797633ba82c0e9198481ddd742284a03cb9c (diff) |
feat: "deno task" subcommand (#13725)
Co-authored-by: David Sherret <dsherret@gmail.com>
Diffstat (limited to 'cli/tools/task.rs')
-rw-r--r-- | cli/tools/task.rs | 165 |
1 files changed, 165 insertions, 0 deletions
diff --git a/cli/tools/task.rs b/cli/tools/task.rs new file mode 100644 index 000000000..3cfce107e --- /dev/null +++ b/cli/tools/task.rs @@ -0,0 +1,165 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use crate::colors; +use crate::config_file::ConfigFile; +use crate::flags::Flags; +use crate::flags::TaskFlags; +use crate::proc_state::ProcState; +use deno_core::anyhow::bail; +use deno_core::anyhow::Context; +use deno_core::error::AnyError; +use std::collections::BTreeMap; +use std::collections::HashMap; +use std::sync::Arc; + +fn get_tasks_config( + maybe_config_file: Option<&ConfigFile>, +) -> Result<BTreeMap<String, String>, AnyError> { + if let Some(config_file) = maybe_config_file { + let maybe_tasks_config = config_file.to_tasks_config()?; + if let Some(tasks_config) = maybe_tasks_config { + for key in tasks_config.keys() { + if key.is_empty() { + bail!("Configuration file task names cannot be empty"); + } else if !key + .chars() + .all(|c| c.is_ascii_alphanumeric() || matches!(c, '_' | '-')) + { + bail!("Configuration file task names must only contain alpha-numeric characters, underscores (_), or dashes (-). Task: {}", key); + } else if !key.chars().next().unwrap().is_ascii_alphabetic() { + bail!("Configuration file task names must start with an alphabetic character. Task: {}", key); + } + } + Ok(tasks_config) + } else { + bail!("No tasks found in configuration file") + } + } else { + bail!("No config file found") + } +} + +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]) + } +} + +pub async fn execute_script( + flags: Flags, + task_flags: TaskFlags, +) -> Result<i32, AnyError> { + let flags = Arc::new(flags); + let ps = ProcState::build(flags.clone()).await?; + let tasks_config = get_tasks_config(ps.maybe_config_file.as_ref())?; + let config_file_url = &ps.maybe_config_file.as_ref().unwrap().specifier; + 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") + }; + + if task_flags.task.is_empty() { + print_available_tasks(tasks_config); + return Ok(1); + } + + let cwd = config_file_path.parent().unwrap(); + let task_name = task_flags.task; + let maybe_script = tasks_config.get(&task_name); + + if let Some(script) = maybe_script { + let additional_args = flags + .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); + let seq_list = deno_task_shell::parser::parse(&script) + .with_context(|| format!("Error parsing script '{}'.", task_name))?; + let env_vars = std::env::vars().collect::<HashMap<String, String>>(); + 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) + } +} + +#[cfg(test)] +mod test { + use deno_ast::ModuleSpecifier; + use pretty_assertions::assert_eq; + + use super::*; + + #[test] + fn tasks_no_tasks() { + run_task_error_test(r#"{}"#, "No tasks found in configuration file"); + } + + #[test] + fn task_name_invalid_chars() { + run_task_error_test( + r#"{ + "tasks": { + "build": "deno test", + "some%test": "deno bundle mod.ts" + } + }"#, + concat!( + "Configuration file task names must only contain alpha-numeric ", + "characters, underscores (_), or dashes (-). Task: some%test", + ), + ); + } + + #[test] + fn task_name_non_alpha_starting_char() { + run_task_error_test( + r#"{ + "tasks": { + "build": "deno test", + "1test": "deno bundle mod.ts" + } + }"#, + concat!( + "Configuration file task names must start with an ", + "alphabetic character. Task: 1test", + ), + ); + } + + #[test] + fn task_name_empty() { + run_task_error_test( + r#"{ + "tasks": { + "build": "deno test", + "": "deno bundle mod.ts" + } + }"#, + "Configuration file task names cannot be empty", + ); + } + + fn run_task_error_test(config_text: &str, expected_error: &str) { + let config_dir = ModuleSpecifier::parse("file:///deno/").unwrap(); + let config_specifier = config_dir.join("tsconfig.json").unwrap(); + let config_file = ConfigFile::new(config_text, &config_specifier).unwrap(); + assert_eq!( + get_tasks_config(Some(&config_file)) + .err() + .unwrap() + .to_string(), + expected_error, + ); + } +} |