From 6541a0a9fd818424688003c617e4a84c3cf7d34d Mon Sep 17 00:00:00 2001 From: David Sherret Date: Fri, 9 Dec 2022 09:40:48 -0500 Subject: [PATCH] refactor: cleanup main.rs (#16996) 1. Extracts out some code from main.rs 2. Inlines all the `x_command` functions in main.rs --- cli/graph_util.rs | 112 +++++ cli/main.rs | 833 +++++--------------------------------- cli/proc_state.rs | 25 ++ cli/tools/bundle.rs | 158 ++++++++ cli/tools/coverage/mod.rs | 5 + cli/tools/installer.rs | 37 +- cli/tools/mod.rs | 2 + cli/tools/repl/mod.rs | 22 +- cli/tools/run.rs | 169 ++++++++ cli/tools/standalone.rs | 61 ++- 10 files changed, 664 insertions(+), 760 deletions(-) create mode 100644 cli/tools/bundle.rs create mode 100644 cli/tools/run.rs diff --git a/cli/graph_util.rs b/cli/graph_util.rs index 7cef4b6f15..253d556e0a 100644 --- a/cli/graph_util.rs +++ b/cli/graph_util.rs @@ -1,15 +1,24 @@ // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. use crate::args::Lockfile; +use crate::args::TsConfigType; use crate::args::TsTypeLib; +use crate::args::TypeCheckMode; +use crate::cache; +use crate::cache::TypeCheckCache; use crate::colors; use crate::errors::get_error_class_name; use crate::npm::resolve_npm_package_reqs; use crate::npm::NpmPackageReference; use crate::npm::NpmPackageReq; +use crate::proc_state::ProcState; +use crate::resolver::CliResolver; +use crate::tools::check; +use deno_core::anyhow::bail; use deno_core::error::custom_error; use deno_core::error::AnyError; +use deno_core::parking_lot::RwLock; use deno_core::ModuleSpecifier; use deno_graph::Dependency; use deno_graph::GraphImport; @@ -19,6 +28,7 @@ use deno_graph::ModuleGraphError; use deno_graph::ModuleKind; use deno_graph::Range; use deno_graph::Resolved; +use deno_runtime::permissions::Permissions; use std::collections::BTreeMap; use std::collections::HashMap; use std::collections::HashSet; @@ -496,3 +506,105 @@ pub fn graph_lock_or_exit(graph: &ModuleGraph, lockfile: &mut Lockfile) { } } } + +pub async fn create_graph_and_maybe_check( + root: ModuleSpecifier, + ps: &ProcState, +) -> Result, AnyError> { + let mut cache = cache::FetchCacher::new( + ps.emit_cache.clone(), + ps.file_fetcher.clone(), + Permissions::allow_all(), + Permissions::allow_all(), + ); + let maybe_imports = ps.options.to_maybe_imports()?; + let maybe_cli_resolver = CliResolver::maybe_new( + ps.options.to_maybe_jsx_import_source_config(), + ps.maybe_import_map.clone(), + ); + let maybe_graph_resolver = + maybe_cli_resolver.as_ref().map(|r| r.as_graph_resolver()); + let analyzer = ps.parsed_source_cache.as_analyzer(); + let graph = Arc::new( + deno_graph::create_graph( + vec![(root, deno_graph::ModuleKind::Esm)], + &mut cache, + deno_graph::GraphOptions { + is_dynamic: false, + imports: maybe_imports, + resolver: maybe_graph_resolver, + module_analyzer: Some(&*analyzer), + reporter: None, + }, + ) + .await, + ); + + let check_js = ps.options.check_js(); + let mut graph_data = GraphData::default(); + graph_data.add_graph(&graph, false); + graph_data + .check( + &graph.roots, + ps.options.type_check_mode() != TypeCheckMode::None, + check_js, + ) + .unwrap()?; + ps.npm_resolver + .add_package_reqs(graph_data.npm_package_reqs().clone()) + .await?; + if let Some(lockfile) = &ps.lockfile { + graph_lock_or_exit(&graph, &mut lockfile.lock()); + } + + if ps.options.type_check_mode() != TypeCheckMode::None { + let ts_config_result = + ps.options.resolve_ts_config_for_emit(TsConfigType::Check { + lib: ps.options.ts_type_lib_window(), + })?; + if let Some(ignored_options) = ts_config_result.maybe_ignored_options { + eprintln!("{}", ignored_options); + } + let maybe_config_specifier = ps.options.maybe_config_file_specifier(); + let cache = TypeCheckCache::new(&ps.dir.type_checking_cache_db_file_path()); + let check_result = check::check( + &graph.roots, + Arc::new(RwLock::new(graph_data)), + &cache, + ps.npm_resolver.clone(), + check::CheckOptions { + type_check_mode: ps.options.type_check_mode(), + debug: ps.options.log_level() == Some(log::Level::Debug), + maybe_config_specifier, + ts_config: ts_config_result.ts_config, + log_checks: true, + reload: ps.options.reload_flag(), + }, + )?; + log::debug!("{}", check_result.stats); + if !check_result.diagnostics.is_empty() { + return Err(check_result.diagnostics.into()); + } + } + + Ok(graph) +} + +pub fn error_for_any_npm_specifier( + graph: &deno_graph::ModuleGraph, +) -> Result<(), AnyError> { + let first_npm_specifier = graph + .specifiers() + .filter_map(|(_, r)| match r { + Ok((specifier, kind, _)) if kind == deno_graph::ModuleKind::External => { + Some(specifier.clone()) + } + _ => None, + }) + .next(); + if let Some(npm_specifier) = first_npm_specifier { + bail!("npm specifiers have not yet been implemented for this sub command (https://github.com/denoland/deno/issues/15960). Found: {}", npm_specifier) + } else { + Ok(()) + } +} diff --git a/cli/main.rs b/cli/main.rs index 05dc768f7d..a47c9a01a1 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -26,731 +26,23 @@ mod version; mod worker; use crate::args::flags_from_vec; -use crate::args::BenchFlags; -use crate::args::BundleFlags; -use crate::args::CacheFlags; -use crate::args::CheckFlags; -use crate::args::CompileFlags; -use crate::args::CompletionsFlags; -use crate::args::CoverageFlags; use crate::args::DenoSubcommand; -use crate::args::DocFlags; -use crate::args::EvalFlags; use crate::args::Flags; -use crate::args::FmtFlags; -use crate::args::InfoFlags; -use crate::args::InitFlags; -use crate::args::InstallFlags; -use crate::args::LintFlags; -use crate::args::ReplFlags; -use crate::args::RunFlags; -use crate::args::TaskFlags; -use crate::args::TestFlags; -use crate::args::TsConfigType; -use crate::args::TypeCheckMode; -use crate::args::UninstallFlags; -use crate::args::UpgradeFlags; -use crate::args::VendorFlags; -use crate::cache::TypeCheckCache; -use crate::file_fetcher::File; -use crate::graph_util::graph_lock_or_exit; use crate::proc_state::ProcState; use crate::resolver::CliResolver; -use crate::tools::check; use crate::util::display; -use crate::util::file_watcher::ResolutionResult; use args::CliOptions; -use deno_ast::MediaType; -use deno_core::anyhow::bail; -use deno_core::error::generic_error; +use deno_core::anyhow::Context; use deno_core::error::AnyError; use deno_core::error::JsError; -use deno_core::futures::future::FutureExt; -use deno_core::futures::Future; -use deno_core::parking_lot::RwLock; -use deno_core::resolve_url_or_path; use deno_core::v8_set_flags; -use deno_core::ModuleSpecifier; use deno_runtime::colors; use deno_runtime::fmt_errors::format_js_error; -use deno_runtime::permissions::Permissions; use deno_runtime::tokio_util::run_local; -use graph_util::GraphData; -use log::debug; -use log::info; -use npm::NpmPackageReference; use std::env; -use std::io::Read; use std::iter::once; use std::path::PathBuf; -use std::pin::Pin; -use std::sync::Arc; -use worker::create_main_worker; - -async fn compile_command( - flags: Flags, - compile_flags: CompileFlags, -) -> Result { - let ps = ProcState::build(flags.clone()).await?; - let module_specifier = resolve_url_or_path(&compile_flags.source_file)?; - let deno_dir = &ps.dir; - - let output_path = - tools::standalone::resolve_compile_executable_output_path(&compile_flags)?; - - let graph = Arc::try_unwrap( - create_graph_and_maybe_check(module_specifier.clone(), &ps).await?, - ) - .unwrap(); - - // at the moment, we don't support npm specifiers in deno_compile, so show an error - error_for_any_npm_specifier(&graph)?; - - graph.valid()?; - - let parser = ps.parsed_source_cache.as_capturing_parser(); - let eszip = eszip::EszipV2::from_graph(graph, &parser, Default::default())?; - - info!( - "{} {}", - colors::green("Compile"), - module_specifier.to_string() - ); - - // Select base binary based on target - let original_binary = - tools::standalone::get_base_binary(deno_dir, compile_flags.target.clone()) - .await?; - - let final_bin = tools::standalone::create_standalone_binary( - original_binary, - eszip, - module_specifier.clone(), - &compile_flags, - ps, - ) - .await?; - - info!("{} {}", colors::green("Emit"), output_path.display()); - - tools::standalone::write_standalone_binary(output_path, final_bin).await?; - - Ok(0) -} - -async fn init_command( - _flags: Flags, - init_flags: InitFlags, -) -> Result { - tools::init::init_project(init_flags).await?; - Ok(0) -} - -async fn info_command( - flags: Flags, - info_flags: InfoFlags, -) -> Result { - tools::info::info(flags, info_flags).await?; - Ok(0) -} - -async fn install_command( - flags: Flags, - install_flags: InstallFlags, -) -> Result { - let ps = ProcState::build(flags.clone()).await?; - // ensure the module is cached - load_and_type_check(&ps, &[install_flags.module_url.clone()]).await?; - tools::installer::install(flags, install_flags)?; - Ok(0) -} - -async fn uninstall_command( - uninstall_flags: UninstallFlags, -) -> Result { - tools::installer::uninstall(uninstall_flags.name, uninstall_flags.root)?; - Ok(0) -} - -async fn lsp_command() -> Result { - lsp::start().await?; - Ok(0) -} - -async fn lint_command( - flags: Flags, - lint_flags: LintFlags, -) -> Result { - if lint_flags.rules { - tools::lint::print_rules_list(lint_flags.json); - return Ok(0); - } - - tools::lint::lint(flags, lint_flags).await?; - Ok(0) -} - -async fn cache_command( - flags: Flags, - cache_flags: CacheFlags, -) -> Result { - let ps = ProcState::build(flags).await?; - load_and_type_check(&ps, &cache_flags.files).await?; - ps.cache_module_emits()?; - Ok(0) -} - -async fn check_command( - flags: Flags, - check_flags: CheckFlags, -) -> Result { - let ps = ProcState::build(flags).await?; - load_and_type_check(&ps, &check_flags.files).await?; - Ok(0) -} - -async fn load_and_type_check( - ps: &ProcState, - files: &[String], -) -> Result<(), AnyError> { - let lib = ps.options.ts_type_lib_window(); - - let specifiers = files - .iter() - .map(|file| resolve_url_or_path(file)) - .collect::, _>>()?; - ps.prepare_module_load( - specifiers, - false, - lib, - Permissions::allow_all(), - Permissions::allow_all(), - false, - ) - .await?; - - Ok(()) -} - -async fn eval_command( - flags: Flags, - eval_flags: EvalFlags, -) -> Result { - // deno_graph works off of extensions for local files to determine the media - // type, and so our "fake" specifier needs to have the proper extension. - let main_module = - resolve_url_or_path(&format!("./$deno$eval.{}", eval_flags.ext))?; - let ps = ProcState::build(flags).await?; - let permissions = - Permissions::from_options(&ps.options.permissions_options())?; - let mut worker = - create_main_worker(&ps, main_module.clone(), permissions).await?; - // Create a dummy source file. - let source_code = if eval_flags.print { - format!("console.log({})", eval_flags.code) - } else { - eval_flags.code - } - .into_bytes(); - - let file = File { - local: main_module.clone().to_file_path().unwrap(), - maybe_types: None, - media_type: MediaType::Unknown, - source: String::from_utf8(source_code)?.into(), - specifier: main_module.clone(), - maybe_headers: None, - }; - - // Save our fake file into file fetcher cache - // to allow module access by TS compiler. - ps.file_fetcher.insert_cached(file); - let exit_code = worker.run().await?; - Ok(exit_code) -} - -async fn create_graph_and_maybe_check( - root: ModuleSpecifier, - ps: &ProcState, -) -> Result, AnyError> { - let mut cache = cache::FetchCacher::new( - ps.emit_cache.clone(), - ps.file_fetcher.clone(), - Permissions::allow_all(), - Permissions::allow_all(), - ); - let maybe_imports = ps.options.to_maybe_imports()?; - let maybe_cli_resolver = CliResolver::maybe_new( - ps.options.to_maybe_jsx_import_source_config(), - ps.maybe_import_map.clone(), - ); - let maybe_graph_resolver = - maybe_cli_resolver.as_ref().map(|r| r.as_graph_resolver()); - let analyzer = ps.parsed_source_cache.as_analyzer(); - let graph = Arc::new( - deno_graph::create_graph( - vec![(root, deno_graph::ModuleKind::Esm)], - &mut cache, - deno_graph::GraphOptions { - is_dynamic: false, - imports: maybe_imports, - resolver: maybe_graph_resolver, - module_analyzer: Some(&*analyzer), - reporter: None, - }, - ) - .await, - ); - - let check_js = ps.options.check_js(); - let mut graph_data = GraphData::default(); - graph_data.add_graph(&graph, false); - graph_data - .check( - &graph.roots, - ps.options.type_check_mode() != TypeCheckMode::None, - check_js, - ) - .unwrap()?; - ps.npm_resolver - .add_package_reqs(graph_data.npm_package_reqs().clone()) - .await?; - if let Some(lockfile) = &ps.lockfile { - graph_lock_or_exit(&graph, &mut lockfile.lock()); - } - - if ps.options.type_check_mode() != TypeCheckMode::None { - let ts_config_result = - ps.options.resolve_ts_config_for_emit(TsConfigType::Check { - lib: ps.options.ts_type_lib_window(), - })?; - if let Some(ignored_options) = ts_config_result.maybe_ignored_options { - eprintln!("{}", ignored_options); - } - let maybe_config_specifier = ps.options.maybe_config_file_specifier(); - let cache = TypeCheckCache::new(&ps.dir.type_checking_cache_db_file_path()); - let check_result = check::check( - &graph.roots, - Arc::new(RwLock::new(graph_data)), - &cache, - ps.npm_resolver.clone(), - check::CheckOptions { - type_check_mode: ps.options.type_check_mode(), - debug: ps.options.log_level() == Some(log::Level::Debug), - maybe_config_specifier, - ts_config: ts_config_result.ts_config, - log_checks: true, - reload: ps.options.reload_flag(), - }, - )?; - debug!("{}", check_result.stats); - if !check_result.diagnostics.is_empty() { - return Err(check_result.diagnostics.into()); - } - } - - Ok(graph) -} - -fn bundle_module_graph( - graph: &deno_graph::ModuleGraph, - ps: &ProcState, -) -> Result { - info!("{} {}", colors::green("Bundle"), graph.roots[0].0); - - let ts_config_result = ps - .options - .resolve_ts_config_for_emit(TsConfigType::Bundle)?; - if ps.options.type_check_mode() == TypeCheckMode::None { - if let Some(ignored_options) = ts_config_result.maybe_ignored_options { - eprintln!("{}", ignored_options); - } - } - - deno_emit::bundle_graph( - graph, - deno_emit::BundleOptions { - bundle_type: deno_emit::BundleType::Module, - emit_options: ts_config_result.ts_config.into(), - emit_ignore_directives: true, - }, - ) -} - -async fn bundle_command( - flags: Flags, - bundle_flags: BundleFlags, -) -> Result { - let cli_options = Arc::new(CliOptions::from_flags(flags)?); - let resolver = |_| { - let cli_options = cli_options.clone(); - let source_file1 = bundle_flags.source_file.clone(); - let source_file2 = bundle_flags.source_file.clone(); - async move { - let module_specifier = resolve_url_or_path(&source_file1)?; - - debug!(">>>>> bundle START"); - let ps = ProcState::from_options(cli_options).await?; - let graph = create_graph_and_maybe_check(module_specifier, &ps).await?; - - let mut paths_to_watch: Vec = graph - .specifiers() - .filter_map(|(_, r)| { - r.as_ref().ok().and_then(|(s, _, _)| s.to_file_path().ok()) - }) - .collect(); - - if let Ok(Some(import_map_path)) = ps - .options - .resolve_import_map_specifier() - .map(|ms| ms.and_then(|ref s| s.to_file_path().ok())) - { - paths_to_watch.push(import_map_path); - } - - Ok((paths_to_watch, graph, ps)) - } - .map(move |result| match result { - Ok((paths_to_watch, graph, ps)) => ResolutionResult::Restart { - paths_to_watch, - result: Ok((ps, graph)), - }, - Err(e) => ResolutionResult::Restart { - paths_to_watch: vec![PathBuf::from(source_file2)], - result: Err(e), - }, - }) - }; - - let operation = |(ps, graph): (ProcState, Arc)| { - let out_file = bundle_flags.out_file.clone(); - async move { - // at the moment, we don't support npm specifiers in deno bundle, so show an error - error_for_any_npm_specifier(&graph)?; - - let bundle_output = bundle_module_graph(graph.as_ref(), &ps)?; - debug!(">>>>> bundle END"); - - if let Some(out_file) = out_file.as_ref() { - let output_bytes = bundle_output.code.as_bytes(); - let output_len = output_bytes.len(); - util::fs::write_file(out_file, output_bytes, 0o644)?; - info!( - "{} {:?} ({})", - colors::green("Emit"), - out_file, - colors::gray(display::human_size(output_len as f64)) - ); - if let Some(bundle_map) = bundle_output.maybe_map { - let map_bytes = bundle_map.as_bytes(); - let map_len = map_bytes.len(); - let ext = if let Some(curr_ext) = out_file.extension() { - format!("{}.map", curr_ext.to_string_lossy()) - } else { - "map".to_string() - }; - let map_out_file = out_file.with_extension(ext); - util::fs::write_file(&map_out_file, map_bytes, 0o644)?; - info!( - "{} {:?} ({})", - colors::green("Emit"), - map_out_file, - colors::gray(display::human_size(map_len as f64)) - ); - } - } else { - println!("{}", bundle_output.code); - } - - Ok(()) - } - }; - - if cli_options.watch_paths().is_some() { - util::file_watcher::watch_func( - resolver, - operation, - util::file_watcher::PrintConfig { - job_name: "Bundle".to_string(), - clear_screen: !cli_options.no_clear_screen(), - }, - ) - .await?; - } else { - let module_graph = - if let ResolutionResult::Restart { result, .. } = resolver(None).await { - result? - } else { - unreachable!(); - }; - operation(module_graph).await?; - } - - Ok(0) -} - -fn error_for_any_npm_specifier( - graph: &deno_graph::ModuleGraph, -) -> Result<(), AnyError> { - let first_npm_specifier = graph - .specifiers() - .filter_map(|(_, r)| match r { - Ok((specifier, kind, _)) if kind == deno_graph::ModuleKind::External => { - Some(specifier.clone()) - } - _ => None, - }) - .next(); - if let Some(npm_specifier) = first_npm_specifier { - bail!("npm specifiers have not yet been implemented for this sub command (https://github.com/denoland/deno/issues/15960). Found: {}", npm_specifier) - } else { - Ok(()) - } -} - -async fn doc_command( - flags: Flags, - doc_flags: DocFlags, -) -> Result { - tools::doc::print_docs(flags, doc_flags).await?; - Ok(0) -} - -async fn format_command( - flags: Flags, - fmt_flags: FmtFlags, -) -> Result { - let config = CliOptions::from_flags(flags)?; - - if fmt_flags.files.len() == 1 && fmt_flags.files[0].to_string_lossy() == "-" { - let maybe_fmt_config = config.to_fmt_config()?; - tools::fmt::format_stdin( - fmt_flags, - maybe_fmt_config.map(|c| c.options).unwrap_or_default(), - )?; - return Ok(0); - } - - tools::fmt::format(&config, fmt_flags).await?; - Ok(0) -} - -async fn repl_command( - flags: Flags, - repl_flags: ReplFlags, -) -> Result { - let main_module = resolve_url_or_path("./$deno$repl.ts").unwrap(); - let ps = ProcState::build(flags).await?; - let mut worker = create_main_worker( - &ps, - main_module.clone(), - Permissions::from_options(&ps.options.permissions_options())?, - ) - .await?; - worker.setup_repl().await?; - tools::repl::run(&ps, worker.into_main_worker(), repl_flags).await -} - -async fn run_from_stdin(flags: Flags) -> Result { - let ps = ProcState::build(flags).await?; - let main_module = resolve_url_or_path("./$deno$stdin.ts").unwrap(); - let mut worker = create_main_worker( - &ps.clone(), - main_module.clone(), - Permissions::from_options(&ps.options.permissions_options())?, - ) - .await?; - - let mut source = Vec::new(); - std::io::stdin().read_to_end(&mut source)?; - // Create a dummy source file. - let source_file = File { - local: main_module.clone().to_file_path().unwrap(), - maybe_types: None, - media_type: MediaType::TypeScript, - source: String::from_utf8(source)?.into(), - specifier: main_module.clone(), - maybe_headers: None, - }; - // Save our fake file into file fetcher cache - // to allow module access by TS compiler - ps.file_fetcher.insert_cached(source_file); - - let exit_code = worker.run().await?; - Ok(exit_code) -} - -// TODO(bartlomieju): this function is not handling `exit_code` set by the runtime -// code properly. -async fn run_with_watch(flags: Flags, script: String) -> Result { - let flags = Arc::new(flags); - let main_module = resolve_url_or_path(&script)?; - let (sender, receiver) = tokio::sync::mpsc::unbounded_channel(); - - let operation = |(sender, main_module): ( - tokio::sync::mpsc::UnboundedSender>, - ModuleSpecifier, - )| { - let flags = flags.clone(); - Ok(async move { - let ps = - ProcState::build_for_file_watcher((*flags).clone(), sender.clone()) - .await?; - let permissions = - Permissions::from_options(&ps.options.permissions_options())?; - let worker = - create_main_worker(&ps, main_module.clone(), permissions).await?; - worker.run_for_watcher().await?; - - Ok(()) - }) - }; - - util::file_watcher::watch_func2( - receiver, - operation, - (sender, main_module), - util::file_watcher::PrintConfig { - job_name: "Process".to_string(), - clear_screen: !flags.no_clear_screen, - }, - ) - .await?; - - Ok(0) -} - -async fn run_command( - flags: Flags, - run_flags: RunFlags, -) -> Result { - // Read script content from stdin - if run_flags.is_stdin() { - return run_from_stdin(flags).await; - } - - if !flags.has_permission() && flags.has_permission_in_argv() { - log::warn!( - "{}", - crate::colors::yellow( - r#"Permission flags have likely been incorrectly set after the script argument. -To grant permissions, set them before the script argument. For example: - deno run --allow-read=. main.js"# - ) - ); - } - - if flags.watch.is_some() { - return run_with_watch(flags, run_flags.script).await; - } - - // TODO(bartlomieju): actually I think it will also fail if there's an import - // map specified and bare specifier is used on the command line - this should - // probably call `ProcState::resolve` instead - let ps = ProcState::build(flags).await?; - - // Run a background task that checks for available upgrades. If an earlier - // run of this background task found a new version of Deno. - tools::upgrade::check_for_upgrades(ps.dir.upgrade_check_file_path()); - - let main_module = if NpmPackageReference::from_str(&run_flags.script).is_ok() - { - ModuleSpecifier::parse(&run_flags.script)? - } else { - resolve_url_or_path(&run_flags.script)? - }; - let permissions = - Permissions::from_options(&ps.options.permissions_options())?; - let mut worker = - create_main_worker(&ps, main_module.clone(), permissions).await?; - - let exit_code = worker.run().await?; - Ok(exit_code) -} - -async fn task_command( - flags: Flags, - task_flags: TaskFlags, -) -> Result { - tools::task::execute_script(flags, task_flags).await -} - -async fn coverage_command( - flags: Flags, - coverage_flags: CoverageFlags, -) -> Result { - if coverage_flags.files.is_empty() { - return Err(generic_error("No matching coverage profiles found")); - } - - tools::coverage::cover_files(flags, coverage_flags).await?; - Ok(0) -} - -async fn bench_command( - flags: Flags, - bench_flags: BenchFlags, -) -> Result { - if flags.watch.is_some() { - tools::bench::run_benchmarks_with_watch(flags, bench_flags).await?; - } else { - tools::bench::run_benchmarks(flags, bench_flags).await?; - } - - Ok(0) -} - -async fn test_command( - flags: Flags, - test_flags: TestFlags, -) -> Result { - if let Some(ref coverage_dir) = flags.coverage_dir { - std::fs::create_dir_all(coverage_dir)?; - env::set_var( - "DENO_UNSTABLE_COVERAGE_DIR", - PathBuf::from(coverage_dir).canonicalize()?, - ); - } - - if flags.watch.is_some() { - tools::test::run_tests_with_watch(flags, test_flags).await?; - } else { - tools::test::run_tests(flags, test_flags).await?; - } - - Ok(0) -} - -async fn completions_command( - _flags: Flags, - completions_flags: CompletionsFlags, -) -> Result { - display::write_to_stdout_ignore_sigpipe(&completions_flags.buf)?; - Ok(0) -} - -async fn types_command(flags: Flags) -> Result { - let types = tsc::get_types_declaration_file_text(flags.unstable); - display::write_to_stdout_ignore_sigpipe(types.as_bytes())?; - Ok(0) -} - -async fn upgrade_command( - _flags: Flags, - upgrade_flags: UpgradeFlags, -) -> Result { - tools::upgrade::upgrade(upgrade_flags).await?; - Ok(0) -} - -async fn vendor_command( - flags: Flags, - vendor_flags: VendorFlags, -) -> Result { - tools::vendor::vendor(flags, vendor_flags).await?; - Ok(0) -} fn init_v8_flags(v8_flags: &[String]) { let v8_flags_includes_help = v8_flags @@ -776,74 +68,139 @@ fn init_v8_flags(v8_flags: &[String]) { } } -fn get_subcommand( - flags: Flags, -) -> Pin>>> { +async fn run_subcommand(flags: Flags) -> Result { match flags.subcommand.clone() { DenoSubcommand::Bench(bench_flags) => { - bench_command(flags, bench_flags).boxed_local() + if flags.watch.is_some() { + tools::bench::run_benchmarks_with_watch(flags, bench_flags).await?; + } else { + tools::bench::run_benchmarks(flags, bench_flags).await?; + } + Ok(0) } DenoSubcommand::Bundle(bundle_flags) => { - bundle_command(flags, bundle_flags).boxed_local() + tools::bundle::bundle(flags, bundle_flags).await?; + Ok(0) } DenoSubcommand::Doc(doc_flags) => { - doc_command(flags, doc_flags).boxed_local() + tools::doc::print_docs(flags, doc_flags).await?; + Ok(0) } DenoSubcommand::Eval(eval_flags) => { - eval_command(flags, eval_flags).boxed_local() + tools::run::eval_command(flags, eval_flags).await } DenoSubcommand::Cache(cache_flags) => { - cache_command(flags, cache_flags).boxed_local() + let ps = ProcState::build(flags).await?; + ps.load_and_type_check_files(&cache_flags.files).await?; + ps.cache_module_emits()?; + Ok(0) } DenoSubcommand::Check(check_flags) => { - check_command(flags, check_flags).boxed_local() + let ps = ProcState::build(flags).await?; + ps.load_and_type_check_files(&check_flags.files).await?; + Ok(0) } DenoSubcommand::Compile(compile_flags) => { - compile_command(flags, compile_flags).boxed_local() + tools::standalone::compile(flags, compile_flags).await?; + Ok(0) } DenoSubcommand::Coverage(coverage_flags) => { - coverage_command(flags, coverage_flags).boxed_local() + tools::coverage::cover_files(flags, coverage_flags).await?; + Ok(0) } DenoSubcommand::Fmt(fmt_flags) => { - format_command(flags, fmt_flags).boxed_local() + let config = CliOptions::from_flags(flags)?; + + if fmt_flags.files.len() == 1 + && fmt_flags.files[0].to_string_lossy() == "-" + { + let maybe_fmt_config = config.to_fmt_config()?; + tools::fmt::format_stdin( + fmt_flags, + maybe_fmt_config.map(|c| c.options).unwrap_or_default(), + )?; + } else { + tools::fmt::format(&config, fmt_flags).await?; + } + Ok(0) } DenoSubcommand::Init(init_flags) => { - init_command(flags, init_flags).boxed_local() + tools::init::init_project(init_flags).await?; + Ok(0) } DenoSubcommand::Info(info_flags) => { - info_command(flags, info_flags).boxed_local() + tools::info::info(flags, info_flags).await?; + Ok(0) } DenoSubcommand::Install(install_flags) => { - install_command(flags, install_flags).boxed_local() + tools::installer::install_command(flags, install_flags).await?; + Ok(0) } DenoSubcommand::Uninstall(uninstall_flags) => { - uninstall_command(uninstall_flags).boxed_local() + tools::installer::uninstall(uninstall_flags.name, uninstall_flags.root)?; + Ok(0) + } + DenoSubcommand::Lsp => { + lsp::start().await?; + Ok(0) } - DenoSubcommand::Lsp => lsp_command().boxed_local(), DenoSubcommand::Lint(lint_flags) => { - lint_command(flags, lint_flags).boxed_local() + if lint_flags.rules { + tools::lint::print_rules_list(lint_flags.json); + } else { + tools::lint::lint(flags, lint_flags).await?; + } + Ok(0) } DenoSubcommand::Repl(repl_flags) => { - repl_command(flags, repl_flags).boxed_local() + tools::repl::run(flags, repl_flags).await } DenoSubcommand::Run(run_flags) => { - run_command(flags, run_flags).boxed_local() + if run_flags.is_stdin() { + tools::run::run_from_stdin(flags).await + } else { + tools::run::run_script(flags, run_flags).await + } } DenoSubcommand::Task(task_flags) => { - task_command(flags, task_flags).boxed_local() + tools::task::execute_script(flags, task_flags).await } DenoSubcommand::Test(test_flags) => { - test_command(flags, test_flags).boxed_local() + if let Some(ref coverage_dir) = flags.coverage_dir { + std::fs::create_dir_all(coverage_dir) + .with_context(|| format!("Failed creating: {}", coverage_dir))?; + // this is set in order to ensure spawned processes use the same + // coverage directory + env::set_var( + "DENO_UNSTABLE_COVERAGE_DIR", + PathBuf::from(coverage_dir).canonicalize()?, + ); + } + + if flags.watch.is_some() { + tools::test::run_tests_with_watch(flags, test_flags).await?; + } else { + tools::test::run_tests(flags, test_flags).await?; + } + + Ok(0) } DenoSubcommand::Completions(completions_flags) => { - completions_command(flags, completions_flags).boxed_local() + display::write_to_stdout_ignore_sigpipe(&completions_flags.buf)?; + Ok(0) + } + DenoSubcommand::Types => { + let types = tsc::get_types_declaration_file_text(flags.unstable); + display::write_to_stdout_ignore_sigpipe(types.as_bytes())?; + Ok(0) } - DenoSubcommand::Types => types_command(flags).boxed_local(), DenoSubcommand::Upgrade(upgrade_flags) => { - upgrade_command(flags, upgrade_flags).boxed_local() + tools::upgrade::upgrade(upgrade_flags).await?; + Ok(0) } DenoSubcommand::Vendor(vendor_flags) => { - vendor_command(flags, vendor_flags).boxed_local() + tools::vendor::vendor(flags, vendor_flags).await?; + Ok(0) } } } @@ -906,7 +263,7 @@ pub fn main() { let args: Vec = env::args().collect(); - let exit_code = async move { + let future = async move { let standalone_res = match standalone::extract_standalone(args.clone()).await { Ok(Some((metadata, eszip))) => standalone::run(eszip, metadata).await, @@ -933,10 +290,10 @@ pub fn main() { util::logger::init(flags.log_level); - get_subcommand(flags).await + run_subcommand(flags).await }; - let exit_code = unwrap_or_exit(run_local(exit_code)); + let exit_code = unwrap_or_exit(run_local(future)); std::process::exit(exit_code); } diff --git a/cli/proc_state.rs b/cli/proc_state.rs index 07f8a0860b..5be3ae62ea 100644 --- a/cli/proc_state.rs +++ b/cli/proc_state.rs @@ -41,6 +41,7 @@ use deno_core::error::AnyError; use deno_core::futures; use deno_core::parking_lot::Mutex; use deno_core::parking_lot::RwLock; +use deno_core::resolve_url_or_path; use deno_core::url::Url; use deno_core::CompiledWasmModuleStore; use deno_core::ModuleSpecifier; @@ -469,6 +470,30 @@ impl ProcState { Ok(()) } + /// Helper around prepare_module_load that loads and type checks + /// the provided files. + pub async fn load_and_type_check_files( + &self, + files: &[String], + ) -> Result<(), AnyError> { + let lib = self.options.ts_type_lib_window(); + + let specifiers = files + .iter() + .map(|file| resolve_url_or_path(file)) + .collect::, _>>()?; + self + .prepare_module_load( + specifiers, + false, + lib, + Permissions::allow_all(), + Permissions::allow_all(), + false, + ) + .await + } + /// Add the builtin node modules to the graph data. pub async fn prepare_node_std_graph(&self) -> Result<(), AnyError> { if self.node_std_graph_prepared.load(Ordering::Relaxed) { diff --git a/cli/tools/bundle.rs b/cli/tools/bundle.rs new file mode 100644 index 0000000000..606bab46fa --- /dev/null +++ b/cli/tools/bundle.rs @@ -0,0 +1,158 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use std::path::PathBuf; +use std::sync::Arc; + +use deno_core::error::AnyError; +use deno_core::futures::FutureExt; +use deno_core::resolve_url_or_path; +use deno_runtime::colors; + +use crate::args::BundleFlags; +use crate::args::CliOptions; +use crate::args::Flags; +use crate::args::TsConfigType; +use crate::args::TypeCheckMode; +use crate::graph_util::create_graph_and_maybe_check; +use crate::graph_util::error_for_any_npm_specifier; +use crate::proc_state::ProcState; +use crate::util; +use crate::util::display; +use crate::util::file_watcher::ResolutionResult; + +pub async fn bundle( + flags: Flags, + bundle_flags: BundleFlags, +) -> Result<(), AnyError> { + let cli_options = Arc::new(CliOptions::from_flags(flags)?); + let resolver = |_| { + let cli_options = cli_options.clone(); + let source_file1 = bundle_flags.source_file.clone(); + let source_file2 = bundle_flags.source_file.clone(); + async move { + let module_specifier = resolve_url_or_path(&source_file1)?; + + log::debug!(">>>>> bundle START"); + let ps = ProcState::from_options(cli_options).await?; + let graph = create_graph_and_maybe_check(module_specifier, &ps).await?; + + let mut paths_to_watch: Vec = graph + .specifiers() + .filter_map(|(_, r)| { + r.as_ref().ok().and_then(|(s, _, _)| s.to_file_path().ok()) + }) + .collect(); + + if let Ok(Some(import_map_path)) = ps + .options + .resolve_import_map_specifier() + .map(|ms| ms.and_then(|ref s| s.to_file_path().ok())) + { + paths_to_watch.push(import_map_path); + } + + Ok((paths_to_watch, graph, ps)) + } + .map(move |result| match result { + Ok((paths_to_watch, graph, ps)) => ResolutionResult::Restart { + paths_to_watch, + result: Ok((ps, graph)), + }, + Err(e) => ResolutionResult::Restart { + paths_to_watch: vec![PathBuf::from(source_file2)], + result: Err(e), + }, + }) + }; + + let operation = |(ps, graph): (ProcState, Arc)| { + let out_file = bundle_flags.out_file.clone(); + async move { + // at the moment, we don't support npm specifiers in deno bundle, so show an error + error_for_any_npm_specifier(&graph)?; + + let bundle_output = bundle_module_graph(graph.as_ref(), &ps)?; + log::debug!(">>>>> bundle END"); + + if let Some(out_file) = out_file.as_ref() { + let output_bytes = bundle_output.code.as_bytes(); + let output_len = output_bytes.len(); + util::fs::write_file(out_file, output_bytes, 0o644)?; + log::info!( + "{} {:?} ({})", + colors::green("Emit"), + out_file, + colors::gray(display::human_size(output_len as f64)) + ); + if let Some(bundle_map) = bundle_output.maybe_map { + let map_bytes = bundle_map.as_bytes(); + let map_len = map_bytes.len(); + let ext = if let Some(curr_ext) = out_file.extension() { + format!("{}.map", curr_ext.to_string_lossy()) + } else { + "map".to_string() + }; + let map_out_file = out_file.with_extension(ext); + util::fs::write_file(&map_out_file, map_bytes, 0o644)?; + log::info!( + "{} {:?} ({})", + colors::green("Emit"), + map_out_file, + colors::gray(display::human_size(map_len as f64)) + ); + } + } else { + println!("{}", bundle_output.code); + } + + Ok(()) + } + }; + + if cli_options.watch_paths().is_some() { + util::file_watcher::watch_func( + resolver, + operation, + util::file_watcher::PrintConfig { + job_name: "Bundle".to_string(), + clear_screen: !cli_options.no_clear_screen(), + }, + ) + .await?; + } else { + let module_graph = + if let ResolutionResult::Restart { result, .. } = resolver(None).await { + result? + } else { + unreachable!(); + }; + operation(module_graph).await?; + } + + Ok(()) +} + +fn bundle_module_graph( + graph: &deno_graph::ModuleGraph, + ps: &ProcState, +) -> Result { + log::info!("{} {}", colors::green("Bundle"), graph.roots[0].0); + + let ts_config_result = ps + .options + .resolve_ts_config_for_emit(TsConfigType::Bundle)?; + if ps.options.type_check_mode() == TypeCheckMode::None { + if let Some(ignored_options) = ts_config_result.maybe_ignored_options { + eprintln!("{}", ignored_options); + } + } + + deno_emit::bundle_graph( + graph, + deno_emit::BundleOptions { + bundle_type: deno_emit::BundleType::Module, + emit_options: ts_config_result.ts_config.into(), + emit_ignore_directives: true, + }, + ) +} diff --git a/cli/tools/coverage/mod.rs b/cli/tools/coverage/mod.rs index aacaf3d836..04ed9d0333 100644 --- a/cli/tools/coverage/mod.rs +++ b/cli/tools/coverage/mod.rs @@ -13,6 +13,7 @@ use deno_ast::MediaType; use deno_ast::ModuleSpecifier; use deno_core::anyhow::anyhow; use deno_core::anyhow::Context; +use deno_core::error::generic_error; use deno_core::error::AnyError; use deno_core::serde_json; use deno_core::sourcemap::SourceMap; @@ -608,6 +609,10 @@ pub async fn cover_files( flags: Flags, coverage_flags: CoverageFlags, ) -> Result<(), AnyError> { + if coverage_flags.files.is_empty() { + return Err(generic_error("No matching coverage profiles found")); + } + let ps = ProcState::build(flags).await?; let script_coverages = diff --git a/cli/tools/installer.rs b/cli/tools/installer.rs index 0e915677a0..fd7f68b717 100644 --- a/cli/tools/installer.rs +++ b/cli/tools/installer.rs @@ -6,6 +6,7 @@ use crate::args::Flags; use crate::args::InstallFlags; use crate::args::TypeCheckMode; use crate::npm::NpmPackageReference; +use crate::proc_state::ProcState; use crate::util::fs::canonicalize_path_maybe_not_exists; use deno_core::anyhow::Context; use deno_core::error::generic_error; @@ -215,7 +216,21 @@ pub fn uninstall(name: String, root: Option) -> Result<(), AnyError> { Ok(()) } -pub fn install( +pub async fn install_command( + flags: Flags, + install_flags: InstallFlags, +) -> Result<(), AnyError> { + // ensure the module is cached + ProcState::build(flags.clone()) + .await? + .load_and_type_check_files(&[install_flags.module_url.clone()]) + .await?; + + // create the install shim + create_install_shim(flags, install_flags) +} + +fn create_install_shim( flags: Flags, install_flags: InstallFlags, ) -> Result<(), AnyError> { @@ -567,7 +582,7 @@ mod tests { let bin_dir = temp_dir.path().join("bin"); std::fs::create_dir(&bin_dir).unwrap(); - install( + create_install_shim( Flags { unstable: true, ..Flags::default() @@ -810,7 +825,7 @@ mod tests { let local_module_url = Url::from_file_path(&local_module).unwrap(); let local_module_str = local_module.to_string_lossy(); - install( + create_install_shim( Flags::default(), InstallFlags { module_url: local_module_str.to_string(), @@ -838,7 +853,7 @@ mod tests { let bin_dir = temp_dir.path().join("bin"); std::fs::create_dir(&bin_dir).unwrap(); - install( + create_install_shim( Flags::default(), InstallFlags { module_url: "http://localhost:4545/echo_server.ts".to_string(), @@ -857,7 +872,7 @@ mod tests { assert!(file_path.exists()); // No force. Install failed. - let no_force_result = install( + let no_force_result = create_install_shim( Flags::default(), InstallFlags { module_url: "http://localhost:4545/cat.ts".to_string(), // using a different URL @@ -877,7 +892,7 @@ mod tests { assert!(file_content.contains("echo_server.ts")); // Force. Install success. - let force_result = install( + let force_result = create_install_shim( Flags::default(), InstallFlags { module_url: "http://localhost:4545/cat.ts".to_string(), // using a different URL @@ -903,7 +918,7 @@ mod tests { let result = config_file.write_all(config.as_bytes()); assert!(result.is_ok()); - let result = install( + let result = create_install_shim( Flags { config_flag: ConfigFlag::Path( config_file_path.to_string_lossy().to_string(), @@ -936,7 +951,7 @@ mod tests { let bin_dir = temp_dir.path().join("bin"); std::fs::create_dir(&bin_dir).unwrap(); - install( + create_install_shim( Flags::default(), InstallFlags { module_url: "http://localhost:4545/echo_server.ts".to_string(), @@ -976,7 +991,7 @@ mod tests { let local_module_str = local_module.to_string_lossy(); std::fs::write(&local_module, "// Some JavaScript I guess").unwrap(); - install( + create_install_shim( Flags::default(), InstallFlags { module_url: local_module_str.to_string(), @@ -1016,7 +1031,7 @@ mod tests { let result = import_map_file.write_all(import_map.as_bytes()); assert!(result.is_ok()); - let result = install( + let result = create_install_shim( Flags { import_map_path: Some(import_map_path.to_string_lossy().to_string()), ..Flags::default() @@ -1062,7 +1077,7 @@ mod tests { Url::from_file_path(module_path).unwrap().to_string(); assert!(file_module_string.starts_with("file:///")); - let result = install( + let result = create_install_shim( Flags::default(), InstallFlags { module_url: file_module_string.to_string(), diff --git a/cli/tools/mod.rs b/cli/tools/mod.rs index b992a2e9e3..595fdd7603 100644 --- a/cli/tools/mod.rs +++ b/cli/tools/mod.rs @@ -1,6 +1,7 @@ // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. pub mod bench; +pub mod bundle; pub mod check; pub mod coverage; pub mod doc; @@ -10,6 +11,7 @@ pub mod init; pub mod installer; pub mod lint; pub mod repl; +pub mod run; pub mod standalone; pub mod task; pub mod test; diff --git a/cli/tools/repl/mod.rs b/cli/tools/repl/mod.rs index 5021051397..cde4efa28f 100644 --- a/cli/tools/repl/mod.rs +++ b/cli/tools/repl/mod.rs @@ -1,11 +1,13 @@ // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. +use crate::args::Flags; use crate::args::ReplFlags; use crate::colors; use crate::proc_state::ProcState; +use crate::worker::create_main_worker; use deno_core::error::AnyError; +use deno_core::resolve_url_or_path; use deno_runtime::permissions::Permissions; -use deno_runtime::worker::MainWorker; use rustyline::error::ReadlineError; mod cdp; @@ -76,11 +78,17 @@ async fn read_eval_file( Ok((*file.source).to_string()) } -pub async fn run( - ps: &ProcState, - worker: MainWorker, - repl_flags: ReplFlags, -) -> Result { +pub async fn run(flags: Flags, repl_flags: ReplFlags) -> Result { + let main_module = resolve_url_or_path("./$deno$repl.ts").unwrap(); + let ps = ProcState::build(flags).await?; + let mut worker = create_main_worker( + &ps, + main_module.clone(), + Permissions::from_options(&ps.options.permissions_options())?, + ) + .await?; + worker.setup_repl().await?; + let worker = worker.into_main_worker(); let mut repl_session = ReplSession::initialize(worker).await?; let mut rustyline_channel = rustyline_channel(); let mut should_exit_on_interrupt = false; @@ -95,7 +103,7 @@ pub async fn run( if let Some(eval_files) = repl_flags.eval_files { for eval_file in eval_files { - match read_eval_file(ps, &eval_file).await { + match read_eval_file(&ps, &eval_file).await { Ok(eval_source) => { let output = repl_session .evaluate_line_and_get_output(&eval_source) diff --git a/cli/tools/run.rs b/cli/tools/run.rs new file mode 100644 index 0000000000..d714c55d32 --- /dev/null +++ b/cli/tools/run.rs @@ -0,0 +1,169 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use std::io::Read; +use std::path::PathBuf; +use std::sync::Arc; + +use deno_ast::MediaType; +use deno_ast::ModuleSpecifier; +use deno_core::error::AnyError; +use deno_core::resolve_url_or_path; +use deno_runtime::permissions::Permissions; + +use crate::args::EvalFlags; +use crate::args::Flags; +use crate::args::RunFlags; +use crate::file_fetcher::File; +use crate::npm::NpmPackageReference; +use crate::proc_state::ProcState; +use crate::util; +use crate::worker::create_main_worker; + +pub async fn run_script( + flags: Flags, + run_flags: RunFlags, +) -> Result { + if !flags.has_permission() && flags.has_permission_in_argv() { + log::warn!( + "{}", + crate::colors::yellow( + r#"Permission flags have likely been incorrectly set after the script argument. +To grant permissions, set them before the script argument. For example: + deno run --allow-read=. main.js"# + ) + ); + } + + if flags.watch.is_some() { + return run_with_watch(flags, run_flags.script).await; + } + + // TODO(bartlomieju): actually I think it will also fail if there's an import + // map specified and bare specifier is used on the command line - this should + // probably call `ProcState::resolve` instead + let ps = ProcState::build(flags).await?; + + // Run a background task that checks for available upgrades. If an earlier + // run of this background task found a new version of Deno. + super::upgrade::check_for_upgrades(ps.dir.upgrade_check_file_path()); + + let main_module = if NpmPackageReference::from_str(&run_flags.script).is_ok() + { + ModuleSpecifier::parse(&run_flags.script)? + } else { + resolve_url_or_path(&run_flags.script)? + }; + let permissions = + Permissions::from_options(&ps.options.permissions_options())?; + let mut worker = + create_main_worker(&ps, main_module.clone(), permissions).await?; + + let exit_code = worker.run().await?; + Ok(exit_code) +} + +pub async fn run_from_stdin(flags: Flags) -> Result { + let ps = ProcState::build(flags).await?; + let main_module = resolve_url_or_path("./$deno$stdin.ts").unwrap(); + let mut worker = create_main_worker( + &ps.clone(), + main_module.clone(), + Permissions::from_options(&ps.options.permissions_options())?, + ) + .await?; + + let mut source = Vec::new(); + std::io::stdin().read_to_end(&mut source)?; + // Create a dummy source file. + let source_file = File { + local: main_module.clone().to_file_path().unwrap(), + maybe_types: None, + media_type: MediaType::TypeScript, + source: String::from_utf8(source)?.into(), + specifier: main_module.clone(), + maybe_headers: None, + }; + // Save our fake file into file fetcher cache + // to allow module access by TS compiler + ps.file_fetcher.insert_cached(source_file); + + let exit_code = worker.run().await?; + Ok(exit_code) +} + +// TODO(bartlomieju): this function is not handling `exit_code` set by the runtime +// code properly. +async fn run_with_watch(flags: Flags, script: String) -> Result { + let flags = Arc::new(flags); + let main_module = resolve_url_or_path(&script)?; + let (sender, receiver) = tokio::sync::mpsc::unbounded_channel(); + + let operation = |(sender, main_module): ( + tokio::sync::mpsc::UnboundedSender>, + ModuleSpecifier, + )| { + let flags = flags.clone(); + Ok(async move { + let ps = + ProcState::build_for_file_watcher((*flags).clone(), sender.clone()) + .await?; + let permissions = + Permissions::from_options(&ps.options.permissions_options())?; + let worker = + create_main_worker(&ps, main_module.clone(), permissions).await?; + worker.run_for_watcher().await?; + + Ok(()) + }) + }; + + util::file_watcher::watch_func2( + receiver, + operation, + (sender, main_module), + util::file_watcher::PrintConfig { + job_name: "Process".to_string(), + clear_screen: !flags.no_clear_screen, + }, + ) + .await?; + + Ok(0) +} + +pub async fn eval_command( + flags: Flags, + eval_flags: EvalFlags, +) -> Result { + // deno_graph works off of extensions for local files to determine the media + // type, and so our "fake" specifier needs to have the proper extension. + let main_module = + resolve_url_or_path(&format!("./$deno$eval.{}", eval_flags.ext))?; + let ps = ProcState::build(flags).await?; + let permissions = + Permissions::from_options(&ps.options.permissions_options())?; + let mut worker = + create_main_worker(&ps, main_module.clone(), permissions).await?; + // Create a dummy source file. + let source_code = if eval_flags.print { + format!("console.log({})", eval_flags.code) + } else { + eval_flags.code + } + .into_bytes(); + + let file = File { + local: main_module.clone().to_file_path().unwrap(), + maybe_types: None, + media_type: MediaType::Unknown, + source: String::from_utf8(source_code)?.into(), + specifier: main_module.clone(), + maybe_headers: None, + }; + + // Save our fake file into file fetcher cache + // to allow module access by TS compiler. + ps.file_fetcher.insert_cached(file); + let exit_code = worker.run().await?; + Ok(exit_code) +} diff --git a/cli/tools/standalone.rs b/cli/tools/standalone.rs index 5e9867b12d..f7a258a737 100644 --- a/cli/tools/standalone.rs +++ b/cli/tools/standalone.rs @@ -1,7 +1,10 @@ // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. use crate::args::CompileFlags; +use crate::args::Flags; use crate::cache::DenoDir; +use crate::graph_util::create_graph_and_maybe_check; +use crate::graph_util::error_for_any_npm_specifier; use crate::standalone::Metadata; use crate::standalone::MAGIC_TRAILER; use crate::util::path::path_has_trailing_slash; @@ -14,6 +17,7 @@ use deno_core::resolve_url_or_path; use deno_core::serde_json; use deno_core::url::Url; use deno_graph::ModuleSpecifier; +use deno_runtime::colors; use deno_runtime::deno_fetch::reqwest::Client; use deno_runtime::permissions::Permissions; use std::env; @@ -25,10 +29,59 @@ use std::io::SeekFrom; use std::io::Write; use std::path::Path; use std::path::PathBuf; +use std::sync::Arc; use super::installer::infer_name_from_url; -pub async fn get_base_binary( +pub async fn compile( + flags: Flags, + compile_flags: CompileFlags, +) -> Result<(), AnyError> { + let ps = ProcState::build(flags.clone()).await?; + let module_specifier = resolve_url_or_path(&compile_flags.source_file)?; + let deno_dir = &ps.dir; + + let output_path = resolve_compile_executable_output_path(&compile_flags)?; + + let graph = Arc::try_unwrap( + create_graph_and_maybe_check(module_specifier.clone(), &ps).await?, + ) + .unwrap(); + + // at the moment, we don't support npm specifiers in deno_compile, so show an error + error_for_any_npm_specifier(&graph)?; + + graph.valid()?; + + let parser = ps.parsed_source_cache.as_capturing_parser(); + let eszip = eszip::EszipV2::from_graph(graph, &parser, Default::default())?; + + log::info!( + "{} {}", + colors::green("Compile"), + module_specifier.to_string() + ); + + // Select base binary based on target + let original_binary = + get_base_binary(deno_dir, compile_flags.target.clone()).await?; + + let final_bin = create_standalone_binary( + original_binary, + eszip, + module_specifier.clone(), + &compile_flags, + ps, + ) + .await?; + + log::info!("{} {}", colors::green("Emit"), output_path.display()); + + write_standalone_binary(output_path, final_bin).await?; + Ok(()) +} + +async fn get_base_binary( deno_dir: &DenoDir, target: Option, ) -> Result, AnyError> { @@ -90,7 +143,7 @@ async fn download_base_binary( /// This functions creates a standalone deno binary by appending a bundle /// and magic trailer to the currently executing binary. -pub async fn create_standalone_binary( +async fn create_standalone_binary( mut original_bin: Vec, eszip: eszip::EszipV2, entrypoint: ModuleSpecifier, @@ -159,7 +212,7 @@ pub async fn create_standalone_binary( /// This function writes out a final binary to specified path. If output path /// is not already standalone binary it will return error instead. -pub async fn write_standalone_binary( +async fn write_standalone_binary( output_path: PathBuf, final_bin: Vec, ) -> Result<(), AnyError> { @@ -228,7 +281,7 @@ pub async fn write_standalone_binary( Ok(()) } -pub fn resolve_compile_executable_output_path( +fn resolve_compile_executable_output_path( compile_flags: &CompileFlags, ) -> Result { let module_specifier = resolve_url_or_path(&compile_flags.source_file)?;