1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-23 15:16:54 -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 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()

View file

@ -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;

View file

@ -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!(

View file

@ -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
}

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 ]