mirror of
https://github.com/denoland/deno.git
synced 2024-12-24 16:19:12 -05:00
feat(task): add --eval flag (#26943)
This commit adds `--eval` flag to `deno task` subcommand. This flag allows to evaluate provided "task name" as a task itself, effectively allowing to use `deno_task_shell` from the command line. Also fixes shebang parsing for `node_modules/.bin/` entries to handle `#!/usr/bin/node -S node` in addition to `#!/usr/bin/node node`. Closes https://github.com/denoland/deno/issues/26918
This commit is contained in:
parent
dabb6775f3
commit
318dd3cbc3
10 changed files with 160 additions and 17 deletions
|
@ -380,6 +380,7 @@ pub struct TaskFlags {
|
|||
pub cwd: Option<String>,
|
||||
pub task: Option<String>,
|
||||
pub is_run: bool,
|
||||
pub eval: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
|
@ -1386,7 +1387,7 @@ pub fn flags_from_vec(args: Vec<OsString>) -> clap::error::Result<Flags> {
|
|||
"repl" => repl_parse(&mut flags, &mut m)?,
|
||||
"run" => run_parse(&mut flags, &mut m, app, false)?,
|
||||
"serve" => serve_parse(&mut flags, &mut m, app)?,
|
||||
"task" => task_parse(&mut flags, &mut m),
|
||||
"task" => task_parse(&mut flags, &mut m, app)?,
|
||||
"test" => test_parse(&mut flags, &mut m)?,
|
||||
"types" => types_parse(&mut flags, &mut m),
|
||||
"uninstall" => uninstall_parse(&mut flags, &mut m),
|
||||
|
@ -2931,7 +2932,10 @@ fn task_subcommand() -> Command {
|
|||
<p(245)>deno task build</>
|
||||
|
||||
List all available tasks:
|
||||
<p(245)>deno task</>"
|
||||
<p(245)>deno task</>
|
||||
|
||||
Evaluate a task from string
|
||||
<p(245)>deno task --eval \"echo $(pwd)\"</>"
|
||||
),
|
||||
UnstableArgsConfig::ResolutionAndRuntime,
|
||||
)
|
||||
|
@ -2947,6 +2951,13 @@ List all available tasks:
|
|||
.help("Specify the directory to run the task in")
|
||||
.value_hint(ValueHint::DirPath),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("eval")
|
||||
.long("eval")
|
||||
.help(
|
||||
"Evaluate the passed value as if, it was a task in a configuration file",
|
||||
).action(ArgAction::SetTrue)
|
||||
)
|
||||
.arg(node_modules_dir_arg())
|
||||
})
|
||||
}
|
||||
|
@ -5066,7 +5077,11 @@ fn serve_parse(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn task_parse(flags: &mut Flags, matches: &mut ArgMatches) {
|
||||
fn task_parse(
|
||||
flags: &mut Flags,
|
||||
matches: &mut ArgMatches,
|
||||
mut app: Command,
|
||||
) -> clap::error::Result<()> {
|
||||
flags.config_flag = matches
|
||||
.remove_one::<String>("config")
|
||||
.map(ConfigFlag::Path)
|
||||
|
@ -5079,6 +5094,7 @@ fn task_parse(flags: &mut Flags, matches: &mut ArgMatches) {
|
|||
cwd: matches.remove_one::<String>("cwd"),
|
||||
task: None,
|
||||
is_run: false,
|
||||
eval: matches.get_flag("eval"),
|
||||
};
|
||||
|
||||
if let Some((task, mut matches)) = matches.remove_subcommand() {
|
||||
|
@ -5091,9 +5107,15 @@ fn task_parse(flags: &mut Flags, matches: &mut ArgMatches) {
|
|||
.flatten()
|
||||
.filter_map(|arg| arg.into_string().ok()),
|
||||
);
|
||||
} else if task_flags.eval {
|
||||
return Err(app.find_subcommand_mut("task").unwrap().error(
|
||||
clap::error::ErrorKind::MissingRequiredArgument,
|
||||
"[TASK] must be specified when using --eval",
|
||||
));
|
||||
}
|
||||
|
||||
flags.subcommand = DenoSubcommand::Task(task_flags);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parallel_arg_parse(matches: &mut ArgMatches) -> Option<NonZeroUsize> {
|
||||
|
@ -10274,6 +10296,7 @@ mod tests {
|
|||
cwd: None,
|
||||
task: Some("build".to_string()),
|
||||
is_run: false,
|
||||
eval: false,
|
||||
}),
|
||||
argv: svec!["hello", "world"],
|
||||
..Flags::default()
|
||||
|
@ -10288,6 +10311,7 @@ mod tests {
|
|||
cwd: None,
|
||||
task: Some("build".to_string()),
|
||||
is_run: false,
|
||||
eval: false,
|
||||
}),
|
||||
..Flags::default()
|
||||
}
|
||||
|
@ -10301,10 +10325,28 @@ mod tests {
|
|||
cwd: Some("foo".to_string()),
|
||||
task: Some("build".to_string()),
|
||||
is_run: false,
|
||||
eval: false,
|
||||
}),
|
||||
..Flags::default()
|
||||
}
|
||||
);
|
||||
|
||||
let r = flags_from_vec(svec!["deno", "task", "--eval", "echo 1"]);
|
||||
assert_eq!(
|
||||
r.unwrap(),
|
||||
Flags {
|
||||
subcommand: DenoSubcommand::Task(TaskFlags {
|
||||
cwd: None,
|
||||
task: Some("echo 1".to_string()),
|
||||
is_run: false,
|
||||
eval: true,
|
||||
}),
|
||||
..Flags::default()
|
||||
}
|
||||
);
|
||||
|
||||
let r = flags_from_vec(svec!["deno", "task", "--eval"]);
|
||||
assert!(r.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -10326,6 +10368,7 @@ mod tests {
|
|||
cwd: None,
|
||||
task: Some("build".to_string()),
|
||||
is_run: false,
|
||||
eval: false,
|
||||
}),
|
||||
argv: svec!["--", "hello", "world"],
|
||||
config_flag: ConfigFlag::Path("deno.json".to_owned()),
|
||||
|
@ -10343,6 +10386,7 @@ mod tests {
|
|||
cwd: Some("foo".to_string()),
|
||||
task: Some("build".to_string()),
|
||||
is_run: false,
|
||||
eval: false,
|
||||
}),
|
||||
argv: svec!["--", "hello", "world"],
|
||||
..Flags::default()
|
||||
|
@ -10361,6 +10405,7 @@ mod tests {
|
|||
cwd: None,
|
||||
task: Some("build".to_string()),
|
||||
is_run: false,
|
||||
eval: false,
|
||||
}),
|
||||
argv: svec!["--"],
|
||||
..Flags::default()
|
||||
|
@ -10378,6 +10423,7 @@ mod tests {
|
|||
cwd: None,
|
||||
task: Some("build".to_string()),
|
||||
is_run: false,
|
||||
eval: false,
|
||||
}),
|
||||
argv: svec!["-1", "--test"],
|
||||
..Flags::default()
|
||||
|
@ -10395,6 +10441,7 @@ mod tests {
|
|||
cwd: None,
|
||||
task: Some("build".to_string()),
|
||||
is_run: false,
|
||||
eval: false,
|
||||
}),
|
||||
argv: svec!["--test"],
|
||||
..Flags::default()
|
||||
|
@ -10413,6 +10460,7 @@ mod tests {
|
|||
cwd: None,
|
||||
task: Some("build".to_string()),
|
||||
is_run: false,
|
||||
eval: false,
|
||||
}),
|
||||
log_level: Some(log::Level::Error),
|
||||
..Flags::default()
|
||||
|
@ -10430,6 +10478,7 @@ mod tests {
|
|||
cwd: None,
|
||||
task: None,
|
||||
is_run: false,
|
||||
eval: false,
|
||||
}),
|
||||
..Flags::default()
|
||||
}
|
||||
|
@ -10446,6 +10495,7 @@ mod tests {
|
|||
cwd: None,
|
||||
task: None,
|
||||
is_run: false,
|
||||
eval: false,
|
||||
}),
|
||||
config_flag: ConfigFlag::Path("deno.jsonc".to_string()),
|
||||
..Flags::default()
|
||||
|
@ -10463,6 +10513,7 @@ mod tests {
|
|||
cwd: None,
|
||||
task: None,
|
||||
is_run: false,
|
||||
eval: false,
|
||||
}),
|
||||
config_flag: ConfigFlag::Path("deno.jsonc".to_string()),
|
||||
..Flags::default()
|
||||
|
|
|
@ -238,6 +238,7 @@ async fn run_subcommand(flags: Arc<Flags>) -> Result<i32, AnyError> {
|
|||
cwd: None,
|
||||
task: Some(run_flags.script.clone()),
|
||||
is_run: true,
|
||||
eval: false,
|
||||
};
|
||||
new_flags.subcommand = DenoSubcommand::Task(task_flags.clone());
|
||||
let result = tools::task::execute_script(Arc::new(new_flags), task_flags.clone()).await;
|
||||
|
|
|
@ -483,20 +483,32 @@ fn resolve_execution_path_from_npx_shim(
|
|||
static SCRIPT_PATH_RE: Lazy<Regex> =
|
||||
lazy_regex::lazy_regex!(r#""\$basedir\/([^"]+)" "\$@""#);
|
||||
|
||||
if text.starts_with("#!/usr/bin/env node") {
|
||||
// launch this file itself because it's a JS file
|
||||
Some(file_path)
|
||||
} else {
|
||||
// Search for...
|
||||
// > "$basedir/../next/dist/bin/next" "$@"
|
||||
// ...which is what it will look like on Windows
|
||||
SCRIPT_PATH_RE
|
||||
.captures(text)
|
||||
.and_then(|c| c.get(1))
|
||||
.map(|relative_path| {
|
||||
file_path.parent().unwrap().join(relative_path.as_str())
|
||||
})
|
||||
let maybe_first_line = {
|
||||
let index = text.find("\n")?;
|
||||
Some(&text[0..index])
|
||||
};
|
||||
|
||||
if let Some(first_line) = maybe_first_line {
|
||||
// NOTE(bartlomieju): this is not perfect, but handle two most common scenarios
|
||||
// where Node is run without any args. If there are args then we use `NodeCommand`
|
||||
// struct.
|
||||
if first_line == "#!/usr/bin/env node"
|
||||
|| first_line == "#!/usr/bin/env -S node"
|
||||
{
|
||||
// launch this file itself because it's a JS file
|
||||
return Some(file_path);
|
||||
}
|
||||
}
|
||||
|
||||
// Search for...
|
||||
// > "$basedir/../next/dist/bin/next" "$@"
|
||||
// ...which is what it will look like on Windows
|
||||
SCRIPT_PATH_RE
|
||||
.captures(text)
|
||||
.and_then(|c| c.get(1))
|
||||
.map(|relative_path| {
|
||||
file_path.parent().unwrap().join(relative_path.as_str())
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_managed_npm_commands(
|
||||
|
@ -564,6 +576,16 @@ mod test {
|
|||
let unix_shim = r#"#!/usr/bin/env node
|
||||
"use strict";
|
||||
console.log('Hi!');
|
||||
"#;
|
||||
let path = PathBuf::from("/node_modules/.bin/example");
|
||||
assert_eq!(
|
||||
resolve_execution_path_from_npx_shim(path.clone(), unix_shim).unwrap(),
|
||||
path
|
||||
);
|
||||
// example shim on unix
|
||||
let unix_shim = r#"#!/usr/bin/env -S node
|
||||
"use strict";
|
||||
console.log('Hi!');
|
||||
"#;
|
||||
let path = PathBuf::from("/node_modules/.bin/example");
|
||||
assert_eq!(
|
||||
|
|
|
@ -42,7 +42,7 @@ pub async fn execute_script(
|
|||
let factory = CliFactory::from_flags(flags);
|
||||
let cli_options = factory.cli_options()?;
|
||||
let start_dir = &cli_options.start_dir;
|
||||
if !start_dir.has_deno_or_pkg_json() {
|
||||
if !start_dir.has_deno_or_pkg_json() && !task_flags.eval {
|
||||
bail!("deno task couldn't find deno.json(c). See https://docs.deno.com/go/config")
|
||||
}
|
||||
let force_use_pkg_json =
|
||||
|
@ -90,6 +90,19 @@ pub async fn execute_script(
|
|||
concurrency: no_of_concurrent_tasks.into(),
|
||||
};
|
||||
|
||||
if task_flags.eval {
|
||||
return task_runner
|
||||
.run_deno_task(
|
||||
&Url::from_directory_path(cli_options.initial_cwd()).unwrap(),
|
||||
&"".to_string(),
|
||||
&TaskDefinition {
|
||||
command: task_flags.task.as_ref().unwrap().to_string(),
|
||||
dependencies: vec![],
|
||||
description: None,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
}
|
||||
task_runner.run_task(task_name).await
|
||||
}
|
||||
|
||||
|
|
35
tests/specs/task/eval/__test__.jsonc
Normal file
35
tests/specs/task/eval/__test__.jsonc
Normal file
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"tests": {
|
||||
"no_arg": {
|
||||
"args": "task --eval",
|
||||
"output": "no_arg.out",
|
||||
"exitCode": 1
|
||||
},
|
||||
"echo_pwd": {
|
||||
"args": ["task", "--eval", "echo $(pwd)"],
|
||||
"output": "echo_pwd.out"
|
||||
},
|
||||
"piped": {
|
||||
"args": [
|
||||
"task",
|
||||
"--eval",
|
||||
"echo 12345 | (deno eval 'const b = new Uint8Array(1);Deno.stdin.readSync(b);console.log(b)' && deno eval 'const b = new Uint8Array(1);Deno.stdin.readSync(b);console.log(b)')"
|
||||
],
|
||||
"output": "piped.out"
|
||||
},
|
||||
"node_modules_bin": {
|
||||
"tempDir": true,
|
||||
"steps": [{
|
||||
"args": "install",
|
||||
"output": "[WILDCARD]Initialize @denotest/bin[WILDCARD]"
|
||||
}, {
|
||||
"args": [
|
||||
"task",
|
||||
"--eval",
|
||||
"cli-esm hi hello"
|
||||
],
|
||||
"output": "bin.out"
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
3
tests/specs/task/eval/bin.out
Normal file
3
tests/specs/task/eval/bin.out
Normal file
|
@ -0,0 +1,3 @@
|
|||
Task cli-esm hi hello
|
||||
hi
|
||||
hello
|
2
tests/specs/task/eval/echo_pwd.out
Normal file
2
tests/specs/task/eval/echo_pwd.out
Normal file
|
@ -0,0 +1,2 @@
|
|||
Task echo $(pwd)
|
||||
[WILDCARD]
|
4
tests/specs/task/eval/no_arg.out
Normal file
4
tests/specs/task/eval/no_arg.out
Normal file
|
@ -0,0 +1,4 @@
|
|||
error: [TASK] must be specified when using --eval
|
||||
|
||||
Usage: deno task [OPTIONS] [TASK]
|
||||
|
9
tests/specs/task/eval/package.json
Normal file
9
tests/specs/task/eval/package.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"name": "bin_package",
|
||||
"devDependencies": {
|
||||
"@denotest/bin": "1.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"sayhi": "cli-esm hi hello"
|
||||
}
|
||||
}
|
3
tests/specs/task/eval/piped.out
Normal file
3
tests/specs/task/eval/piped.out
Normal file
|
@ -0,0 +1,3 @@
|
|||
Task echo 12345 | (deno eval 'const b = new Uint8Array(1);Deno.stdin.readSync(b);console.log(b)' && deno eval 'const b = new Uint8Array(1);Deno.stdin.readSync(b);console.log(b)')
|
||||
Uint8Array(1) [ 49 ]
|
||||
Uint8Array(1) [ 50 ]
|
Loading…
Reference in a new issue