2023-01-02 16:00:42 -05:00
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
2022-03-10 20:56:14 -05:00
2023-04-17 15:36:23 -04:00
use crate ::args ::CliOptions ;
2022-06-27 16:54:09 -04:00
use crate ::args ::Flags ;
use crate ::args ::TaskFlags ;
2022-03-10 20:56:14 -05:00
use crate ::colors ;
2023-05-01 14:35:23 -04:00
use crate ::factory ::CliFactory ;
2023-09-29 09:26:25 -04:00
use crate ::npm ::ManagedCliNpmResolver ;
2022-11-28 17:28:54 -05:00
use crate ::util ::fs ::canonicalize_path ;
2022-03-10 20:56:14 -05:00
use deno_core ::anyhow ::bail ;
use deno_core ::anyhow ::Context ;
use deno_core ::error ::AnyError ;
2023-02-22 22:45:35 -05:00
use deno_core ::futures ;
use deno_core ::futures ::future ::LocalBoxFuture ;
2023-04-24 19:44:35 -04:00
use deno_runtime ::deno_node ::NodeResolver ;
2023-08-21 05:53:52 -04:00
use deno_semver ::package ::PackageNv ;
2023-02-22 22:45:35 -05:00
use deno_task_shell ::ExecuteResult ;
use deno_task_shell ::ShellCommand ;
use deno_task_shell ::ShellCommandContext ;
use indexmap ::IndexMap ;
2022-03-10 20:56:14 -05:00
use std ::collections ::HashMap ;
2023-09-29 09:26:25 -04:00
use std ::path ::Path ;
2022-06-08 17:30:16 -04:00
use std ::path ::PathBuf ;
2023-02-22 22:45:35 -05:00
use std ::rc ::Rc ;
2023-05-14 17:40:01 -04:00
use tokio ::task ::LocalSet ;
2022-03-10 20:56:14 -05:00
pub async fn execute_script (
flags : Flags ,
task_flags : TaskFlags ,
) -> Result < i32 , AnyError > {
2023-05-01 14:35:23 -04:00
let factory = CliFactory ::from_flags ( flags ) . await ? ;
let cli_options = factory . cli_options ( ) ;
let tasks_config = cli_options . resolve_tasks_config ( ) ? ;
let maybe_package_json = cli_options . maybe_package_json ( ) ;
2023-02-22 22:45:35 -05:00
let package_json_scripts = maybe_package_json
. as_ref ( )
. and_then ( | p | p . scripts . clone ( ) )
. unwrap_or_default ( ) ;
let task_name = match & task_flags . task {
Some ( task ) = > task ,
None = > {
print_available_tasks ( & tasks_config , & package_json_scripts ) ;
return Ok ( 1 ) ;
}
2022-03-10 20:56:14 -05:00
} ;
2023-02-22 22:45:35 -05:00
if let Some ( script ) = tasks_config . get ( task_name ) {
2023-05-01 14:35:23 -04:00
let config_file_url = cli_options . maybe_config_file_specifier ( ) . unwrap ( ) ;
2023-02-22 22:45:35 -05:00
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 " )
} ;
let cwd = match task_flags . cwd {
Some ( path ) = > canonicalize_path ( & PathBuf ::from ( path ) ) ? ,
None = > config_file_path . parent ( ) . unwrap ( ) . to_owned ( ) ,
} ;
2023-05-01 14:35:23 -04:00
let script = get_script_with_args ( script , cli_options ) ;
2023-02-22 22:45:35 -05:00
output_task ( task_name , & script ) ;
let seq_list = deno_task_shell ::parser ::parse ( & script )
. with_context ( | | format! ( " Error parsing script ' {task_name} '. " ) ) ? ;
let env_vars = collect_env_vars ( ) ;
2023-05-14 17:40:01 -04:00
let local = LocalSet ::new ( ) ;
let future =
deno_task_shell ::execute ( seq_list , env_vars , & cwd , Default ::default ( ) ) ;
let exit_code = local . run_until ( future ) . await ;
2023-02-22 22:45:35 -05:00
Ok ( exit_code )
2023-05-18 15:51:17 -04:00
} else if package_json_scripts . contains_key ( task_name ) {
2023-05-10 20:06:59 -04:00
let package_json_deps_provider = factory . package_json_deps_provider ( ) ;
2023-05-01 14:35:23 -04:00
let npm_resolver = factory . npm_resolver ( ) . await ? ;
let node_resolver = factory . node_resolver ( ) . await ? ;
2023-05-10 20:06:59 -04:00
if let Some ( package_deps ) = package_json_deps_provider . deps ( ) {
2023-03-03 17:27:05 -05:00
for ( key , value ) in package_deps {
if let Err ( err ) = value {
log ::info! (
" {} Ignoring dependency '{}' in package.json because its version requirement failed to parse: {:#} " ,
colors ::yellow ( " Warning " ) ,
key ,
err ,
) ;
}
}
}
2023-05-01 14:35:23 -04:00
2023-09-29 09:26:25 -04:00
// install the npm packages if we're using a managed resolver
if let Some ( npm_resolver ) = npm_resolver . as_managed ( ) {
let package_json_deps_installer =
factory . package_json_deps_installer ( ) . await ? ;
package_json_deps_installer
. ensure_top_level_install ( )
. await ? ;
npm_resolver . resolve_pending ( ) . await ? ;
}
2023-02-24 19:35:43 -05:00
2023-03-03 17:27:05 -05:00
log ::info! (
2023-05-01 14:35:23 -04:00
" {} Currently only basic package.json `scripts` are supported. Programs like `rimraf` or `cross-env` will not work correctly. This will be fixed in an upcoming release. " ,
2023-03-03 17:27:05 -05:00
colors ::yellow ( " Warning " ) ,
) ;
2023-02-22 22:45:35 -05:00
let cwd = match task_flags . cwd {
Some ( path ) = > canonicalize_path ( & PathBuf ::from ( path ) ) ? ,
None = > maybe_package_json
. as_ref ( )
. unwrap ( )
. path
. parent ( )
. unwrap ( )
. to_owned ( ) ,
} ;
2023-05-18 15:51:17 -04:00
// At this point we already checked if the task name exists in package.json.
// We can therefore check for "pre" and "post" scripts too, since we're only
// dealing with package.json here and not deno.json
let task_names = vec! [
format! ( " pre {} " , task_name ) ,
task_name . clone ( ) ,
format! ( " post {} " , task_name ) ,
] ;
for task_name in task_names {
if let Some ( script ) = package_json_scripts . get ( & task_name ) {
let script = get_script_with_args ( script , cli_options ) ;
output_task ( & task_name , & script ) ;
let seq_list = deno_task_shell ::parser ::parse ( & script )
. with_context ( | | format! ( " Error parsing script ' {task_name} '. " ) ) ? ;
2023-09-29 09:26:25 -04:00
let npx_commands = match npm_resolver . as_managed ( ) {
Some ( npm_resolver ) = > {
resolve_npm_commands ( npm_resolver , node_resolver ) ?
}
None = > Default ::default ( ) ,
} ;
let env_vars = match npm_resolver . node_modules_path ( ) {
Some ( dir_path ) = > collect_env_vars_with_node_modules_dir ( & dir_path ) ,
None = > collect_env_vars ( ) ,
} ;
2023-05-18 15:51:17 -04:00
let local = LocalSet ::new ( ) ;
let future =
deno_task_shell ::execute ( seq_list , env_vars , & cwd , npx_commands ) ;
let exit_code = local . run_until ( future ) . await ;
if exit_code > 0 {
return Ok ( exit_code ) ;
}
}
}
Ok ( 0 )
2023-02-22 22:45:35 -05:00
} else {
eprintln! ( " Task not found: {task_name} " ) ;
print_available_tasks ( & tasks_config , & package_json_scripts ) ;
Ok ( 1 )
2022-03-10 20:56:14 -05:00
}
2023-02-22 22:45:35 -05:00
}
2022-03-10 20:56:14 -05:00
2023-04-17 15:36:23 -04:00
fn get_script_with_args ( script : & str , options : & CliOptions ) -> String {
let additional_args = options
2023-02-22 22:45:35 -05:00
. argv ( )
. iter ( )
// surround all the additional arguments in double quotes
2023-06-26 09:10:27 -04:00
// and sanitize any command substitution
2023-02-22 22:45:35 -05:00
. map ( | a | format! ( " \" {} \" " , a . replace ( '"' , " \\ \" " ) . replace ( '$' , " \\ $ " ) ) )
. collect ::< Vec < _ > > ( )
. join ( " " ) ;
let script = format! ( " {script} {additional_args} " ) ;
script . trim ( ) . to_owned ( )
}
2022-03-10 20:56:14 -05:00
2023-02-22 22:45:35 -05:00
fn output_task ( task_name : & str , script : & str ) {
log ::info! (
" {} {} {} " ,
colors ::green ( " Task " ) ,
colors ::cyan ( & task_name ) ,
script ,
) ;
}
2023-09-29 09:26:25 -04:00
fn collect_env_vars_with_node_modules_dir (
node_modules_dir_path : & Path ,
) -> HashMap < String , String > {
let mut env_vars = collect_env_vars ( ) ;
prepend_to_path (
& mut env_vars ,
node_modules_dir_path
. 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 ) ;
}
}
}
2023-02-22 22:45:35 -05:00
fn collect_env_vars ( ) -> HashMap < String , String > {
// get the starting env vars (the PWD env var will be set by deno_task_shell)
let mut env_vars = std ::env ::vars ( ) . collect ::< HashMap < String , String > > ( ) ;
const INIT_CWD_NAME : & str = " INIT_CWD " ;
if ! env_vars . contains_key ( INIT_CWD_NAME ) {
if let Ok ( cwd ) = std ::env ::current_dir ( ) {
// if not set, set an INIT_CWD env var that has the cwd
env_vars
. insert ( INIT_CWD_NAME . to_string ( ) , cwd . to_string_lossy ( ) . to_string ( ) ) ;
}
}
env_vars
}
fn print_available_tasks (
// order can be important, so these use an index map
2023-09-01 16:13:13 -04:00
tasks_config : & IndexMap < String , String > ,
2023-02-22 22:45:35 -05:00
package_json_scripts : & IndexMap < String , String > ,
) {
eprintln! ( " {} " , colors ::green ( " Available tasks: " ) ) ;
let mut had_task = false ;
for ( is_deno , ( key , value ) ) in tasks_config . iter ( ) . map ( | e | ( true , e ) ) . chain (
package_json_scripts
2022-03-10 20:56:14 -05:00
. iter ( )
2023-02-22 22:45:35 -05:00
. filter ( | ( key , _ ) | ! tasks_config . contains_key ( * key ) )
. map ( | e | ( false , e ) ) ,
) {
eprintln! (
" - {}{} " ,
colors ::cyan ( key ) ,
if is_deno {
" " . to_string ( )
} else {
format! ( " {} " , colors ::italic_gray ( " (package.json) " ) )
}
2022-03-11 20:35:18 -05:00
) ;
2023-02-22 22:45:35 -05:00
eprintln! ( " {value} " ) ;
had_task = true ;
}
if ! had_task {
eprintln! ( " {} " , colors ::red ( " No tasks found in configuration file " ) ) ;
}
}
struct NpxCommand ;
2022-10-15 16:46:28 -04:00
2023-02-22 22:45:35 -05:00
impl ShellCommand for NpxCommand {
fn execute (
& self ,
mut context : ShellCommandContext ,
) -> LocalBoxFuture < 'static , ExecuteResult > {
if let Some ( first_arg ) = context . args . get ( 0 ) . cloned ( ) {
if let Some ( command ) = context . state . resolve_command ( & first_arg ) {
let context = ShellCommandContext {
args : context . args . iter ( ) . skip ( 1 ) . cloned ( ) . collect ::< Vec < _ > > ( ) ,
.. context
} ;
command . execute ( context )
} else {
let _ = context
. stderr
. write_line ( & format! ( " npx: could not resolve command ' {first_arg} ' " ) ) ;
Box ::pin ( futures ::future ::ready ( ExecuteResult ::from_exit_code ( 1 ) ) )
2022-10-15 16:46:28 -04:00
}
2023-02-22 22:45:35 -05:00
} else {
let _ = context . stderr . write_line ( " npx: missing command " ) ;
Box ::pin ( futures ::future ::ready ( ExecuteResult ::from_exit_code ( 1 ) ) )
2022-10-15 16:46:28 -04:00
}
2023-02-22 22:45:35 -05:00
}
}
2022-10-15 16:46:28 -04:00
2023-02-22 22:45:35 -05:00
#[ derive(Clone) ]
struct NpmPackageBinCommand {
name : String ,
2023-08-21 05:53:52 -04:00
npm_package : PackageNv ,
2023-02-22 22:45:35 -05:00
}
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 ( ) ) ;
executable_command . execute ( ShellCommandContext { args , .. context } )
}
}
fn resolve_npm_commands (
2023-09-29 09:26:25 -04:00
npm_resolver : & ManagedCliNpmResolver ,
2023-04-24 19:44:35 -04:00
node_resolver : & NodeResolver ,
2023-02-22 22:45:35 -05:00
) -> Result < HashMap < String , Rc < dyn ShellCommand > > , AnyError > {
let mut result = HashMap ::new ( ) ;
2023-04-17 15:36:23 -04:00
let snapshot = npm_resolver . snapshot ( ) ;
2023-02-22 22:45:35 -05:00
for id in snapshot . top_level_packages ( ) {
2023-09-28 16:43:45 -04:00
let package_folder = npm_resolver . resolve_pkg_folder_from_pkg_id ( id ) ? ;
let bin_commands =
node_resolver . resolve_binary_commands ( & package_folder ) ? ;
2023-02-22 22:45:35 -05:00
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 ) ) ;
2022-03-10 20:56:14 -05:00
}
2023-02-22 22:45:35 -05:00
Ok ( result )
2022-03-10 20:56:14 -05:00
}
2023-09-29 09:26:25 -04:00
#[ 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 ( ) ) ] )
) ;
}
}