summaryrefslogtreecommitdiff
path: root/cli/tools/task.rs
diff options
context:
space:
mode:
Diffstat (limited to 'cli/tools/task.rs')
-rw-r--r--cli/tools/task.rs490
1 files changed, 52 insertions, 438 deletions
diff --git a/cli/tools/task.rs b/cli/tools/task.rs
index 2905134f4..9ab54f258 100644
--- a/cli/tools/task.rs
+++ b/cli/tools/task.rs
@@ -1,12 +1,12 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+use crate::args::CliOptions;
use crate::args::Flags;
use crate::args::TaskFlags;
use crate::colors;
use crate::factory::CliFactory;
use crate::npm::CliNpmResolver;
-use crate::npm::InnerCliNpmResolverRef;
-use crate::npm::ManagedCliNpmResolver;
+use crate::task_runner;
use crate::util::fs::canonicalize_path;
use deno_config::workspace::TaskOrScript;
use deno_config::workspace::Workspace;
@@ -14,17 +14,8 @@ use deno_config::workspace::WorkspaceTasksConfig;
use deno_core::anyhow::bail;
use deno_core::anyhow::Context;
use deno_core::error::AnyError;
-use deno_core::futures;
-use deno_core::futures::future::LocalBoxFuture;
use deno_core::normalize_path;
-use deno_runtime::deno_node::NodeResolver;
-use deno_semver::package::PackageNv;
-use deno_task_shell::ExecutableCommand;
-use deno_task_shell::ExecuteResult;
use deno_task_shell::ShellCommand;
-use deno_task_shell::ShellCommandContext;
-use lazy_regex::Lazy;
-use regex::Regex;
use std::borrow::Cow;
use std::collections::HashMap;
use std::collections::HashSet;
@@ -32,11 +23,6 @@ use std::path::Path;
use std::path::PathBuf;
use std::rc::Rc;
use std::sync::Arc;
-use tokio::task::LocalSet;
-
-// WARNING: Do not depend on this env var in user code. It's not stable API.
-const USE_PKG_JSON_HIDDEN_ENV_VAR_NAME: &str =
- "DENO_INTERNAL_TASK_USE_PKG_JSON";
pub async fn execute_script(
flags: Flags,
@@ -48,13 +34,16 @@ pub async fn execute_script(
if !start_ctx.has_deno_or_pkg_json() {
bail!("deno task couldn't find deno.json(c). See https://deno.land/manual@v{}/getting_started/configuration_file", env!("CARGO_PKG_VERSION"))
}
- let force_use_pkg_json = std::env::var_os(USE_PKG_JSON_HIDDEN_ENV_VAR_NAME)
- .map(|v| {
- // always remove so sub processes don't inherit this env var
- std::env::remove_var(USE_PKG_JSON_HIDDEN_ENV_VAR_NAME);
- v == "1"
- })
- .unwrap_or(false);
+ let force_use_pkg_json =
+ std::env::var_os(crate::task_runner::USE_PKG_JSON_HIDDEN_ENV_VAR_NAME)
+ .map(|v| {
+ // always remove so sub processes don't inherit this env var
+ std::env::remove_var(
+ crate::task_runner::USE_PKG_JSON_HIDDEN_ENV_VAR_NAME,
+ );
+ v == "1"
+ })
+ .unwrap_or(false);
let tasks_config = start_ctx.to_tasks_config()?;
let tasks_config = if force_use_pkg_json {
tasks_config.with_only_pkg_json()
@@ -76,7 +65,7 @@ pub async fn execute_script(
let npm_resolver = factory.npm_resolver().await?;
let node_resolver = factory.node_resolver().await?;
- let env_vars = real_env_vars();
+ let env_vars = task_runner::real_env_vars();
match tasks_config.task(task_name) {
Some((dir_url, task_or_script)) => match task_or_script {
@@ -87,19 +76,18 @@ pub async fn execute_script(
None => normalize_path(dir_url.to_file_path().unwrap()),
};
- let custom_commands =
- resolve_custom_commands(npm_resolver.as_ref(), node_resolver)?;
+ let custom_commands = task_runner::resolve_custom_commands(
+ npm_resolver.as_ref(),
+ node_resolver,
+ )?;
run_task(RunTaskOptions {
task_name,
script,
cwd: &cwd,
- init_cwd: cli_options.initial_cwd(),
env_vars,
- argv: cli_options.argv(),
custom_commands,
- root_node_modules_dir: npm_resolver
- .root_node_modules_path()
- .map(|p| p.as_path()),
+ npm_resolver: npm_resolver.as_ref(),
+ cli_options,
})
.await
}
@@ -125,21 +113,20 @@ pub async fn execute_script(
task_name.clone(),
format!("post{}", task_name),
];
- let custom_commands =
- resolve_custom_commands(npm_resolver.as_ref(), node_resolver)?;
+ let custom_commands = task_runner::resolve_custom_commands(
+ npm_resolver.as_ref(),
+ node_resolver,
+ )?;
for task_name in &task_names {
if let Some(script) = scripts.get(task_name) {
let exit_code = run_task(RunTaskOptions {
task_name,
script,
cwd: &cwd,
- init_cwd: cli_options.initial_cwd(),
env_vars: env_vars.clone(),
- argv: cli_options.argv(),
custom_commands: custom_commands.clone(),
- root_node_modules_dir: npm_resolver
- .root_node_modules_path()
- .map(|p| p.as_path()),
+ npm_resolver: npm_resolver.as_ref(),
+ cli_options,
})
.await?;
if exit_code > 0 {
@@ -169,40 +156,41 @@ struct RunTaskOptions<'a> {
task_name: &'a str,
script: &'a str,
cwd: &'a Path,
- init_cwd: &'a Path,
env_vars: HashMap<String, String>,
- argv: &'a [String],
custom_commands: HashMap<String, Rc<dyn ShellCommand>>,
- root_node_modules_dir: Option<&'a Path>,
+ npm_resolver: &'a dyn CliNpmResolver,
+ cli_options: &'a CliOptions,
}
async fn run_task(opts: RunTaskOptions<'_>) -> Result<i32, AnyError> {
- let script = get_script_with_args(opts.script, opts.argv);
- output_task(opts.task_name, &script);
- let seq_list = deno_task_shell::parser::parse(&script)
- .with_context(|| format!("Error parsing script '{}'.", opts.task_name))?;
- let env_vars =
- prepare_env_vars(opts.env_vars, opts.init_cwd, opts.root_node_modules_dir);
- let local = LocalSet::new();
- let future = deno_task_shell::execute(
- seq_list,
+ let RunTaskOptions {
+ task_name,
+ script,
+ cwd,
env_vars,
- opts.cwd,
- opts.custom_commands,
+ custom_commands,
+ npm_resolver,
+ cli_options,
+ } = opts;
+
+ output_task(
+ opts.task_name,
+ &task_runner::get_script_with_args(script, cli_options.argv()),
);
- Ok(local.run_until(future).await)
-}
-fn get_script_with_args(script: &str, argv: &[String]) -> String {
- let additional_args = argv
- .iter()
- // surround all the additional arguments in double quotes
- // and sanitize any command substitution
- .map(|a| format!("\"{}\"", a.replace('"', "\\\"").replace('$', "\\$")))
- .collect::<Vec<_>>()
- .join(" ");
- let script = format!("{script} {additional_args}");
- script.trim().to_owned()
+ task_runner::run_task(task_runner::RunTaskOptions {
+ task_name,
+ script,
+ cwd,
+ env_vars,
+ custom_commands,
+ init_cwd: opts.cli_options.initial_cwd(),
+ argv: cli_options.argv(),
+ root_node_modules_dir: npm_resolver
+ .root_node_modules_path()
+ .map(|p| p.as_path()),
+ })
+ .await
}
fn output_task(task_name: &str, script: &str) {
@@ -214,56 +202,6 @@ fn output_task(task_name: &str, script: &str) {
);
}
-fn prepare_env_vars(
- mut env_vars: HashMap<String, String>,
- initial_cwd: &Path,
- node_modules_dir: Option<&Path>,
-) -> HashMap<String, String> {
- const INIT_CWD_NAME: &str = "INIT_CWD";
- if !env_vars.contains_key(INIT_CWD_NAME) {
- // if not set, set an INIT_CWD env var that has the cwd
- env_vars.insert(
- INIT_CWD_NAME.to_string(),
- initial_cwd.to_string_lossy().to_string(),
- );
- }
- if let Some(node_modules_dir) = node_modules_dir {
- prepend_to_path(
- &mut env_vars,
- node_modules_dir.join(".bin").to_string_lossy().to_string(),
- );
- }
- env_vars
-}
-
-fn prepend_to_path(env_vars: &mut HashMap<String, String>, value: String) {
- match env_vars.get_mut("PATH") {
- Some(path) => {
- if path.is_empty() {
- *path = value;
- } else {
- *path =
- format!("{}{}{}", value, if cfg!(windows) { ";" } else { ":" }, path);
- }
- }
- None => {
- env_vars.insert("PATH".to_string(), value);
- }
- }
-}
-
-fn real_env_vars() -> HashMap<String, String> {
- std::env::vars()
- .map(|(k, v)| {
- if cfg!(windows) {
- (k.to_uppercase(), v)
- } else {
- (k, v)
- }
- })
- .collect::<HashMap<String, String>>()
-}
-
fn print_available_tasks(
writer: &mut dyn std::io::Write,
workspace: &Arc<Workspace>,
@@ -357,327 +295,3 @@ fn print_available_tasks(
Ok(())
}
-
-struct NpmCommand;
-
-impl ShellCommand for NpmCommand {
- fn execute(
- &self,
- mut context: ShellCommandContext,
- ) -> LocalBoxFuture<'static, ExecuteResult> {
- if context.args.first().map(|s| s.as_str()) == Some("run")
- && context.args.len() > 2
- // for now, don't run any npm scripts that have a flag because
- // we don't handle stuff like `--workspaces` properly
- && !context.args.iter().any(|s| s.starts_with('-'))
- {
- // run with deno task instead
- let mut args = Vec::with_capacity(context.args.len());
- args.push("task".to_string());
- args.extend(context.args.iter().skip(1).cloned());
-
- let mut state = context.state;
- state.apply_env_var(USE_PKG_JSON_HIDDEN_ENV_VAR_NAME, "1");
- return ExecutableCommand::new(
- "deno".to_string(),
- std::env::current_exe().unwrap(),
- )
- .execute(ShellCommandContext {
- args,
- state,
- ..context
- });
- }
-
- // fallback to running the real npm command
- let npm_path = match context.state.resolve_command_path("npm") {
- Ok(path) => path,
- Err(err) => {
- let _ = context.stderr.write_line(&format!("{}", err));
- return Box::pin(futures::future::ready(
- ExecuteResult::from_exit_code(err.exit_code()),
- ));
- }
- };
- ExecutableCommand::new("npm".to_string(), npm_path).execute(context)
- }
-}
-
-struct NpxCommand;
-
-impl ShellCommand for NpxCommand {
- fn execute(
- &self,
- mut context: ShellCommandContext,
- ) -> LocalBoxFuture<'static, ExecuteResult> {
- if let Some(first_arg) = context.args.first().cloned() {
- if let Some(command) = context.state.resolve_custom_command(&first_arg) {
- let context = ShellCommandContext {
- args: context.args.iter().skip(1).cloned().collect::<Vec<_>>(),
- ..context
- };
- command.execute(context)
- } else {
- // can't find the command, so fallback to running the real npx command
- let npx_path = match context.state.resolve_command_path("npx") {
- Ok(npx) => npx,
- Err(err) => {
- let _ = context.stderr.write_line(&format!("{}", err));
- return Box::pin(futures::future::ready(
- ExecuteResult::from_exit_code(err.exit_code()),
- ));
- }
- };
- ExecutableCommand::new("npx".to_string(), npx_path).execute(context)
- }
- } else {
- let _ = context.stderr.write_line("npx: missing command");
- Box::pin(futures::future::ready(ExecuteResult::from_exit_code(1)))
- }
- }
-}
-
-#[derive(Clone)]
-struct NpmPackageBinCommand {
- name: String,
- npm_package: PackageNv,
-}
-
-impl ShellCommand for NpmPackageBinCommand {
- fn execute(
- &self,
- context: ShellCommandContext,
- ) -> LocalBoxFuture<'static, ExecuteResult> {
- let mut args = vec![
- "run".to_string(),
- "-A".to_string(),
- if self.npm_package.name == self.name {
- format!("npm:{}", self.npm_package)
- } else {
- format!("npm:{}/{}", self.npm_package, self.name)
- },
- ];
- args.extend(context.args);
- let executable_command = deno_task_shell::ExecutableCommand::new(
- "deno".to_string(),
- std::env::current_exe().unwrap(),
- );
- executable_command.execute(ShellCommandContext { args, ..context })
- }
-}
-
-/// Runs a module in the node_modules folder.
-#[derive(Clone)]
-struct NodeModulesFileRunCommand {
- command_name: String,
- path: PathBuf,
-}
-
-impl ShellCommand for NodeModulesFileRunCommand {
- fn execute(
- &self,
- mut context: ShellCommandContext,
- ) -> LocalBoxFuture<'static, ExecuteResult> {
- let mut args = vec![
- "run".to_string(),
- "--ext=js".to_string(),
- "-A".to_string(),
- self.path.to_string_lossy().to_string(),
- ];
- args.extend(context.args);
- let executable_command = deno_task_shell::ExecutableCommand::new(
- "deno".to_string(),
- std::env::current_exe().unwrap(),
- );
- // set this environment variable so that the launched process knows the npm command name
- context
- .state
- .apply_env_var("DENO_INTERNAL_NPM_CMD_NAME", &self.command_name);
- executable_command.execute(ShellCommandContext { args, ..context })
- }
-}
-
-fn resolve_custom_commands(
- npm_resolver: &dyn CliNpmResolver,
- node_resolver: &NodeResolver,
-) -> Result<HashMap<String, Rc<dyn ShellCommand>>, AnyError> {
- let mut commands = match npm_resolver.as_inner() {
- InnerCliNpmResolverRef::Byonm(npm_resolver) => {
- let node_modules_dir = npm_resolver.root_node_modules_path().unwrap();
- resolve_npm_commands_from_bin_dir(node_modules_dir)
- }
- InnerCliNpmResolverRef::Managed(npm_resolver) => {
- resolve_managed_npm_commands(npm_resolver, node_resolver)?
- }
- };
- commands.insert("npm".to_string(), Rc::new(NpmCommand));
- Ok(commands)
-}
-
-fn resolve_npm_commands_from_bin_dir(
- node_modules_dir: &Path,
-) -> HashMap<String, Rc<dyn ShellCommand>> {
- let mut result = HashMap::<String, Rc<dyn ShellCommand>>::new();
- let bin_dir = node_modules_dir.join(".bin");
- log::debug!("Resolving commands in '{}'.", bin_dir.display());
- match std::fs::read_dir(&bin_dir) {
- Ok(entries) => {
- for entry in entries {
- let Ok(entry) = entry else {
- continue;
- };
- if let Some(command) = resolve_bin_dir_entry_command(entry) {
- result.insert(command.command_name.clone(), Rc::new(command));
- }
- }
- }
- Err(err) => {
- log::debug!("Failed read_dir for '{}': {:#}", bin_dir.display(), err);
- }
- }
- result
-}
-
-fn resolve_bin_dir_entry_command(
- entry: std::fs::DirEntry,
-) -> Option<NodeModulesFileRunCommand> {
- if entry.path().extension().is_some() {
- return None; // only look at files without extensions (even on Windows)
- }
- let file_type = entry.file_type().ok()?;
- let path = if file_type.is_file() {
- entry.path()
- } else if file_type.is_symlink() {
- entry.path().canonicalize().ok()?
- } else {
- return None;
- };
- let text = std::fs::read_to_string(&path).ok()?;
- let command_name = entry.file_name().to_string_lossy().to_string();
- if let Some(path) = resolve_execution_path_from_npx_shim(path, &text) {
- log::debug!(
- "Resolved npx command '{}' to '{}'.",
- command_name,
- path.display()
- );
- Some(NodeModulesFileRunCommand { command_name, path })
- } else {
- log::debug!("Failed resolving npx command '{}'.", command_name);
- None
- }
-}
-
-/// This is not ideal, but it works ok because it allows us to bypass
-/// the shebang and execute the script directly with Deno.
-fn resolve_execution_path_from_npx_shim(
- file_path: PathBuf,
- text: &str,
-) -> Option<PathBuf> {
- 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())
- })
- }
-}
-
-fn resolve_managed_npm_commands(
- npm_resolver: &ManagedCliNpmResolver,
- node_resolver: &NodeResolver,
-) -> Result<HashMap<String, Rc<dyn ShellCommand>>, AnyError> {
- let mut result = HashMap::new();
- let snapshot = npm_resolver.snapshot();
- for id in snapshot.top_level_packages() {
- let package_folder = npm_resolver.resolve_pkg_folder_from_pkg_id(id)?;
- let bin_commands =
- node_resolver.resolve_binary_commands(&package_folder)?;
- for bin_command in bin_commands {
- result.insert(
- bin_command.to_string(),
- Rc::new(NpmPackageBinCommand {
- name: bin_command,
- npm_package: id.nv.clone(),
- }) as Rc<dyn ShellCommand>,
- );
- }
- }
- if !result.contains_key("npx") {
- result.insert("npx".to_string(), Rc::new(NpxCommand));
- }
- Ok(result)
-}
-
-#[cfg(test)]
-mod test {
- use super::*;
-
- #[test]
- fn test_prepend_to_path() {
- let mut env_vars = HashMap::new();
-
- prepend_to_path(&mut env_vars, "/example".to_string());
- assert_eq!(
- env_vars,
- HashMap::from([("PATH".to_string(), "/example".to_string())])
- );
-
- prepend_to_path(&mut env_vars, "/example2".to_string());
- let separator = if cfg!(windows) { ";" } else { ":" };
- assert_eq!(
- env_vars,
- HashMap::from([(
- "PATH".to_string(),
- format!("/example2{}/example", separator)
- )])
- );
-
- env_vars.get_mut("PATH").unwrap().clear();
- prepend_to_path(&mut env_vars, "/example".to_string());
- assert_eq!(
- env_vars,
- HashMap::from([("PATH".to_string(), "/example".to_string())])
- );
- }
-
- #[test]
- fn test_resolve_execution_path_from_npx_shim() {
- // example shim on unix
- 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 windows
- let windows_shim = r#"#!/bin/sh
-basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
-
-case `uname` in
- *CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
-esac
-
-if [ -x "$basedir/node" ]; then
- exec "$basedir/node" "$basedir/../example/bin/example" "$@"
-else
- exec node "$basedir/../example/bin/example" "$@"
-fi"#;
- assert_eq!(
- resolve_execution_path_from_npx_shim(path.clone(), windows_shim).unwrap(),
- path.parent().unwrap().join("../example/bin/example")
- );
- }
-}