1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-27 16:10:57 -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:
Bartek Iwańczuk 2024-11-20 01:23:20 +00:00 committed by GitHub
parent dabb6775f3
commit 318dd3cbc3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 160 additions and 17 deletions

View file

@ -380,6 +380,7 @@ pub struct TaskFlags {
pub cwd: Option<String>, pub cwd: Option<String>,
pub task: Option<String>, pub task: Option<String>,
pub is_run: bool, pub is_run: bool,
pub eval: bool,
} }
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] #[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)?, "repl" => repl_parse(&mut flags, &mut m)?,
"run" => run_parse(&mut flags, &mut m, app, false)?, "run" => run_parse(&mut flags, &mut m, app, false)?,
"serve" => serve_parse(&mut flags, &mut m, app)?, "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)?, "test" => test_parse(&mut flags, &mut m)?,
"types" => types_parse(&mut flags, &mut m), "types" => types_parse(&mut flags, &mut m),
"uninstall" => uninstall_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</> <p(245)>deno task build</>
List all available tasks: 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, UnstableArgsConfig::ResolutionAndRuntime,
) )
@ -2947,6 +2951,13 @@ List all available tasks:
.help("Specify the directory to run the task in") .help("Specify the directory to run the task in")
.value_hint(ValueHint::DirPath), .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()) .arg(node_modules_dir_arg())
}) })
} }
@ -5066,7 +5077,11 @@ fn serve_parse(
Ok(()) 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 flags.config_flag = matches
.remove_one::<String>("config") .remove_one::<String>("config")
.map(ConfigFlag::Path) .map(ConfigFlag::Path)
@ -5079,6 +5094,7 @@ fn task_parse(flags: &mut Flags, matches: &mut ArgMatches) {
cwd: matches.remove_one::<String>("cwd"), cwd: matches.remove_one::<String>("cwd"),
task: None, task: None,
is_run: false, is_run: false,
eval: matches.get_flag("eval"),
}; };
if let Some((task, mut matches)) = matches.remove_subcommand() { if let Some((task, mut matches)) = matches.remove_subcommand() {
@ -5091,9 +5107,15 @@ fn task_parse(flags: &mut Flags, matches: &mut ArgMatches) {
.flatten() .flatten()
.filter_map(|arg| arg.into_string().ok()), .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); flags.subcommand = DenoSubcommand::Task(task_flags);
Ok(())
} }
fn parallel_arg_parse(matches: &mut ArgMatches) -> Option<NonZeroUsize> { fn parallel_arg_parse(matches: &mut ArgMatches) -> Option<NonZeroUsize> {
@ -10274,6 +10296,7 @@ mod tests {
cwd: None, cwd: None,
task: Some("build".to_string()), task: Some("build".to_string()),
is_run: false, is_run: false,
eval: false,
}), }),
argv: svec!["hello", "world"], argv: svec!["hello", "world"],
..Flags::default() ..Flags::default()
@ -10288,6 +10311,7 @@ mod tests {
cwd: None, cwd: None,
task: Some("build".to_string()), task: Some("build".to_string()),
is_run: false, is_run: false,
eval: false,
}), }),
..Flags::default() ..Flags::default()
} }
@ -10301,10 +10325,28 @@ mod tests {
cwd: Some("foo".to_string()), cwd: Some("foo".to_string()),
task: Some("build".to_string()), task: Some("build".to_string()),
is_run: false, is_run: false,
eval: false,
}), }),
..Flags::default() ..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] #[test]
@ -10326,6 +10368,7 @@ mod tests {
cwd: None, cwd: None,
task: Some("build".to_string()), task: Some("build".to_string()),
is_run: false, is_run: false,
eval: false,
}), }),
argv: svec!["--", "hello", "world"], argv: svec!["--", "hello", "world"],
config_flag: ConfigFlag::Path("deno.json".to_owned()), config_flag: ConfigFlag::Path("deno.json".to_owned()),
@ -10343,6 +10386,7 @@ mod tests {
cwd: Some("foo".to_string()), cwd: Some("foo".to_string()),
task: Some("build".to_string()), task: Some("build".to_string()),
is_run: false, is_run: false,
eval: false,
}), }),
argv: svec!["--", "hello", "world"], argv: svec!["--", "hello", "world"],
..Flags::default() ..Flags::default()
@ -10361,6 +10405,7 @@ mod tests {
cwd: None, cwd: None,
task: Some("build".to_string()), task: Some("build".to_string()),
is_run: false, is_run: false,
eval: false,
}), }),
argv: svec!["--"], argv: svec!["--"],
..Flags::default() ..Flags::default()
@ -10378,6 +10423,7 @@ mod tests {
cwd: None, cwd: None,
task: Some("build".to_string()), task: Some("build".to_string()),
is_run: false, is_run: false,
eval: false,
}), }),
argv: svec!["-1", "--test"], argv: svec!["-1", "--test"],
..Flags::default() ..Flags::default()
@ -10395,6 +10441,7 @@ mod tests {
cwd: None, cwd: None,
task: Some("build".to_string()), task: Some("build".to_string()),
is_run: false, is_run: false,
eval: false,
}), }),
argv: svec!["--test"], argv: svec!["--test"],
..Flags::default() ..Flags::default()
@ -10413,6 +10460,7 @@ mod tests {
cwd: None, cwd: None,
task: Some("build".to_string()), task: Some("build".to_string()),
is_run: false, is_run: false,
eval: false,
}), }),
log_level: Some(log::Level::Error), log_level: Some(log::Level::Error),
..Flags::default() ..Flags::default()
@ -10430,6 +10478,7 @@ mod tests {
cwd: None, cwd: None,
task: None, task: None,
is_run: false, is_run: false,
eval: false,
}), }),
..Flags::default() ..Flags::default()
} }
@ -10446,6 +10495,7 @@ mod tests {
cwd: None, cwd: None,
task: None, task: None,
is_run: false, is_run: false,
eval: false,
}), }),
config_flag: ConfigFlag::Path("deno.jsonc".to_string()), config_flag: ConfigFlag::Path("deno.jsonc".to_string()),
..Flags::default() ..Flags::default()
@ -10463,6 +10513,7 @@ mod tests {
cwd: None, cwd: None,
task: None, task: None,
is_run: false, is_run: false,
eval: false,
}), }),
config_flag: ConfigFlag::Path("deno.jsonc".to_string()), config_flag: ConfigFlag::Path("deno.jsonc".to_string()),
..Flags::default() ..Flags::default()

View file

@ -238,6 +238,7 @@ async fn run_subcommand(flags: Arc<Flags>) -> Result<i32, AnyError> {
cwd: None, cwd: None,
task: Some(run_flags.script.clone()), task: Some(run_flags.script.clone()),
is_run: true, is_run: true,
eval: false,
}; };
new_flags.subcommand = DenoSubcommand::Task(task_flags.clone()); new_flags.subcommand = DenoSubcommand::Task(task_flags.clone());
let result = tools::task::execute_script(Arc::new(new_flags), task_flags.clone()).await; let result = tools::task::execute_script(Arc::new(new_flags), task_flags.clone()).await;

View file

@ -483,10 +483,23 @@ fn resolve_execution_path_from_npx_shim(
static SCRIPT_PATH_RE: Lazy<Regex> = static SCRIPT_PATH_RE: Lazy<Regex> =
lazy_regex::lazy_regex!(r#""\$basedir\/([^"]+)" "\$@""#); lazy_regex::lazy_regex!(r#""\$basedir\/([^"]+)" "\$@""#);
if text.starts_with("#!/usr/bin/env node") { 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 // launch this file itself because it's a JS file
Some(file_path) return Some(file_path);
} else { }
}
// Search for... // Search for...
// > "$basedir/../next/dist/bin/next" "$@" // > "$basedir/../next/dist/bin/next" "$@"
// ...which is what it will look like on Windows // ...which is what it will look like on Windows
@ -496,7 +509,6 @@ fn resolve_execution_path_from_npx_shim(
.map(|relative_path| { .map(|relative_path| {
file_path.parent().unwrap().join(relative_path.as_str()) file_path.parent().unwrap().join(relative_path.as_str())
}) })
}
} }
fn resolve_managed_npm_commands( fn resolve_managed_npm_commands(
@ -564,6 +576,16 @@ mod test {
let unix_shim = r#"#!/usr/bin/env node let unix_shim = r#"#!/usr/bin/env node
"use strict"; "use strict";
console.log('Hi!'); 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"); let path = PathBuf::from("/node_modules/.bin/example");
assert_eq!( assert_eq!(

View file

@ -42,7 +42,7 @@ pub async fn execute_script(
let factory = CliFactory::from_flags(flags); let factory = CliFactory::from_flags(flags);
let cli_options = factory.cli_options()?; let cli_options = factory.cli_options()?;
let start_dir = &cli_options.start_dir; 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") bail!("deno task couldn't find deno.json(c). See https://docs.deno.com/go/config")
} }
let force_use_pkg_json = let force_use_pkg_json =
@ -90,6 +90,19 @@ pub async fn execute_script(
concurrency: no_of_concurrent_tasks.into(), 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 task_runner.run_task(task_name).await
} }

View 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"
}]
}
}
}

View file

@ -0,0 +1,3 @@
Task cli-esm hi hello
hi
hello

View file

@ -0,0 +1,2 @@
Task echo $(pwd)
[WILDCARD]

View file

@ -0,0 +1,4 @@
error: [TASK] must be specified when using --eval
Usage: deno task [OPTIONS] [TASK]

View file

@ -0,0 +1,9 @@
{
"name": "bin_package",
"devDependencies": {
"@denotest/bin": "1.0.0"
},
"scripts": {
"sayhi": "cli-esm hi hello"
}
}

View 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 ]