mirror of
https://github.com/denoland/deno.git
synced 2025-02-01 12:16:11 -05:00
feat: "deno task" subcommand (#13725)
Co-authored-by: David Sherret <dsherret@gmail.com>
This commit is contained in:
parent
808f797633
commit
47f22777be
20 changed files with 468 additions and 5 deletions
31
Cargo.lock
generated
31
Cargo.lock
generated
|
@ -750,6 +750,7 @@ dependencies = [
|
||||||
"deno_lint",
|
"deno_lint",
|
||||||
"deno_net",
|
"deno_net",
|
||||||
"deno_runtime",
|
"deno_runtime",
|
||||||
|
"deno_task_shell",
|
||||||
"deno_url",
|
"deno_url",
|
||||||
"deno_web",
|
"deno_web",
|
||||||
"deno_webgpu",
|
"deno_webgpu",
|
||||||
|
@ -778,7 +779,7 @@ dependencies = [
|
||||||
"os_pipe",
|
"os_pipe",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project",
|
"pin-project",
|
||||||
"pretty_assertions",
|
"pretty_assertions 0.7.2",
|
||||||
"rand 0.8.4",
|
"rand 0.8.4",
|
||||||
"regex",
|
"regex",
|
||||||
"ring",
|
"ring",
|
||||||
|
@ -1065,6 +1066,18 @@ dependencies = [
|
||||||
"winres",
|
"winres",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deno_task_shell"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ad23aacc3db4f37eb88fb9c874a85a4abe5eab14a98fc070ee8df9e204f243e6"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"futures",
|
||||||
|
"pretty_assertions 1.1.0",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deno_tls"
|
name = "deno_tls"
|
||||||
version = "0.27.0"
|
version = "0.27.0"
|
||||||
|
@ -2252,9 +2265,9 @@ checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
version = "0.4.5"
|
version = "0.4.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109"
|
checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"scopeguard",
|
"scopeguard",
|
||||||
]
|
]
|
||||||
|
@ -2896,6 +2909,18 @@ dependencies = [
|
||||||
"output_vt100",
|
"output_vt100",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pretty_assertions"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "76d5b548b725018ab5496482b45cb8bef21e9fed1858a6d674e3a8a0f0bb5d50"
|
||||||
|
dependencies = [
|
||||||
|
"ansi_term",
|
||||||
|
"ctor",
|
||||||
|
"diff",
|
||||||
|
"output_vt100",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro-error"
|
name = "proc-macro-error"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
|
|
|
@ -51,6 +51,7 @@ deno_doc = "0.32.0"
|
||||||
deno_graph = "0.24.0"
|
deno_graph = "0.24.0"
|
||||||
deno_lint = { version = "0.26.0", features = ["docs"] }
|
deno_lint = { version = "0.26.0", features = ["docs"] }
|
||||||
deno_runtime = { version = "0.48.0", path = "../runtime" }
|
deno_runtime = { version = "0.48.0", path = "../runtime" }
|
||||||
|
deno_task_shell = "0.1.6"
|
||||||
|
|
||||||
atty = "=0.2.14"
|
atty = "=0.2.14"
|
||||||
base64 = "=0.13.0"
|
base64 = "=0.13.0"
|
||||||
|
|
|
@ -530,6 +530,7 @@ pub struct ConfigFileJson {
|
||||||
pub import_map: Option<String>,
|
pub import_map: Option<String>,
|
||||||
pub lint: Option<Value>,
|
pub lint: Option<Value>,
|
||||||
pub fmt: Option<Value>,
|
pub fmt: Option<Value>,
|
||||||
|
pub tasks: Option<Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -648,6 +649,19 @@ impl ConfigFile {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn to_tasks_config(
|
||||||
|
&self,
|
||||||
|
) -> Result<Option<BTreeMap<String, String>>, AnyError> {
|
||||||
|
if let Some(config) = self.json.tasks.clone() {
|
||||||
|
let tasks_config: BTreeMap<String, String> =
|
||||||
|
serde_json::from_value(config)
|
||||||
|
.context("Failed to parse \"tasks\" configuration")?;
|
||||||
|
Ok(Some(tasks_config))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// If the configuration file contains "extra" modules (like TypeScript
|
/// If the configuration file contains "extra" modules (like TypeScript
|
||||||
/// `"types"`) options, return them as imports to be added to a module graph.
|
/// `"types"`) options, return them as imports to be added to a module graph.
|
||||||
pub fn to_maybe_imports(&self) -> MaybeImportsResult {
|
pub fn to_maybe_imports(&self) -> MaybeImportsResult {
|
||||||
|
@ -784,6 +798,10 @@ mod tests {
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"proseWrap": "preserve"
|
"proseWrap": "preserve"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"tasks": {
|
||||||
|
"build": "deno run --allow-read --allow-write build.ts",
|
||||||
|
"server": "deno run --allow-net --allow-read server.ts"
|
||||||
}
|
}
|
||||||
}"#;
|
}"#;
|
||||||
let config_dir = ModuleSpecifier::parse("file:///deno/").unwrap();
|
let config_dir = ModuleSpecifier::parse("file:///deno/").unwrap();
|
||||||
|
@ -841,6 +859,16 @@ mod tests {
|
||||||
assert_eq!(fmt_config.options.line_width, Some(80));
|
assert_eq!(fmt_config.options.line_width, Some(80));
|
||||||
assert_eq!(fmt_config.options.indent_width, Some(4));
|
assert_eq!(fmt_config.options.indent_width, Some(4));
|
||||||
assert_eq!(fmt_config.options.single_quote, Some(true));
|
assert_eq!(fmt_config.options.single_quote, Some(true));
|
||||||
|
|
||||||
|
let tasks_config = config_file.to_tasks_config().unwrap().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
tasks_config["build"],
|
||||||
|
"deno run --allow-read --allow-write build.ts",
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
tasks_config["server"],
|
||||||
|
"deno run --allow-net --allow-read server.ts"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
103
cli/flags.rs
103
cli/flags.rs
|
@ -139,6 +139,11 @@ pub struct RunFlags {
|
||||||
pub script: String,
|
pub script: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||||
|
pub struct TaskFlags {
|
||||||
|
pub task: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||||
pub struct TestFlags {
|
pub struct TestFlags {
|
||||||
pub ignore: Vec<PathBuf>,
|
pub ignore: Vec<PathBuf>,
|
||||||
|
@ -187,6 +192,7 @@ pub enum DenoSubcommand {
|
||||||
Lint(LintFlags),
|
Lint(LintFlags),
|
||||||
Repl(ReplFlags),
|
Repl(ReplFlags),
|
||||||
Run(RunFlags),
|
Run(RunFlags),
|
||||||
|
Task(TaskFlags),
|
||||||
Test(TestFlags),
|
Test(TestFlags),
|
||||||
Types,
|
Types,
|
||||||
Upgrade(UpgradeFlags),
|
Upgrade(UpgradeFlags),
|
||||||
|
@ -500,6 +506,7 @@ pub fn flags_from_vec(args: Vec<String>) -> clap::Result<Flags> {
|
||||||
Some(("compile", m)) => compile_parse(&mut flags, m),
|
Some(("compile", m)) => compile_parse(&mut flags, m),
|
||||||
Some(("lsp", m)) => lsp_parse(&mut flags, m),
|
Some(("lsp", m)) => lsp_parse(&mut flags, m),
|
||||||
Some(("vendor", m)) => vendor_parse(&mut flags, m),
|
Some(("vendor", m)) => vendor_parse(&mut flags, m),
|
||||||
|
Some(("task", m)) => task_parse(&mut flags, m),
|
||||||
_ => handle_repl_flags(&mut flags, ReplFlags { eval: None }),
|
_ => handle_repl_flags(&mut flags, ReplFlags { eval: None }),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -568,6 +575,7 @@ If the flag is set, restrict these messages to errors.",
|
||||||
.subcommand(lint_subcommand())
|
.subcommand(lint_subcommand())
|
||||||
.subcommand(repl_subcommand())
|
.subcommand(repl_subcommand())
|
||||||
.subcommand(run_subcommand())
|
.subcommand(run_subcommand())
|
||||||
|
.subcommand(task_subcommand())
|
||||||
.subcommand(test_subcommand())
|
.subcommand(test_subcommand())
|
||||||
.subcommand(types_subcommand())
|
.subcommand(types_subcommand())
|
||||||
.subcommand(upgrade_subcommand())
|
.subcommand(upgrade_subcommand())
|
||||||
|
@ -1256,6 +1264,25 @@ Deno allows specifying the filename '-' to read the file from stdin.
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn task_subcommand<'a>() -> App<'a> {
|
||||||
|
App::new("task")
|
||||||
|
.setting(AppSettings::TrailingVarArg)
|
||||||
|
.arg(config_arg())
|
||||||
|
.arg(Arg::new("task").help("Task to be executed"))
|
||||||
|
.arg(
|
||||||
|
Arg::new("task_args")
|
||||||
|
.multiple_values(true)
|
||||||
|
.multiple_occurrences(true)
|
||||||
|
.help("Additional arguments passed to the task"),
|
||||||
|
)
|
||||||
|
.about("Run a task defined in the configuration file")
|
||||||
|
.long_about(
|
||||||
|
"Run a task defined in the configuration file
|
||||||
|
|
||||||
|
deno task build",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn test_subcommand<'a>() -> App<'a> {
|
fn test_subcommand<'a>() -> App<'a> {
|
||||||
runtime_args(App::new("test"), true, true)
|
runtime_args(App::new("test"), true, true)
|
||||||
.setting(AppSettings::TrailingVarArg)
|
.setting(AppSettings::TrailingVarArg)
|
||||||
|
@ -2197,6 +2224,26 @@ fn run_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
|
||||||
flags.subcommand = DenoSubcommand::Run(RunFlags { script });
|
flags.subcommand = DenoSubcommand::Run(RunFlags { script });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn task_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
|
||||||
|
config_arg_parse(flags, matches);
|
||||||
|
|
||||||
|
let mut task_name = "".to_string();
|
||||||
|
if let Some(task) = matches.value_of("task") {
|
||||||
|
task_name = task.to_string();
|
||||||
|
|
||||||
|
let task_args: Vec<String> = matches
|
||||||
|
.values_of("task_args")
|
||||||
|
.unwrap_or_default()
|
||||||
|
.map(String::from)
|
||||||
|
.collect();
|
||||||
|
for v in task_args {
|
||||||
|
flags.argv.push(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flags.subcommand = DenoSubcommand::Task(TaskFlags { task: task_name });
|
||||||
|
}
|
||||||
|
|
||||||
fn test_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
|
fn test_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
|
||||||
runtime_args_parse(flags, matches, true, true);
|
runtime_args_parse(flags, matches, true, true);
|
||||||
// NOTE: `deno test` always uses `--no-prompt`, tests shouldn't ever do
|
// NOTE: `deno test` always uses `--no-prompt`, tests shouldn't ever do
|
||||||
|
@ -5063,4 +5110,60 @@ mod tests {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn task_subcommand() {
|
||||||
|
let r =
|
||||||
|
flags_from_vec(svec!["deno", "task", "build", "--", "hello", "world",]);
|
||||||
|
assert_eq!(
|
||||||
|
r.unwrap(),
|
||||||
|
Flags {
|
||||||
|
subcommand: DenoSubcommand::Task(TaskFlags {
|
||||||
|
task: "build".to_string(),
|
||||||
|
}),
|
||||||
|
argv: svec!["hello", "world"],
|
||||||
|
..Flags::default()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let r = flags_from_vec(svec!["deno", "task", "build"]);
|
||||||
|
assert_eq!(
|
||||||
|
r.unwrap(),
|
||||||
|
Flags {
|
||||||
|
subcommand: DenoSubcommand::Task(TaskFlags {
|
||||||
|
task: "build".to_string(),
|
||||||
|
}),
|
||||||
|
..Flags::default()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn task_subcommand_empty() {
|
||||||
|
let r = flags_from_vec(svec!["deno", "task",]);
|
||||||
|
assert_eq!(
|
||||||
|
r.unwrap(),
|
||||||
|
Flags {
|
||||||
|
subcommand: DenoSubcommand::Task(TaskFlags {
|
||||||
|
task: "".to_string(),
|
||||||
|
}),
|
||||||
|
..Flags::default()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn task_subcommand_config() {
|
||||||
|
let r = flags_from_vec(svec!["deno", "task", "--config", "deno.jsonc"]);
|
||||||
|
assert_eq!(
|
||||||
|
r.unwrap(),
|
||||||
|
Flags {
|
||||||
|
subcommand: DenoSubcommand::Task(TaskFlags {
|
||||||
|
task: "".to_string(),
|
||||||
|
}),
|
||||||
|
config_path: Some("deno.jsonc".to_string()),
|
||||||
|
..Flags::default()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
11
cli/main.rs
11
cli/main.rs
|
@ -56,6 +56,7 @@ use crate::flags::InstallFlags;
|
||||||
use crate::flags::LintFlags;
|
use crate::flags::LintFlags;
|
||||||
use crate::flags::ReplFlags;
|
use crate::flags::ReplFlags;
|
||||||
use crate::flags::RunFlags;
|
use crate::flags::RunFlags;
|
||||||
|
use crate::flags::TaskFlags;
|
||||||
use crate::flags::TestFlags;
|
use crate::flags::TestFlags;
|
||||||
use crate::flags::UninstallFlags;
|
use crate::flags::UninstallFlags;
|
||||||
use crate::flags::UpgradeFlags;
|
use crate::flags::UpgradeFlags;
|
||||||
|
@ -1228,6 +1229,13 @@ async fn run_command(
|
||||||
Ok(worker.get_exit_code())
|
Ok(worker.get_exit_code())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn task_command(
|
||||||
|
flags: Flags,
|
||||||
|
task_flags: TaskFlags,
|
||||||
|
) -> Result<i32, AnyError> {
|
||||||
|
tools::task::execute_script(flags, task_flags).await
|
||||||
|
}
|
||||||
|
|
||||||
async fn coverage_command(
|
async fn coverage_command(
|
||||||
flags: Flags,
|
flags: Flags,
|
||||||
coverage_flags: CoverageFlags,
|
coverage_flags: CoverageFlags,
|
||||||
|
@ -1360,6 +1368,9 @@ fn get_subcommand(
|
||||||
DenoSubcommand::Run(run_flags) => {
|
DenoSubcommand::Run(run_flags) => {
|
||||||
run_command(flags, run_flags).boxed_local()
|
run_command(flags, run_flags).boxed_local()
|
||||||
}
|
}
|
||||||
|
DenoSubcommand::Task(task_flags) => {
|
||||||
|
task_command(flags, task_flags).boxed_local()
|
||||||
|
}
|
||||||
DenoSubcommand::Test(test_flags) => {
|
DenoSubcommand::Test(test_flags) => {
|
||||||
test_command(flags, test_flags).boxed_local()
|
test_command(flags, test_flags).boxed_local()
|
||||||
}
|
}
|
||||||
|
|
|
@ -310,6 +310,17 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"tasks": {
|
||||||
|
"description": "Configuration for deno task",
|
||||||
|
"type": "object",
|
||||||
|
"patternProperties": {
|
||||||
|
"^[A-Za-z][A-Za-z0-9_\\-]*$": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Command to execute for this task name."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,6 +80,8 @@ mod lsp;
|
||||||
mod repl;
|
mod repl;
|
||||||
#[path = "run_tests.rs"]
|
#[path = "run_tests.rs"]
|
||||||
mod run;
|
mod run;
|
||||||
|
#[path = "task_tests.rs"]
|
||||||
|
mod task;
|
||||||
#[path = "test_tests.rs"]
|
#[path = "test_tests.rs"]
|
||||||
mod test;
|
mod test;
|
||||||
#[path = "upgrade_tests.rs"]
|
#[path = "upgrade_tests.rs"]
|
||||||
|
|
71
cli/tests/integration/task_tests.rs
Normal file
71
cli/tests/integration/task_tests.rs
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
use crate::itest;
|
||||||
|
|
||||||
|
// Most of the tests for this are in deno_task_shell.
|
||||||
|
// These tests are intended to only test integration.
|
||||||
|
|
||||||
|
itest!(task_no_args {
|
||||||
|
args: "task --config task/deno.json",
|
||||||
|
output: "task/task_no_args.out",
|
||||||
|
envs: vec![("NO_COLOR".to_string(), "1".to_string())],
|
||||||
|
exit_code: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
itest!(task_non_existent {
|
||||||
|
args: "task --config task/deno.json non_existent",
|
||||||
|
output: "task/task_non_existent.out",
|
||||||
|
envs: vec![("NO_COLOR".to_string(), "1".to_string())],
|
||||||
|
exit_code: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
itest!(task_boolean_logic {
|
||||||
|
args: "task --config task/deno.json boolean_logic",
|
||||||
|
output: "task/task_boolean_logic.out",
|
||||||
|
envs: vec![("NO_COLOR".to_string(), "1".to_string())],
|
||||||
|
});
|
||||||
|
|
||||||
|
itest!(task_exit_code_1 {
|
||||||
|
args: "task --config task/deno.json exit_code_5",
|
||||||
|
output: "task/task_exit_code_5.out",
|
||||||
|
envs: vec![("NO_COLOR".to_string(), "1".to_string())],
|
||||||
|
exit_code: 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
itest!(task_additional_args {
|
||||||
|
args: "task --config task/deno.json echo 2",
|
||||||
|
output: "task/task_additional_args.out",
|
||||||
|
envs: vec![("NO_COLOR".to_string(), "1".to_string())],
|
||||||
|
});
|
||||||
|
|
||||||
|
itest!(task_additional_args_no_shell_expansion {
|
||||||
|
args_vec: vec!["task", "--config", "task/deno.json", "echo", "$(echo 5)"],
|
||||||
|
output: "task/task_additional_args_no_shell_expansion.out",
|
||||||
|
envs: vec![("NO_COLOR".to_string(), "1".to_string())],
|
||||||
|
});
|
||||||
|
|
||||||
|
itest!(task_additional_args_nested_strings {
|
||||||
|
args_vec: vec![
|
||||||
|
"task",
|
||||||
|
"--config",
|
||||||
|
"task/deno.json",
|
||||||
|
"echo",
|
||||||
|
"string \"quoted string\""
|
||||||
|
],
|
||||||
|
output: "task/task_additional_args_nested_strings.out",
|
||||||
|
envs: vec![("NO_COLOR".to_string(), "1".to_string())],
|
||||||
|
});
|
||||||
|
|
||||||
|
itest!(task_additional_args_no_logic {
|
||||||
|
args_vec: vec![
|
||||||
|
"task",
|
||||||
|
"--config",
|
||||||
|
"task/deno.json",
|
||||||
|
"echo",
|
||||||
|
"||",
|
||||||
|
"echo",
|
||||||
|
"5"
|
||||||
|
],
|
||||||
|
output: "task/task_additional_args_no_logic.out",
|
||||||
|
envs: vec![("NO_COLOR".to_string(), "1".to_string())],
|
||||||
|
});
|
8
cli/tests/testdata/task/deno.json
vendored
Normal file
8
cli/tests/testdata/task/deno.json
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"boolean_logic": "sleep 0.1 && echo 3 && echo 4 & echo 1 && echo 2 || echo NOPE",
|
||||||
|
"echo": "echo 1",
|
||||||
|
"strings": "deno run main.ts && deno eval \"console.log(\\\"test\\\")\"",
|
||||||
|
"exit_code_5": "echo $(echo 10 ; exit 2) && exit 5"
|
||||||
|
}
|
||||||
|
}
|
1
cli/tests/testdata/task/task_additional_args.out
vendored
Normal file
1
cli/tests/testdata/task/task_additional_args.out
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
1 2
|
1
cli/tests/testdata/task/task_additional_args_nested_strings.out
vendored
Normal file
1
cli/tests/testdata/task/task_additional_args_nested_strings.out
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
1 string "quoted string"
|
1
cli/tests/testdata/task/task_additional_args_no_logic.out
vendored
Normal file
1
cli/tests/testdata/task/task_additional_args_no_logic.out
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
1 || echo 5
|
1
cli/tests/testdata/task/task_additional_args_no_shell_expansion.out
vendored
Normal file
1
cli/tests/testdata/task/task_additional_args_no_shell_expansion.out
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
1 $(echo 5)
|
4
cli/tests/testdata/task/task_boolean_logic.out
vendored
Normal file
4
cli/tests/testdata/task/task_boolean_logic.out
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
1
cli/tests/testdata/task/task_exit_code_5.out
vendored
Normal file
1
cli/tests/testdata/task/task_exit_code_5.out
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
10
|
9
cli/tests/testdata/task/task_no_args.out
vendored
Normal file
9
cli/tests/testdata/task/task_no_args.out
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
Available tasks:
|
||||||
|
- boolean_logic
|
||||||
|
sleep 0.1 && echo 3 && echo 4 & echo 1 && echo 2 || echo NOPE
|
||||||
|
- echo
|
||||||
|
echo 1
|
||||||
|
- exit_code_5
|
||||||
|
echo $(echo 10 ; exit 2) && exit 5
|
||||||
|
- strings
|
||||||
|
deno run main.ts && deno eval "console.log(\"test\")"
|
10
cli/tests/testdata/task/task_non_existent.out
vendored
Normal file
10
cli/tests/testdata/task/task_non_existent.out
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
Task not found: non_existent
|
||||||
|
Available tasks:
|
||||||
|
- boolean_logic
|
||||||
|
sleep 0.1 && echo 3 && echo 4 & echo 1 && echo 2 || echo NOPE
|
||||||
|
- echo
|
||||||
|
echo 1
|
||||||
|
- exit_code_5
|
||||||
|
echo $(echo 10 ; exit 2) && exit 5
|
||||||
|
- strings
|
||||||
|
deno run main.ts && deno eval "console.log(\"test\")"
|
|
@ -7,6 +7,7 @@ pub mod installer;
|
||||||
pub mod lint;
|
pub mod lint;
|
||||||
pub mod repl;
|
pub mod repl;
|
||||||
pub mod standalone;
|
pub mod standalone;
|
||||||
|
pub mod task;
|
||||||
pub mod test;
|
pub mod test;
|
||||||
pub mod upgrade;
|
pub mod upgrade;
|
||||||
pub mod vendor;
|
pub mod vendor;
|
||||||
|
|
165
cli/tools/task.rs
Normal file
165
cli/tools/task.rs
Normal file
|
@ -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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1705,6 +1705,7 @@ pub fn run_powershell_script_file(
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct CheckOutputIntegrationTest {
|
pub struct CheckOutputIntegrationTest {
|
||||||
pub args: &'static str,
|
pub args: &'static str,
|
||||||
|
pub args_vec: Vec<&'static str>,
|
||||||
pub output: &'static str,
|
pub output: &'static str,
|
||||||
pub input: Option<&'static str>,
|
pub input: Option<&'static str>,
|
||||||
pub output_str: Option<&'static str>,
|
pub output_str: Option<&'static str>,
|
||||||
|
@ -1715,7 +1716,15 @@ pub struct CheckOutputIntegrationTest {
|
||||||
|
|
||||||
impl CheckOutputIntegrationTest {
|
impl CheckOutputIntegrationTest {
|
||||||
pub fn run(&self) {
|
pub fn run(&self) {
|
||||||
let args = self.args.split_whitespace();
|
let args = if self.args_vec.is_empty() {
|
||||||
|
std::borrow::Cow::Owned(self.args.split_whitespace().collect::<Vec<_>>())
|
||||||
|
} else {
|
||||||
|
assert!(
|
||||||
|
self.args.is_empty(),
|
||||||
|
"Do not provide args when providing args_vec."
|
||||||
|
);
|
||||||
|
std::borrow::Cow::Borrowed(&self.args_vec)
|
||||||
|
};
|
||||||
let deno_exe = deno_exe_path();
|
let deno_exe = deno_exe_path();
|
||||||
println!("deno_exe path {}", deno_exe.display());
|
println!("deno_exe path {}", deno_exe.display());
|
||||||
|
|
||||||
|
@ -1730,7 +1739,7 @@ impl CheckOutputIntegrationTest {
|
||||||
let mut command = deno_cmd();
|
let mut command = deno_cmd();
|
||||||
println!("deno_exe args {}", self.args);
|
println!("deno_exe args {}", self.args);
|
||||||
println!("deno_exe testdata path {:?}", &testdata_dir);
|
println!("deno_exe testdata path {:?}", &testdata_dir);
|
||||||
command.args(args);
|
command.args(args.iter());
|
||||||
command.envs(self.envs.clone());
|
command.envs(self.envs.clone());
|
||||||
command.current_dir(&testdata_dir);
|
command.current_dir(&testdata_dir);
|
||||||
command.stdin(Stdio::piped());
|
command.stdin(Stdio::piped());
|
||||||
|
|
Loading…
Add table
Reference in a new issue