summaryrefslogtreecommitdiff
path: root/cli/proc_state.rs
diff options
context:
space:
mode:
Diffstat (limited to 'cli/proc_state.rs')
-rw-r--r--cli/proc_state.rs512
1 files changed, 512 insertions, 0 deletions
diff --git a/cli/proc_state.rs b/cli/proc_state.rs
new file mode 100644
index 000000000..e6bc5f997
--- /dev/null
+++ b/cli/proc_state.rs
@@ -0,0 +1,512 @@
+// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
+
+use crate::colors;
+use crate::config_file::ConfigFile;
+use crate::deno_dir;
+use crate::file_fetcher::CacheSetting;
+use crate::file_fetcher::FileFetcher;
+use crate::flags;
+use crate::http_cache;
+use crate::lockfile::Lockfile;
+use crate::module_graph::CheckOptions;
+use crate::module_graph::GraphBuilder;
+use crate::module_graph::TranspileOptions;
+use crate::module_graph::TypeLib;
+use crate::source_maps::SourceMapGetter;
+use crate::specifier_handler::FetchHandler;
+use crate::version;
+
+use deno_core::error::anyhow;
+use deno_core::error::get_custom_error_class;
+use deno_core::error::AnyError;
+use deno_core::error::Context;
+use deno_core::parking_lot::Mutex;
+use deno_core::resolve_url;
+use deno_core::url::Url;
+use deno_core::ModuleSource;
+use deno_core::ModuleSpecifier;
+use deno_core::SharedArrayBufferStore;
+use deno_runtime::deno_broadcast_channel::InMemoryBroadcastChannel;
+use deno_runtime::deno_web::BlobStore;
+use deno_runtime::inspector_server::InspectorServer;
+use deno_runtime::permissions::Permissions;
+use deno_tls::rustls::RootCertStore;
+use deno_tls::rustls_native_certs::load_native_certs;
+use deno_tls::webpki_roots::TLS_SERVER_ROOTS;
+use import_map::ImportMap;
+use log::debug;
+use log::warn;
+use std::collections::HashMap;
+use std::collections::HashSet;
+use std::env;
+use std::fs::File;
+use std::io::BufReader;
+use std::ops::Deref;
+use std::sync::Arc;
+
+/// This structure represents state of single "deno" program.
+///
+/// It is shared by all created workers (thus V8 isolates).
+#[derive(Clone)]
+pub struct ProcState(Arc<Inner>);
+
+pub struct Inner {
+ /// Flags parsed from `argv` contents.
+ pub flags: flags::Flags,
+ pub dir: deno_dir::DenoDir,
+ pub coverage_dir: Option<String>,
+ pub file_fetcher: FileFetcher,
+ pub modules:
+ Arc<Mutex<HashMap<ModuleSpecifier, Result<ModuleSource, AnyError>>>>,
+ pub lockfile: Option<Arc<Mutex<Lockfile>>>,
+ pub maybe_config_file: Option<ConfigFile>,
+ pub maybe_import_map: Option<ImportMap>,
+ pub maybe_inspector_server: Option<Arc<InspectorServer>>,
+ pub root_cert_store: Option<RootCertStore>,
+ pub blob_store: BlobStore,
+ pub broadcast_channel: InMemoryBroadcastChannel,
+ pub shared_array_buffer_store: SharedArrayBufferStore,
+}
+
+impl Deref for ProcState {
+ type Target = Arc<Inner>;
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl ProcState {
+ pub async fn build(flags: flags::Flags) -> Result<Self, AnyError> {
+ let maybe_custom_root = flags
+ .cache_path
+ .clone()
+ .or_else(|| env::var("DENO_DIR").map(String::into).ok());
+ let dir = deno_dir::DenoDir::new(maybe_custom_root)?;
+ let deps_cache_location = dir.root.join("deps");
+ let http_cache = http_cache::HttpCache::new(&deps_cache_location);
+
+ let mut root_cert_store = RootCertStore::empty();
+ let ca_stores: Vec<String> = flags
+ .ca_stores
+ .clone()
+ .or_else(|| {
+ let env_ca_store = env::var("DENO_TLS_CA_STORE").ok()?;
+ Some(
+ env_ca_store
+ .split(',')
+ .map(|s| s.trim().to_string())
+ .filter(|s| !s.is_empty())
+ .collect(),
+ )
+ })
+ .unwrap_or_else(|| vec!["mozilla".to_string()]);
+
+ for store in ca_stores.iter() {
+ match store.as_str() {
+ "mozilla" => {
+ root_cert_store.add_server_trust_anchors(&TLS_SERVER_ROOTS);
+ }
+ "system" => {
+ let roots = load_native_certs()
+ .expect("could not load platform certs")
+ .roots;
+ root_cert_store.roots.extend(roots);
+ }
+ _ => {
+ return Err(anyhow!("Unknown certificate store \"{}\" specified (allowed: \"system,mozilla\")", store));
+ }
+ }
+ }
+
+ let ca_file = flags.ca_file.clone().or_else(|| env::var("DENO_CERT").ok());
+ if let Some(ca_file) = ca_file {
+ let certfile = File::open(&ca_file)?;
+ let mut reader = BufReader::new(certfile);
+
+ // This function does not return specific errors, if it fails give a generic message.
+ if let Err(_err) = root_cert_store.add_pem_file(&mut reader) {
+ return Err(anyhow!("Unable to add pem file to certificate store"));
+ }
+ }
+
+ if let Some(insecure_allowlist) =
+ flags.unsafely_ignore_certificate_errors.as_ref()
+ {
+ let domains = if insecure_allowlist.is_empty() {
+ "for all hostnames".to_string()
+ } else {
+ format!("for: {}", insecure_allowlist.join(", "))
+ };
+ let msg =
+ format!("DANGER: TLS certificate validation is disabled {}", domains);
+ eprintln!("{}", colors::yellow(msg));
+ }
+
+ let cache_usage = if flags.cached_only {
+ CacheSetting::Only
+ } else if !flags.cache_blocklist.is_empty() {
+ CacheSetting::ReloadSome(flags.cache_blocklist.clone())
+ } else if flags.reload {
+ CacheSetting::ReloadAll
+ } else {
+ CacheSetting::Use
+ };
+
+ let blob_store = BlobStore::default();
+ let broadcast_channel = InMemoryBroadcastChannel::default();
+ let shared_array_buffer_store = SharedArrayBufferStore::default();
+
+ let file_fetcher = FileFetcher::new(
+ http_cache,
+ cache_usage,
+ !flags.no_remote,
+ Some(root_cert_store.clone()),
+ blob_store.clone(),
+ flags.unsafely_ignore_certificate_errors.clone(),
+ )?;
+
+ let lockfile = if let Some(filename) = &flags.lock {
+ let lockfile = Lockfile::new(filename.clone(), flags.lock_write)?;
+ Some(Arc::new(Mutex::new(lockfile)))
+ } else {
+ None
+ };
+
+ let maybe_config_file =
+ if let Some(config_path) = flags.config_path.as_ref() {
+ Some(ConfigFile::read(config_path)?)
+ } else {
+ None
+ };
+
+ let maybe_import_map: Option<ImportMap> =
+ match flags.import_map_path.as_ref() {
+ None => None,
+ Some(import_map_url) => {
+ let import_map_specifier =
+ deno_core::resolve_url_or_path(import_map_url).context(format!(
+ "Bad URL (\"{}\") for import map.",
+ import_map_url
+ ))?;
+ let file = file_fetcher
+ .fetch(&import_map_specifier, &mut Permissions::allow_all())
+ .await
+ .context(format!(
+ "Unable to load '{}' import map",
+ import_map_specifier
+ ))?;
+ let import_map =
+ ImportMap::from_json(import_map_specifier.as_str(), &file.source)?;
+ Some(import_map)
+ }
+ };
+
+ let maybe_inspect_host = flags.inspect.or(flags.inspect_brk);
+ let maybe_inspector_server = maybe_inspect_host.map(|host| {
+ Arc::new(InspectorServer::new(host, version::get_user_agent()))
+ });
+
+ let coverage_dir = flags
+ .coverage_dir
+ .clone()
+ .or_else(|| env::var("DENO_UNSTABLE_COVERAGE_DIR").ok());
+
+ Ok(ProcState(Arc::new(Inner {
+ dir,
+ coverage_dir,
+ flags,
+ file_fetcher,
+ modules: Default::default(),
+ lockfile,
+ maybe_config_file,
+ maybe_import_map,
+ maybe_inspector_server,
+ root_cert_store: Some(root_cert_store.clone()),
+ blob_store,
+ broadcast_channel,
+ shared_array_buffer_store,
+ })))
+ }
+
+ /// Prepares a set of module specifiers for loading in one shot.
+ pub async fn prepare_module_graph(
+ &self,
+ specifiers: Vec<ModuleSpecifier>,
+ lib: TypeLib,
+ root_permissions: Permissions,
+ dynamic_permissions: Permissions,
+ maybe_import_map: Option<ImportMap>,
+ ) -> Result<(), AnyError> {
+ let handler = Arc::new(Mutex::new(FetchHandler::new(
+ self,
+ root_permissions,
+ dynamic_permissions,
+ )?));
+
+ let mut builder =
+ GraphBuilder::new(handler, maybe_import_map, self.lockfile.clone());
+
+ for specifier in specifiers {
+ builder.add(&specifier, false).await?;
+ }
+ builder.analyze_config_file(&self.maybe_config_file).await?;
+
+ let mut graph = builder.get_graph();
+ let debug = self.flags.log_level == Some(log::Level::Debug);
+ let maybe_config_file = self.maybe_config_file.clone();
+ let reload_exclusions = {
+ let modules = self.modules.lock();
+ modules.keys().cloned().collect::<HashSet<_>>()
+ };
+
+ let result_modules = if self.flags.no_check {
+ let result_info = graph.transpile(TranspileOptions {
+ debug,
+ maybe_config_file,
+ reload: self.flags.reload,
+ reload_exclusions,
+ })?;
+ debug!("{}", result_info.stats);
+ if let Some(ignored_options) = result_info.maybe_ignored_options {
+ warn!("{}", ignored_options);
+ }
+ result_info.loadable_modules
+ } else {
+ let result_info = graph.check(CheckOptions {
+ debug,
+ emit: true,
+ lib,
+ maybe_config_file,
+ reload: self.flags.reload,
+ reload_exclusions,
+ })?;
+
+ debug!("{}", result_info.stats);
+ if let Some(ignored_options) = result_info.maybe_ignored_options {
+ eprintln!("{}", ignored_options);
+ }
+ if !result_info.diagnostics.is_empty() {
+ return Err(anyhow!(result_info.diagnostics));
+ }
+ result_info.loadable_modules
+ };
+
+ let mut loadable_modules = self.modules.lock();
+ loadable_modules.extend(result_modules);
+
+ if let Some(ref lockfile) = self.lockfile {
+ let g = lockfile.lock();
+ g.write()?;
+ }
+
+ Ok(())
+ }
+
+ /// This function is called when new module load is initialized by the JsRuntime. Its
+ /// resposibility is to collect all dependencies and if it is required then also perform TS
+ /// typecheck and traspilation.
+ pub async fn prepare_module_load(
+ &self,
+ specifier: ModuleSpecifier,
+ lib: TypeLib,
+ root_permissions: Permissions,
+ dynamic_permissions: Permissions,
+ is_dynamic: bool,
+ maybe_import_map: Option<ImportMap>,
+ ) -> Result<(), AnyError> {
+ let specifier = specifier.clone();
+ let handler = Arc::new(Mutex::new(FetchHandler::new(
+ self,
+ root_permissions,
+ dynamic_permissions,
+ )?));
+ let mut builder =
+ GraphBuilder::new(handler, maybe_import_map, self.lockfile.clone());
+ builder.add(&specifier, is_dynamic).await?;
+ builder.analyze_config_file(&self.maybe_config_file).await?;
+ let mut graph = builder.get_graph();
+ let debug = self.flags.log_level == Some(log::Level::Debug);
+ let maybe_config_file = self.maybe_config_file.clone();
+ let reload_exclusions = {
+ let modules = self.modules.lock();
+ modules.keys().cloned().collect::<HashSet<_>>()
+ };
+
+ let result_modules = if self.flags.no_check {
+ let result_info = graph.transpile(TranspileOptions {
+ debug,
+ maybe_config_file,
+ reload: self.flags.reload,
+ reload_exclusions,
+ })?;
+ debug!("{}", result_info.stats);
+ if let Some(ignored_options) = result_info.maybe_ignored_options {
+ warn!("{}", ignored_options);
+ }
+ result_info.loadable_modules
+ } else {
+ let result_info = graph.check(CheckOptions {
+ debug,
+ emit: true,
+ lib,
+ maybe_config_file,
+ reload: self.flags.reload,
+ reload_exclusions,
+ })?;
+
+ debug!("{}", result_info.stats);
+ if let Some(ignored_options) = result_info.maybe_ignored_options {
+ eprintln!("{}", ignored_options);
+ }
+ if !result_info.diagnostics.is_empty() {
+ return Err(anyhow!(result_info.diagnostics));
+ }
+ result_info.loadable_modules
+ };
+
+ let mut loadable_modules = self.modules.lock();
+ loadable_modules.extend(result_modules);
+
+ if let Some(ref lockfile) = self.lockfile {
+ let g = lockfile.lock();
+ g.write()?;
+ }
+
+ Ok(())
+ }
+
+ pub fn load(
+ &self,
+ specifier: ModuleSpecifier,
+ maybe_referrer: Option<ModuleSpecifier>,
+ ) -> Result<ModuleSource, AnyError> {
+ let modules = self.modules.lock();
+ modules
+ .get(&specifier)
+ .map(|r| match r {
+ Ok(module_source) => Ok(module_source.clone()),
+ Err(err) => {
+ // TODO(@kitsonk) this feels a bit hacky but it works, without
+ // introducing another enum to have to try to deal with.
+ if get_custom_error_class(err) == Some("NotFound") {
+ let message = if let Some(referrer) = &maybe_referrer {
+ format!("{}\n From: {}\n If the source module contains only types, use `import type` and `export type` to import it instead.", err, referrer)
+ } else {
+ format!("{}\n If the source module contains only types, use `import type` and `export type` to import it instead.", err)
+ };
+ warn!("{}: {}", crate::colors::yellow("warning"), message);
+ Ok(ModuleSource {
+ code: "".to_string(),
+ module_url_found: specifier.to_string(),
+ module_url_specified: specifier.to_string(),
+ })
+ } else {
+ // anyhow errors don't support cloning, so we have to manage this
+ // ourselves
+ Err(anyhow!(err.to_string()))
+ }
+ },
+ })
+ .unwrap_or_else(|| {
+ if let Some(referrer) = maybe_referrer {
+ Err(anyhow!(
+ "Module \"{}\" is missing from the graph.\n From: {}",
+ specifier,
+ referrer
+ ))
+ } else {
+ Err(anyhow!(
+ "Module \"{}\" is missing from the graph.",
+ specifier
+ ))
+ }
+ })
+ }
+
+ // TODO(@kitsonk) this should be refactored to get it from the module graph
+ fn get_emit(&self, url: &Url) -> Option<(Vec<u8>, Option<Vec<u8>>)> {
+ match url.scheme() {
+ // we should only be looking for emits for schemes that denote external
+ // modules, which the disk_cache supports
+ "wasm" | "file" | "http" | "https" | "data" | "blob" => (),
+ _ => {
+ return None;
+ }
+ }
+ let emit_path = self
+ .dir
+ .gen_cache
+ .get_cache_filename_with_extension(url, "js")?;
+ let emit_map_path = self
+ .dir
+ .gen_cache
+ .get_cache_filename_with_extension(url, "js.map")?;
+ if let Ok(code) = self.dir.gen_cache.get(&emit_path) {
+ let maybe_map = if let Ok(map) = self.dir.gen_cache.get(&emit_map_path) {
+ Some(map)
+ } else {
+ None
+ };
+ Some((code, maybe_map))
+ } else {
+ None
+ }
+ }
+}
+
+// TODO(@kitsonk) this is only temporary, but should be refactored to somewhere
+// else, like a refactored file_fetcher.
+impl SourceMapGetter for ProcState {
+ fn get_source_map(&self, file_name: &str) -> Option<Vec<u8>> {
+ if let Ok(specifier) = resolve_url(file_name) {
+ if let Some((code, maybe_map)) = self.get_emit(&specifier) {
+ let code = String::from_utf8(code).unwrap();
+ source_map_from_code(code).or(maybe_map)
+ } else if let Ok(source) = self.load(specifier, None) {
+ source_map_from_code(source.code)
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ }
+
+ fn get_source_line(
+ &self,
+ file_name: &str,
+ line_number: usize,
+ ) -> Option<String> {
+ if let Ok(specifier) = resolve_url(file_name) {
+ self.file_fetcher.get_source(&specifier).map(|out| {
+ // Do NOT use .lines(): it skips the terminating empty line.
+ // (due to internally using .split_terminator() instead of .split())
+ let lines: Vec<&str> = out.source.split('\n').collect();
+ assert!(lines.len() > line_number);
+ lines[line_number].to_string()
+ })
+ } else {
+ None
+ }
+ }
+}
+
+fn source_map_from_code(code: String) -> Option<Vec<u8>> {
+ let lines: Vec<&str> = code.split('\n').collect();
+ if let Some(last_line) = lines.last() {
+ if last_line
+ .starts_with("//# sourceMappingURL=data:application/json;base64,")
+ {
+ let input = last_line.trim_start_matches(
+ "//# sourceMappingURL=data:application/json;base64,",
+ );
+ let decoded_map = base64::decode(input)
+ .expect("Unable to decode source map from emitted file.");
+ Some(decoded_map)
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+}