summaryrefslogtreecommitdiff
path: root/cli/tools/task.rs
diff options
context:
space:
mode:
authorBartek IwaƄczuk <biwanczuk@gmail.com>2022-03-11 02:56:14 +0100
committerGitHub <noreply@github.com>2022-03-10 20:56:14 -0500
commit47f22777beb7eb98a07fa56fbbd40ab5c1fa231e (patch)
treeaa61065f07df3c1ad5a28326801720593a8cdd19 /cli/tools/task.rs
parent808f797633ba82c0e9198481ddd742284a03cb9c (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.rs165
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,
+ );
+ }
+}