diff --git a/Cargo.lock b/Cargo.lock index 311a7ac885..655b21708e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -284,6 +284,12 @@ dependencies = [ "tokio", ] +[[package]] +name = "async-once-cell" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288f83726785267c6f2ef073a3d83dc3f9b81464e9f99898240cced85fce35a" + [[package]] name = "async-recursion" version = "1.1.1" @@ -1460,9 +1466,9 @@ dependencies = [ [[package]] name = "deno_cache_dir" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e73ed17f285731a23df9779ca1e0e721de866db6776ed919ebd9235e0a107c4c" +checksum = "27429da4d0e601baaa41415a43468d49a586645d13497f12e8a9346f9f6b1347" dependencies = [ "async-trait", "base32", @@ -2219,9 +2225,9 @@ dependencies = [ [[package]] name = "deno_path_util" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420e8211aaba7fde83ccaa9a5dad855c3b940ed988d70c95159acd600a70dc87" +checksum = "c87b8996966ae1b13ee9c20219b1d10fc53905b9570faae6adfa34614fd15224" dependencies = [ "deno_error", "percent-encoding", @@ -2282,6 +2288,7 @@ name = "deno_resolver" version = "0.18.0" dependencies = [ "anyhow", + "async-once-cell", "async-trait", "base32", "boxed_error", @@ -2294,8 +2301,11 @@ dependencies = [ "deno_package_json", "deno_path_util", "deno_semver", + "deno_terminal 0.2.0", + "futures", "log", "node_resolver", + "once_cell", "parking_lot", "sys_traits", "test_server", diff --git a/Cargo.toml b/Cargo.toml index c009bc3c5e..449a9ebc51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,11 +54,11 @@ deno_ast = { version = "=0.44.0", features = ["transpiling"] } deno_core = { version = "0.331.0" } deno_bench_util = { version = "0.181.0", path = "./bench_util" } -deno_config = { version = "=0.45.0", features = ["workspace", "sync"] } +deno_config = { version = "=0.45.0", features = ["workspace"] } deno_lockfile = "=0.24.0" deno_media_type = { version = "=0.2.5", features = ["module_specifier"] } deno_npm = "=0.27.2" -deno_path_util = "=0.3.0" +deno_path_util = "=0.3.1" deno_permissions = { version = "0.46.0", path = "./runtime/permissions" } deno_runtime = { version = "0.195.0", path = "./runtime" } deno_semver = "=0.7.1" @@ -107,6 +107,7 @@ node_resolver = { version = "0.25.0", path = "./resolvers/node" } aes = "=0.8.3" anyhow = "1.0.57" +async-once-cell = "0.5.4" async-trait = "0.1.73" base32 = "=0.5.1" base64 = "0.21.7" @@ -125,7 +126,7 @@ console_static_text = "=0.8.1" dashmap = "5.5.3" data-encoding = "2.3.3" data-url = "=0.3.1" -deno_cache_dir = "=0.16.0" +deno_cache_dir = "=0.17.0" deno_error = "=0.5.5" deno_package_json = { version = "0.4.0", default-features = false } deno_unsync = "0.4.2" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index a9b0b64577..0a4487ce56 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -67,8 +67,8 @@ winres.workspace = true [dependencies] deno_ast = { workspace = true, features = ["bundler", "cjs", "codegen", "proposal", "react", "sourcemap", "transforms", "typescript", "view", "visit"] } -deno_cache_dir.workspace = true -deno_config.workspace = true +deno_cache_dir = { workspace = true, features = ["sync"] } +deno_config = { workspace = true, features = ["sync", "workspace"] } deno_core = { workspace = true, features = ["include_js_files_for_snapshotting"] } deno_doc = { version = "=0.164.0", features = ["rust", "comrak"] } deno_error.workspace = true @@ -79,7 +79,7 @@ deno_lockfile.workspace = true deno_media_type = { workspace = true, features = ["data_url", "decoding", "module_specifier"] } deno_npm.workspace = true deno_npm_cache.workspace = true -deno_package_json.workspace = true +deno_package_json = { workspace = true, features = ["sync"] } deno_path_util.workspace = true deno_resolver = { workspace = true, features = ["sync"] } deno_runtime = { workspace = true, features = ["include_js_files_for_snapshotting"] } diff --git a/cli/args/flags.rs b/cli/args/flags.rs index 0ddd04da2d..7b640a4460 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -34,6 +34,7 @@ use deno_graph::GraphKind; use deno_lib::args::CaData; use deno_lib::args::UnstableConfig; use deno_lib::version::DENO_VERSION_INFO; +use deno_npm::NpmSystemInfo; use deno_path_util::normalize_path; use deno_path_util::url_to_file_path; use deno_runtime::deno_permissions::SysDescriptor; @@ -500,6 +501,52 @@ impl DenoSubcommand { | Self::Lsp ) } + + pub fn npm_system_info(&self) -> NpmSystemInfo { + match self { + DenoSubcommand::Compile(CompileFlags { + target: Some(target), + .. + }) => { + // the values of NpmSystemInfo align with the possible values for the + // `arch` and `platform` fields of Node.js' `process` global: + // https://nodejs.org/api/process.html + match target.as_str() { + "aarch64-apple-darwin" => NpmSystemInfo { + os: "darwin".into(), + cpu: "arm64".into(), + }, + "aarch64-unknown-linux-gnu" => NpmSystemInfo { + os: "linux".into(), + cpu: "arm64".into(), + }, + "x86_64-apple-darwin" => NpmSystemInfo { + os: "darwin".into(), + cpu: "x64".into(), + }, + "x86_64-unknown-linux-gnu" => NpmSystemInfo { + os: "linux".into(), + cpu: "x64".into(), + }, + "x86_64-pc-windows-msvc" => NpmSystemInfo { + os: "win32".into(), + cpu: "x64".into(), + }, + value => { + log::warn!( + concat!( + "Not implemented npm system info for target '{}'. Using current ", + "system default. This may impact architecture specific dependencies." + ), + value, + ); + NpmSystemInfo::default() + } + } + } + _ => NpmSystemInfo::default(), + } + } } impl Default for DenoSubcommand { diff --git a/cli/args/import_map.rs b/cli/args/import_map.rs deleted file mode 100644 index 75a1611120..0000000000 --- a/cli/args/import_map.rs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2018-2025 the Deno authors. MIT license. - -use deno_core::error::AnyError; -use deno_core::serde_json; -use deno_core::url::Url; - -use crate::file_fetcher::CliFileFetcher; -use crate::file_fetcher::TextDecodedFile; - -pub async fn resolve_import_map_value_from_specifier( - specifier: &Url, - file_fetcher: &CliFileFetcher, -) -> Result { - if specifier.scheme() == "data" { - let data_url_text = - deno_media_type::data_url::RawDataUrl::parse(specifier)?.decode()?; - Ok(serde_json::from_str(&data_url_text)?) - } else { - let file = TextDecodedFile::decode( - file_fetcher.fetch_bypass_permissions(specifier).await?, - )?; - Ok(serde_json::from_str(&file.source)?) - } -} diff --git a/cli/args/mod.rs b/cli/args/mod.rs index e4f332a8bc..1f03cdfc19 100644 --- a/cli/args/mod.rs +++ b/cli/args/mod.rs @@ -3,7 +3,6 @@ pub mod deno_json; mod flags; mod flags_net; -mod import_map; mod lockfile; mod package_json; @@ -35,17 +34,9 @@ pub use deno_config::deno_json::TsConfigForEmit; pub use deno_config::deno_json::TsConfigType; pub use deno_config::deno_json::TsTypeLib; pub use deno_config::glob::FilePatterns; -use deno_config::workspace::CreateResolverOptions; -use deno_config::workspace::FolderConfigs; -use deno_config::workspace::PackageJsonDepResolution; -use deno_config::workspace::VendorEnablement; use deno_config::workspace::Workspace; use deno_config::workspace::WorkspaceDirectory; -use deno_config::workspace::WorkspaceDirectoryEmptyOptions; -use deno_config::workspace::WorkspaceDiscoverOptions; -use deno_config::workspace::WorkspaceDiscoverStart; use deno_config::workspace::WorkspaceLintConfig; -use deno_config::workspace::WorkspaceResolver; use deno_core::anyhow::bail; use deno_core::anyhow::Context; use deno_core::error::AnyError; @@ -57,16 +48,11 @@ pub use deno_json::check_warn_tsconfig; use deno_lib::args::has_flag_env_var; use deno_lib::args::npm_pkg_req_ref_to_binary_command; use deno_lib::args::CaData; -use deno_lib::args::NpmProcessStateKind; use deno_lib::args::NPM_PROCESS_STATE; use deno_lib::version::DENO_VERSION_INFO; use deno_lib::worker::StorageKeyResolver; use deno_lint::linter::LintConfig as DenoLintConfig; -use deno_npm::npm_rc::NpmRc; -use deno_npm::npm_rc::ResolvedNpmRc; -use deno_npm::resolution::ValidSerializedNpmResolutionSnapshot; use deno_npm::NpmSystemInfo; -use deno_path_util::normalize_path; use deno_runtime::deno_permissions::PermissionsOptions; use deno_runtime::inspector_server::InspectorServer; use deno_semver::npm::NpmPackageReqReference; @@ -75,46 +61,16 @@ use deno_telemetry::OtelConfig; use deno_terminal::colors; use dotenvy::from_filename; pub use flags::*; -use import_map::resolve_import_map_value_from_specifier; pub use lockfile::AtomicWriteFileWithRetriesError; pub use lockfile::CliLockfile; pub use lockfile::CliLockfileReadFromPathOptions; use once_cell::sync::Lazy; pub use package_json::NpmInstallDepsProvider; pub use package_json::PackageJsonDepValueParseWithLocationError; -use sys_traits::EnvHomeDir; +use sys_traits::FsRead; use thiserror::Error; -use crate::cache::DenoDirProvider; -use crate::file_fetcher::CliFileFetcher; use crate::sys::CliSys; -use crate::util::fs::canonicalize_path_maybe_not_exists; - -pub fn npm_registry_url() -> &'static Url { - static NPM_REGISTRY_DEFAULT_URL: Lazy = Lazy::new(|| { - let env_var_name = "NPM_CONFIG_REGISTRY"; - if let Ok(registry_url) = std::env::var(env_var_name) { - // ensure there is a trailing slash for the directory - let registry_url = format!("{}/", registry_url.trim_end_matches('/')); - match Url::parse(®istry_url) { - Ok(url) => { - return url; - } - Err(err) => { - log::debug!( - "Invalid {} environment variable: {:#}", - env_var_name, - err, - ); - } - } - } - - Url::parse("https://registry.npmjs.org").unwrap() - }); - - &NPM_REGISTRY_DEFAULT_URL -} pub static DENO_DISABLE_PEDANTIC_NODE_WARNINGS: Lazy = Lazy::new(|| { std::env::var("DENO_DISABLE_PEDANTIC_NODE_WARNINGS") @@ -214,6 +170,53 @@ pub fn ts_config_to_transpile_and_emit_options( )) } +#[derive(Debug, Clone)] +pub struct ExternalImportMap { + pub path: PathBuf, + pub value: serde_json::Value, +} + +#[derive(Debug)] +pub struct WorkspaceExternalImportMapLoader { + sys: CliSys, + workspace: Arc, + maybe_external_import_map: + once_cell::sync::OnceCell>, +} + +impl WorkspaceExternalImportMapLoader { + pub fn new(sys: CliSys, workspace: Arc) -> Self { + Self { + sys, + workspace, + maybe_external_import_map: Default::default(), + } + } + + pub fn get_or_load(&self) -> Result, AnyError> { + self + .maybe_external_import_map + .get_or_try_init(|| { + let Some(deno_json) = self.workspace.root_deno_json() else { + return Ok(None); + }; + if deno_json.is_an_import_map() { + return Ok(None); + } + let Some(path) = deno_json.to_import_map_path()? else { + return Ok(None); + }; + let contents = + self.sys.fs_read_to_string(&path).with_context(|| { + format!("Unable to read import map at '{}'", path.display()) + })?; + let value = serde_json::from_str(&contents)?; + Ok(Some(ExternalImportMap { path, value })) + }) + .map(|v| v.as_ref()) + } +} + pub struct WorkspaceBenchOptions { pub filter: Option, pub json: bool, @@ -480,162 +483,26 @@ fn resolve_lint_rules_options( } } -pub fn discover_npmrc_from_workspace( - workspace: &Workspace, -) -> Result<(Arc, Option), AnyError> { - let root_folder = workspace.root_folder_configs(); - discover_npmrc( - root_folder.pkg_json.as_ref().map(|p| p.path.clone()), - root_folder.deno_json.as_ref().and_then(|cf| { - if cf.specifier.scheme() == "file" { - Some(cf.specifier.to_file_path().unwrap()) - } else { - None - } - }), - ) -} - -/// Discover `.npmrc` file - currently we only support it next to `package.json` -/// or next to `deno.json`. -/// -/// In the future we will need to support it in user directory or global directory -/// as per https://docs.npmjs.com/cli/v10/configuring-npm/npmrc#files. -fn discover_npmrc( - maybe_package_json_path: Option, - maybe_deno_json_path: Option, -) -> Result<(Arc, Option), AnyError> { - const NPMRC_NAME: &str = ".npmrc"; - - fn get_env_var(var_name: &str) -> Option { - std::env::var(var_name).ok() - } - - #[derive(Debug, Error)] - #[error("Error loading .npmrc at {}.", path.display())] - struct NpmRcLoadError { - path: PathBuf, - #[source] - source: std::io::Error, - } - - fn try_to_read_npmrc( - dir: &Path, - ) -> Result, NpmRcLoadError> { - let path = dir.join(NPMRC_NAME); - let maybe_source = match std::fs::read_to_string(&path) { - Ok(source) => Some(source), - Err(err) if err.kind() == std::io::ErrorKind::NotFound => None, - Err(err) => return Err(NpmRcLoadError { path, source: err }), - }; - - Ok(maybe_source.map(|source| (source, path))) - } - - fn try_to_parse_npmrc( - source: String, - path: &Path, - ) -> Result, AnyError> { - let npmrc = NpmRc::parse(&source, &get_env_var).with_context(|| { - format!("Failed to parse .npmrc at {}", path.display()) - })?; - let resolved = npmrc - .as_resolved(npm_registry_url()) - .context("Failed to resolve .npmrc options")?; - log::debug!(".npmrc found at: '{}'", path.display()); - Ok(Arc::new(resolved)) - } - - // 1. Try `.npmrc` next to `package.json` - if let Some(package_json_path) = maybe_package_json_path { - if let Some(package_json_dir) = package_json_path.parent() { - if let Some((source, path)) = try_to_read_npmrc(package_json_dir)? { - return try_to_parse_npmrc(source, &path).map(|r| (r, Some(path))); - } - } - } - - // 2. Try `.npmrc` next to `deno.json(c)` - if let Some(deno_json_path) = maybe_deno_json_path { - if let Some(deno_json_dir) = deno_json_path.parent() { - if let Some((source, path)) = try_to_read_npmrc(deno_json_dir)? { - return try_to_parse_npmrc(source, &path).map(|r| (r, Some(path))); - } - } - } - - // TODO(bartlomieju): update to read both files - one in the project root and one and - // home dir and then merge them. - // 3. Try `.npmrc` in the user's home directory - if let Some(home_dir) = crate::sys::CliSys::default().env_home_dir() { - match try_to_read_npmrc(&home_dir) { - Ok(Some((source, path))) => { - return try_to_parse_npmrc(source, &path).map(|r| (r, Some(path))); - } - Ok(None) => {} - Err(err) if err.source.kind() == std::io::ErrorKind::PermissionDenied => { - log::debug!( - "Skipping .npmrc in home directory due to permission denied error. {:#}", - err - ); - } - Err(err) => { - return Err(err.into()); - } - } - } - - log::debug!("No .npmrc file found"); - Ok((create_default_npmrc(), None)) -} - -pub fn create_default_npmrc() -> Arc { - Arc::new(ResolvedNpmRc { - default_config: deno_npm::npm_rc::RegistryConfigWithUrl { - registry_url: npm_registry_url().clone(), - config: Default::default(), - }, - scopes: Default::default(), - registry_configs: Default::default(), - }) -} - -/// Overrides for the options below that when set will -/// use these values over the values derived from the -/// CLI flags or config file. -#[derive(Default, Clone)] -struct CliOptionOverrides { - import_map_specifier: Option>, -} - /// Holds the resolved options of many sources used by subcommands /// and provides some helper function for creating common objects. +#[derive(Debug)] pub struct CliOptions { // the source of the options is a detail the rest of the // application need not concern itself with, so keep these private flags: Arc, initial_cwd: PathBuf, main_module_cell: std::sync::OnceLock>, - maybe_node_modules_folder: Option, - npmrc: Arc, maybe_lockfile: Option>, - maybe_external_import_map: Option<(PathBuf, serde_json::Value)>, - overrides: CliOptionOverrides, pub start_dir: Arc, - pub deno_dir_provider: Arc, } impl CliOptions { #[allow(clippy::too_many_arguments)] pub fn new( - sys: &CliSys, flags: Arc, initial_cwd: PathBuf, maybe_lockfile: Option>, - npmrc: Arc, start_dir: Arc, - force_global_cache: bool, - maybe_external_import_map: Option<(PathBuf, serde_json::Value)>, ) -> Result { if let Some(insecure_allowlist) = flags.unsafely_ignore_certificate_errors.as_ref() @@ -652,154 +519,38 @@ impl CliOptions { } } - let maybe_lockfile = maybe_lockfile.filter(|_| !force_global_cache); - let deno_dir_provider = Arc::new(DenoDirProvider::new( - sys.clone(), - flags.internal.cache_path.clone(), - )); - let maybe_node_modules_folder = resolve_node_modules_folder( - &initial_cwd, - &flags, - &start_dir.workspace, - &deno_dir_provider, - ) - .with_context(|| "Resolving node_modules folder.")?; - load_env_variables_from_env_file(flags.env_file.as_ref()); Ok(Self { flags, initial_cwd, maybe_lockfile, - npmrc, - maybe_node_modules_folder, - overrides: Default::default(), main_module_cell: std::sync::OnceLock::new(), - maybe_external_import_map, start_dir, - deno_dir_provider, }) } - pub fn from_flags(sys: &CliSys, flags: Arc) -> Result { - let initial_cwd = - std::env::current_dir().with_context(|| "Failed getting cwd.")?; - let maybe_vendor_override = flags.vendor.map(|v| match v { - true => VendorEnablement::Enable { cwd: &initial_cwd }, - false => VendorEnablement::Disable, - }); - let resolve_workspace_discover_options = || { - let additional_config_file_names: &'static [&'static str] = - if matches!(flags.subcommand, DenoSubcommand::Publish(..)) { - &["jsr.json", "jsr.jsonc"] - } else { - &[] - }; - let discover_pkg_json = flags.config_flag != ConfigFlag::Disabled - && !flags.no_npm - && !has_flag_env_var("DENO_NO_PACKAGE_JSON"); - if !discover_pkg_json { - log::debug!("package.json auto-discovery is disabled"); - } - WorkspaceDiscoverOptions { - deno_json_cache: None, - pkg_json_cache: Some(&node_resolver::PackageJsonThreadLocalCache), - workspace_cache: None, - additional_config_file_names, - discover_pkg_json, - maybe_vendor_override, - } - }; - let resolve_empty_options = || WorkspaceDirectoryEmptyOptions { - root_dir: Arc::new( - ModuleSpecifier::from_directory_path(&initial_cwd).unwrap(), - ), - use_vendor_dir: maybe_vendor_override - .unwrap_or(VendorEnablement::Disable), - }; - - let start_dir = match &flags.config_flag { - ConfigFlag::Discover => { - if let Some(start_paths) = flags.config_path_args(&initial_cwd) { - WorkspaceDirectory::discover( - sys, - WorkspaceDiscoverStart::Paths(&start_paths), - &resolve_workspace_discover_options(), - )? - } else { - WorkspaceDirectory::empty(resolve_empty_options()) - } - } - ConfigFlag::Path(path) => { - let config_path = normalize_path(initial_cwd.join(path)); - WorkspaceDirectory::discover( - sys, - WorkspaceDiscoverStart::ConfigFile(&config_path), - &resolve_workspace_discover_options(), - )? - } - ConfigFlag::Disabled => { - WorkspaceDirectory::empty(resolve_empty_options()) - } - }; - + pub fn from_flags( + sys: &CliSys, + flags: Arc, + initial_cwd: PathBuf, + maybe_external_import_map: Option<&ExternalImportMap>, + start_dir: Arc, + ) -> Result { for diagnostic in start_dir.workspace.diagnostics() { log::warn!("{} {}", colors::yellow("Warning"), diagnostic); } - let (npmrc, _) = discover_npmrc_from_workspace(&start_dir.workspace)?; - - fn load_external_import_map( - deno_json: &ConfigFile, - ) -> Result, AnyError> { - if !deno_json.is_an_import_map() { - if let Some(path) = deno_json.to_import_map_path()? { - let contents = std::fs::read_to_string(&path).with_context(|| { - format!("Unable to read import map at '{}'", path.display()) - })?; - let map = serde_json::from_str(&contents)?; - return Ok(Some((path, map))); - } - } - Ok(None) - } - - let external_import_map = - if let Some(deno_json) = start_dir.workspace.root_deno_json() { - load_external_import_map(deno_json)? - } else { - None - }; - let maybe_lock_file = CliLockfile::discover( sys, &flags, &start_dir.workspace, - external_import_map.as_ref().map(|(_, v)| v), + maybe_external_import_map.as_ref().map(|v| &v.value), )?; log::debug!("Finished config loading."); - Self::new( - sys, - flags, - initial_cwd, - maybe_lock_file.map(Arc::new), - npmrc, - Arc::new(start_dir), - false, - external_import_map, - ) - } - - /// This method is purposefully verbose to disourage its use. Do not use it - /// except in the factory structs. Instead, prefer specific methods on `CliOptions` - /// that can take all sources of information into account (ex. config files or env vars). - pub fn into_self_and_flags( - self: Arc, - ) -> (Arc, Arc) { - let flags = self.flags.clone(); - (self, flags) + Self::new(flags, initial_cwd, maybe_lock_file.map(Arc::new), start_dir) } #[inline(always)] @@ -842,49 +593,7 @@ impl CliOptions { } pub fn npm_system_info(&self) -> NpmSystemInfo { - match self.sub_command() { - DenoSubcommand::Compile(CompileFlags { - target: Some(target), - .. - }) => { - // the values of NpmSystemInfo align with the possible values for the - // `arch` and `platform` fields of Node.js' `process` global: - // https://nodejs.org/api/process.html - match target.as_str() { - "aarch64-apple-darwin" => NpmSystemInfo { - os: "darwin".into(), - cpu: "arm64".into(), - }, - "aarch64-unknown-linux-gnu" => NpmSystemInfo { - os: "linux".into(), - cpu: "arm64".into(), - }, - "x86_64-apple-darwin" => NpmSystemInfo { - os: "darwin".into(), - cpu: "x64".into(), - }, - "x86_64-unknown-linux-gnu" => NpmSystemInfo { - os: "linux".into(), - cpu: "x64".into(), - }, - "x86_64-pc-windows-msvc" => NpmSystemInfo { - os: "win32".into(), - cpu: "x64".into(), - }, - value => { - log::warn!( - concat!( - "Not implemented npm system info for target '{}'. Using current ", - "system default. This may impact architecture specific dependencies." - ), - value, - ); - NpmSystemInfo::default() - } - } - } - _ => NpmSystemInfo::default(), - } + self.sub_command().npm_system_info() } /// Resolve the specifier for a specified import map. @@ -893,72 +602,12 @@ impl CliOptions { /// happens to be an import map. pub fn resolve_specified_import_map_specifier( &self, - ) -> Result, AnyError> { - match self.overrides.import_map_specifier.clone() { - Some(maybe_url) => Ok(maybe_url), - None => resolve_import_map_specifier( - self.flags.import_map_path.as_deref(), - self.workspace().root_deno_json().map(|c| c.as_ref()), - &self.initial_cwd, - ), - } - } - - pub async fn create_workspace_resolver( - &self, - file_fetcher: &CliFileFetcher, - pkg_json_dep_resolution: PackageJsonDepResolution, - ) -> Result { - let overrode_no_import_map: bool = self - .overrides - .import_map_specifier - .as_ref() - .map(|s| s.is_none()) - == Some(true); - let cli_arg_specified_import_map = if overrode_no_import_map { - // use a fake empty import map - Some(deno_config::workspace::SpecifiedImportMap { - base_url: self.workspace().root_dir().join("import_map.json").unwrap(), - value: serde_json::Value::Object(Default::default()), - }) - } else { - let maybe_import_map_specifier = - self.resolve_specified_import_map_specifier()?; - match maybe_import_map_specifier { - Some(specifier) => { - let value = - resolve_import_map_value_from_specifier(&specifier, file_fetcher) - .await - .with_context(|| { - format!("Unable to load '{}' import map", specifier) - })?; - Some(deno_config::workspace::SpecifiedImportMap { - base_url: specifier, - value, - }) - } - None => { - if let Some((path, import_map)) = - self.maybe_external_import_map.as_ref() - { - let path_url = deno_path_util::url_from_file_path(path)?; - Some(deno_config::workspace::SpecifiedImportMap { - base_url: path_url, - value: import_map.clone(), - }) - } else { - None - } - } - } - }; - Ok(self.workspace().create_resolver( - &CliSys::default(), - CreateResolverOptions { - pkg_json_dep_resolution, - specified_import_map: cli_arg_specified_import_map, - }, - )?) + ) -> Result, ImportMapSpecifierResolveError> { + resolve_import_map_specifier( + self.flags.import_map_path.as_deref(), + self.workspace().root_deno_json().map(|c| c.as_ref()), + &self.initial_cwd, + ) } pub fn node_ipc_fd(&self) -> Option { @@ -1068,19 +717,6 @@ impl CliOptions { } } - pub fn resolve_npm_resolution_snapshot( - &self, - ) -> Result, AnyError> { - if let Some(NpmProcessStateKind::Snapshot(snapshot)) = - NPM_PROCESS_STATE.as_ref().map(|s| &s.kind) - { - // TODO(bartlomieju): remove this clone - Ok(Some(snapshot.clone().into_valid()?)) - } else { - Ok(None) - } - } - pub fn resolve_storage_key_resolver(&self) -> StorageKeyResolver { if let Some(location) = &self.flags.location { StorageKeyResolver::from_flag(location) @@ -1099,14 +735,6 @@ impl CliOptions { NPM_PROCESS_STATE.is_some() } - pub fn has_node_modules_dir(&self) -> bool { - self.maybe_node_modules_folder.is_some() - } - - pub fn node_modules_dir_path(&self) -> Option<&PathBuf> { - self.maybe_node_modules_folder.as_ref() - } - pub fn node_modules_dir( &self, ) -> Result< @@ -1170,10 +798,6 @@ impl CliOptions { }) } - pub fn npmrc(&self) -> &Arc { - &self.npmrc - } - pub fn resolve_fmt_options_for_members( &self, fmt_flags: &FmtFlags, @@ -1571,41 +1195,6 @@ impl CliOptions { self.workspace().package_jsons().next().is_some() || self.is_node_main() } - fn byonm_enabled(&self) -> bool { - // check if enabled via unstable - self.node_modules_dir().ok().flatten() == Some(NodeModulesDirMode::Manual) - || NPM_PROCESS_STATE - .as_ref() - .map(|s| matches!(s.kind, NpmProcessStateKind::Byonm)) - .unwrap_or(false) - } - - pub fn use_byonm(&self) -> bool { - if matches!( - self.sub_command(), - DenoSubcommand::Install(_) - | DenoSubcommand::Add(_) - | DenoSubcommand::Remove(_) - | DenoSubcommand::Init(_) - | DenoSubcommand::Outdated(_) - ) { - // For `deno install/add/remove/init` we want to force the managed resolver so it can set up `node_modules/` directory. - return false; - } - if self.node_modules_dir().ok().flatten().is_none() - && self.maybe_node_modules_folder.is_some() - && self - .workspace() - .config_folders() - .values() - .any(|f| f.pkg_json.is_some()) - { - return true; - } - - self.byonm_enabled() - } - pub fn unstable_sloppy_imports(&self) -> bool { self.flags.unstable_config.sloppy_imports || self.workspace().has_unstable("sloppy-imports") @@ -1730,63 +1319,6 @@ impl CliOptions { } } -/// Resolves the path to use for a local node_modules folder. -fn resolve_node_modules_folder( - cwd: &Path, - flags: &Flags, - workspace: &Workspace, - deno_dir_provider: &Arc, -) -> Result, AnyError> { - fn resolve_from_root(root_folder: &FolderConfigs, cwd: &Path) -> PathBuf { - root_folder - .deno_json - .as_ref() - .map(|c| Cow::Owned(c.dir_path())) - .or_else(|| { - root_folder - .pkg_json - .as_ref() - .map(|c| Cow::Borrowed(c.dir_path())) - }) - .unwrap_or(Cow::Borrowed(cwd)) - .join("node_modules") - } - - let root_folder = workspace.root_folder_configs(); - let use_node_modules_dir = if let Some(mode) = flags.node_modules_dir { - Some(mode.uses_node_modules_dir()) - } else { - workspace - .node_modules_dir()? - .map(|m| m.uses_node_modules_dir()) - .or(flags.vendor) - .or_else(|| root_folder.deno_json.as_ref().and_then(|c| c.json.vendor)) - }; - let path = if use_node_modules_dir == Some(false) { - return Ok(None); - } else if let Some(state) = &*NPM_PROCESS_STATE { - return Ok(state.local_node_modules_path.as_ref().map(PathBuf::from)); - } else if root_folder.pkg_json.is_some() { - let node_modules_dir = resolve_from_root(root_folder, cwd); - if let Ok(deno_dir) = deno_dir_provider.get_or_create() { - // `deno_dir.root` can be symlink in macOS - if let Ok(root) = canonicalize_path_maybe_not_exists(&deno_dir.root) { - if node_modules_dir.starts_with(root) { - // if the package.json is in deno_dir, then do not use node_modules - // next to it as local node_modules dir - return Ok(None); - } - } - } - node_modules_dir - } else if use_node_modules_dir.is_none() { - return Ok(None); - } else { - resolve_from_root(root_folder, cwd) - }; - Ok(Some(canonicalize_path_maybe_not_exists(&path)?)) -} - fn try_resolve_node_binary_main_entrypoint( specifier: &str, initial_cwd: &Path, @@ -1817,22 +1349,31 @@ fn try_resolve_node_binary_main_entrypoint( } } +#[derive(Debug, Error)] +#[error("Bad URL for import map.")] +pub struct ImportMapSpecifierResolveError { + #[source] + source: deno_path_util::ResolveUrlOrPathError, +} + fn resolve_import_map_specifier( maybe_import_map_path: Option<&str>, maybe_config_file: Option<&ConfigFile>, current_dir: &Path, -) -> Result, AnyError> { +) -> Result, ImportMapSpecifierResolveError> { if let Some(import_map_path) = maybe_import_map_path { if let Some(config_file) = &maybe_config_file { if config_file.json.import_map.is_some() { - log::warn!("{} the configuration file \"{}\" contains an entry for \"importMap\" that is being ignored.", colors::yellow("Warning"), config_file.specifier); + log::warn!( + "{} the configuration file \"{}\" contains an entry for \"importMap\" that is being ignored.", + colors::yellow("Warning"), + config_file.specifier, + ); } } let specifier = - deno_core::resolve_url_or_path(import_map_path, current_dir) - .with_context(|| { - format!("Bad URL (\"{import_map_path}\") for import map.") - })?; + deno_path_util::resolve_url_or_path(import_map_path, current_dir) + .map_err(|source| ImportMapSpecifierResolveError { source })?; Ok(Some(specifier)) } else { Ok(None) @@ -1873,9 +1414,9 @@ fn load_env_variables_from_env_file(filename: Option<&Vec>) { Ok(_) => (), Err(error) => { match error { - dotenvy::Error::LineParse(line, index)=> log::info!("{} Parsing failed within the specified environment file: {} at index: {} of the value: {}",colors::yellow("Warning"), env_file_name, index, line), - dotenvy::Error::Io(_)=> log::info!("{} The `--env-file` flag was used, but the environment file specified '{}' was not found.",colors::yellow("Warning"),env_file_name), - dotenvy::Error::EnvVar(_)=> log::info!("{} One or more of the environment variables isn't present or not unicode within the specified environment file: {}",colors::yellow("Warning"),env_file_name), + dotenvy::Error::LineParse(line, index)=> log::info!("{} Parsing failed within the specified environment file: {} at index: {} of the value: {}", colors::yellow("Warning"), env_file_name, index, line), + dotenvy::Error::Io(_)=> log::info!("{} The `--env-file` flag was used, but the environment file specified '{}' was not found.", colors::yellow("Warning"), env_file_name), + dotenvy::Error::EnvVar(_)=> log::info!("{} One or more of the environment variables isn't present or not unicode within the specified environment file: {}", colors::yellow("Warning"), env_file_name), _ => log::info!("{} Unknown failure occurred with the specified environment file: {}", colors::yellow("Warning"), env_file_name), } } @@ -1916,8 +1457,7 @@ mod test { "importMap": "import_map.json" }"#; let cwd = &std::env::current_dir().unwrap(); - let config_specifier = - ModuleSpecifier::parse("file:///deno/deno.jsonc").unwrap(); + let config_specifier = Url::parse("file:///deno/deno.jsonc").unwrap(); let config_file = ConfigFile::new(config_text, config_specifier).unwrap(); let actual = resolve_import_map_specifier( Some("import-map.json"), @@ -1926,7 +1466,7 @@ mod test { ); let import_map_path = cwd.join("import-map.json"); let expected_specifier = - ModuleSpecifier::from_file_path(import_map_path).unwrap(); + deno_path_util::url_from_file_path(&import_map_path).unwrap(); assert!(actual.is_ok()); let actual = actual.unwrap(); assert_eq!(actual, Some(expected_specifier)); @@ -1935,8 +1475,7 @@ mod test { #[test] fn resolve_import_map_none() { let config_text = r#"{}"#; - let config_specifier = - ModuleSpecifier::parse("file:///deno/deno.jsonc").unwrap(); + let config_specifier = Url::parse("file:///deno/deno.jsonc").unwrap(); let config_file = ConfigFile::new(config_text, config_specifier).unwrap(); let actual = resolve_import_map_specifier( None, diff --git a/cli/cache/deno_dir.rs b/cli/cache/deno_dir.rs index 5be08446cf..a5dd96ea7d 100644 --- a/cli/cache/deno_dir.rs +++ b/cli/cache/deno_dir.rs @@ -2,46 +2,39 @@ use std::env; use std::path::PathBuf; +use std::sync::Arc; use deno_cache_dir::DenoDirResolutionError; use super::DiskCache; +use crate::factory::CliDenoDirPathProvider; use crate::sys::CliSys; /// Lazily creates the deno dir which might be useful in scenarios /// where functionality wants to continue if the DENO_DIR can't be created. pub struct DenoDirProvider { + deno_dir_path_provider: Arc, sys: CliSys, - maybe_custom_root: Option, - deno_dir: std::sync::OnceLock>, + deno_dir: once_cell::sync::OnceCell, } impl DenoDirProvider { - pub fn new(sys: CliSys, maybe_custom_root: Option) -> Self { + pub fn new( + sys: CliSys, + deno_dir_path_provider: Arc, + ) -> Self { Self { sys, - maybe_custom_root, + deno_dir_path_provider, deno_dir: Default::default(), } } pub fn get_or_create(&self) -> Result<&DenoDir, DenoDirResolutionError> { - self - .deno_dir - .get_or_init(|| { - DenoDir::new(self.sys.clone(), self.maybe_custom_root.clone()) - }) - .as_ref() - .map_err(|err| match err { - DenoDirResolutionError::NoCacheOrHomeDir => { - DenoDirResolutionError::NoCacheOrHomeDir - } - DenoDirResolutionError::FailedCwd { source } => { - DenoDirResolutionError::FailedCwd { - source: std::io::Error::new(source.kind(), source.to_string()), - } - } - }) + self.deno_dir.get_or_try_init(|| { + let path = self.deno_dir_path_provider.get_or_create()?; + Ok(DenoDir::new(self.sys.clone(), path.clone())) + }) } } @@ -56,23 +49,14 @@ pub struct DenoDir { } impl DenoDir { - pub fn new( - sys: CliSys, - maybe_custom_root: Option, - ) -> Result { - let root = deno_cache_dir::resolve_deno_dir( - &sys_traits::impls::RealSys, - maybe_custom_root, - )?; + pub fn new(sys: CliSys, root: PathBuf) -> Self { assert!(root.is_absolute()); let gen_path = root.join("gen"); - let deno_dir = Self { + Self { root, - gen_cache: DiskCache::new(sys, &gen_path), - }; - - Ok(deno_dir) + gen_cache: DiskCache::new(sys, gen_path), + } } /// The root directory of the DENO_DIR for display purposes only. diff --git a/cli/cache/disk_cache.rs b/cli/cache/disk_cache.rs index cb28c02922..a085ef5235 100644 --- a/cli/cache/disk_cache.rs +++ b/cli/cache/disk_cache.rs @@ -1,7 +1,6 @@ // Copyright 2018-2025 the Deno authors. MIT license. use std::ffi::OsStr; -use std::fs; use std::path::Component; use std::path::Path; use std::path::PathBuf; @@ -13,6 +12,7 @@ use deno_cache_dir::CACHE_PERM; use deno_core::url::Host; use deno_core::url::Url; use deno_path_util::fs::atomic_write_file_with_retries; +use sys_traits::FsRead; use crate::sys::CliSys; @@ -24,12 +24,9 @@ pub struct DiskCache { impl DiskCache { /// `location` must be an absolute path. - pub fn new(sys: CliSys, location: &Path) -> Self { + pub fn new(sys: CliSys, location: PathBuf) -> Self { assert!(location.is_absolute()); - Self { - sys, - location: location.to_owned(), - } + Self { sys, location } } fn get_cache_filename(&self, url: &Url) -> Option { @@ -119,7 +116,7 @@ impl DiskCache { pub fn get(&self, filename: &Path) -> std::io::Result> { let path = self.location.join(filename); - fs::read(path) + Ok(self.sys.fs_read(path)?.into_owned()) } pub fn set(&self, filename: &Path, data: &[u8]) -> std::io::Result<()> { @@ -141,7 +138,7 @@ mod tests { fn test_set_get_cache_file() { let temp_dir = TempDir::new(); let sub_dir = temp_dir.path().join("sub_dir"); - let cache = DiskCache::new(RealSys, &sub_dir.to_path_buf()); + let cache = DiskCache::new(RealSys, sub_dir.to_path_buf()); let path = PathBuf::from("foo/bar.txt"); cache.set(&path, b"hello").unwrap(); assert_eq!(cache.get(&path).unwrap(), b"hello"); @@ -155,7 +152,7 @@ mod tests { PathBuf::from("/deno_dir/") }; - let cache = DiskCache::new(RealSys, &cache_location); + let cache = DiskCache::new(RealSys, cache_location); let mut test_cases = vec![ ( @@ -211,7 +208,7 @@ mod tests { } else { "/foo" }; - let cache = DiskCache::new(RealSys, &PathBuf::from(p)); + let cache = DiskCache::new(RealSys, PathBuf::from(p)); let mut test_cases = vec![ ( @@ -259,7 +256,7 @@ mod tests { PathBuf::from("/deno_dir/") }; - let cache = DiskCache::new(RealSys, &cache_location); + let cache = DiskCache::new(RealSys, cache_location); let mut test_cases = vec!["unknown://localhost/test.ts"]; diff --git a/cli/cache/emit.rs b/cli/cache/emit.rs index 116454dd9a..8f42dfb312 100644 --- a/cli/cache/emit.rs +++ b/cli/cache/emit.rs @@ -167,7 +167,7 @@ mod test { pub fn emit_cache_general_use() { let temp_dir = TempDir::new(); let disk_cache = - DiskCache::new(CliSys::default(), temp_dir.path().as_path()); + DiskCache::new(CliSys::default(), temp_dir.path().to_path_buf()); let cache = EmitCache { disk_cache: disk_cache.clone(), file_serializer: EmitFileSerializer { diff --git a/cli/cache/mod.rs b/cli/cache/mod.rs index 695d21f3d1..80e9ab40ae 100644 --- a/cli/cache/mod.rs +++ b/cli/cache/mod.rs @@ -57,7 +57,6 @@ pub use parsed_source::LazyGraphSourceParser; pub use parsed_source::ParsedSourceCache; pub type GlobalHttpCache = deno_cache_dir::GlobalHttpCache; -pub type LocalHttpCache = deno_cache_dir::LocalHttpCache; pub type LocalLspHttpCache = deno_cache_dir::LocalLspHttpCache; pub use deno_cache_dir::HttpCache; use deno_error::JsErrorBox; @@ -120,11 +119,7 @@ impl FetchCacher { } else if specifier.scheme() == "file" { specifier.to_file_path().ok() } else { - #[allow(deprecated)] - self - .global_http_cache - .get_global_cache_filepath(specifier) - .ok() + self.global_http_cache.local_path_for_url(specifier).ok() } } } diff --git a/cli/factory.rs b/cli/factory.rs index 91c5d07b75..3dafc26b21 100644 --- a/cli/factory.rs +++ b/cli/factory.rs @@ -1,37 +1,44 @@ // Copyright 2018-2025 the Deno authors. MIT license. +use std::borrow::Cow; use std::future::Future; +use std::path::Path; use std::path::PathBuf; use std::sync::Arc; use deno_cache_dir::npm::NpmCacheDir; -use deno_config::workspace::PackageJsonDepResolution; +use deno_config::workspace::WorkspaceDirectory; use deno_config::workspace::WorkspaceResolver; +use deno_core::anyhow::Context; use deno_core::error::AnyError; use deno_core::futures::FutureExt; +use deno_core::serde_json; +use deno_core::url::Url; use deno_core::FeatureChecker; use deno_error::JsErrorBox; use deno_lib::args::get_root_cert_store; +use deno_lib::args::resolve_npm_resolution_snapshot; use deno_lib::args::CaData; +use deno_lib::args::NpmProcessStateKind; +use deno_lib::args::NPM_PROCESS_STATE; use deno_lib::loader::NpmModuleLoader; use deno_lib::npm::create_npm_process_state_provider; use deno_lib::npm::NpmRegistryReadPermissionChecker; use deno_lib::npm::NpmRegistryReadPermissionCheckerMode; use deno_lib::worker::LibMainWorkerFactory; use deno_lib::worker::LibMainWorkerOptions; +use deno_npm::npm_rc::ResolvedNpmRc; use deno_npm_cache::NpmCacheSetting; use deno_resolver::cjs::IsCjsResolutionMode; -use deno_resolver::npm::managed::ManagedInNpmPkgCheckerCreateOptions; +use deno_resolver::factory::ConfigDiscoveryOption; +use deno_resolver::factory::DenoDirPathProviderOptions; +use deno_resolver::factory::NpmProcessStateOptions; +use deno_resolver::factory::ResolverFactoryOptions; +use deno_resolver::factory::SpecifiedImportMapProvider; use deno_resolver::npm::managed::NpmResolutionCell; -use deno_resolver::npm::CreateInNpmPkgCheckerOptions; use deno_resolver::npm::DenoInNpmPackageChecker; -use deno_resolver::npm::NpmReqResolverOptions; -use deno_resolver::sloppy_imports::SloppyImportsCachedFs; -use deno_resolver::DenoResolverOptions; -use deno_resolver::NodeAndNpmReqResolver; use deno_runtime::deno_fs; use deno_runtime::deno_fs::RealFs; -use deno_runtime::deno_node::RealIsBuiltInNodeModuleChecker; use deno_runtime::deno_permissions::Permissions; use deno_runtime::deno_permissions::PermissionsContainer; use deno_runtime::deno_tls::rustls::RootCertStore; @@ -39,16 +46,18 @@ use deno_runtime::deno_tls::RootCertStoreProvider; use deno_runtime::deno_web::BlobStore; use deno_runtime::inspector_server::InspectorServer; use deno_runtime::permissions::RuntimePermissionDescriptorParser; -use log::warn; use node_resolver::analyze::NodeCodeTranslator; use once_cell::sync::OnceCell; +use sys_traits::EnvCurrentDir; use crate::args::check_warn_tsconfig; use crate::args::CliOptions; +use crate::args::ConfigFlag; use crate::args::DenoSubcommand; use crate::args::Flags; use crate::args::NpmInstallDepsProvider; use crate::args::TsConfigType; +use crate::args::WorkspaceExternalImportMapLoader; use crate::cache::Caches; use crate::cache::CodeCache; use crate::cache::DenoDir; @@ -56,12 +65,12 @@ use crate::cache::DenoDirProvider; use crate::cache::EmitCache; use crate::cache::GlobalHttpCache; use crate::cache::HttpCache; -use crate::cache::LocalHttpCache; use crate::cache::ModuleInfoCache; use crate::cache::NodeAnalysisCache; use crate::cache::ParsedSourceCache; use crate::emit::Emitter; use crate::file_fetcher::CliFileFetcher; +use crate::file_fetcher::TextDecodedFile; use crate::graph_container::MainModuleGraphContainer; use crate::graph_util::FileWatcherReporter; use crate::graph_util::ModuleGraphBuilder; @@ -75,13 +84,10 @@ use crate::node::CliNodeResolver; use crate::node::CliPackageJsonResolver; use crate::npm::installer::NpmInstaller; use crate::npm::installer::NpmResolutionInstaller; -use crate::npm::CliByonmNpmResolverCreateOptions; -use crate::npm::CliManagedNpmResolverCreateOptions; use crate::npm::CliNpmCache; use crate::npm::CliNpmCacheHttpClient; use crate::npm::CliNpmRegistryInfoProvider; use crate::npm::CliNpmResolver; -use crate::npm::CliNpmResolverCreateOptions; use crate::npm::CliNpmResolverManagedSnapshotOption; use crate::npm::CliNpmTarballCache; use crate::npm::NpmResolutionInitializer; @@ -100,7 +106,6 @@ use crate::tools::lint::LintRuleProvider; use crate::tools::run::hmr::HmrRunner; use crate::tsc::TypeCheckingCjsTracker; use crate::util::file_watcher::WatcherCommunicator; -use crate::util::fs::canonicalize_path_maybe_not_exists; use crate::util::progress_bar::ProgressBar; use crate::util::progress_bar::ProgressBarStyle; use crate::worker::CliMainWorkerFactory; @@ -143,6 +148,74 @@ impl RootCertStoreProvider for CliRootCertStoreProvider { } } +#[derive(Debug)] +struct CliSpecifiedImportMapProvider { + cli_options: Arc, + file_fetcher: Arc, + workspace_external_import_map_loader: Arc, +} + +#[async_trait::async_trait(?Send)] +impl SpecifiedImportMapProvider for CliSpecifiedImportMapProvider { + async fn get( + &self, + ) -> Result, AnyError> { + async fn resolve_import_map_value_from_specifier( + specifier: &Url, + file_fetcher: &CliFileFetcher, + ) -> Result { + if specifier.scheme() == "data" { + let data_url_text = + deno_media_type::data_url::RawDataUrl::parse(specifier)?.decode()?; + Ok(serde_json::from_str(&data_url_text)?) + } else { + let file = TextDecodedFile::decode( + file_fetcher.fetch_bypass_permissions(specifier).await?, + )?; + Ok(serde_json::from_str(&file.source)?) + } + } + + let maybe_import_map_specifier = + self.cli_options.resolve_specified_import_map_specifier()?; + match maybe_import_map_specifier { + Some(specifier) => { + let value = resolve_import_map_value_from_specifier( + &specifier, + &self.file_fetcher, + ) + .await + .with_context(|| { + format!("Unable to load '{}' import map", specifier) + })?; + Ok(Some(deno_config::workspace::SpecifiedImportMap { + base_url: specifier, + value, + })) + } + None => { + if let Some(import_map) = + self.workspace_external_import_map_loader.get_or_load()? + { + let path_url = deno_path_util::url_from_file_path(&import_map.path)?; + Ok(Some(deno_config::workspace::SpecifiedImportMap { + base_url: path_url, + value: import_map.value.clone(), + })) + } else { + Ok(None) + } + } + } + } +} + +pub type CliWorkspaceFactory = deno_resolver::factory::WorkspaceFactory; +pub type CliDenoDirPathProvider = + deno_resolver::factory::DenoDirPathProvider; + +pub type CliResolverFactory = deno_resolver::factory::ResolverFactory; + pub struct Deferred(once_cell::unsync::OnceCell); impl Default for Deferred { @@ -152,10 +225,6 @@ impl Default for Deferred { } impl Deferred { - pub fn from_value(value: T) -> Self { - Self(once_cell::unsync::OnceCell::from(value)) - } - #[inline(always)] pub fn get_or_try_init( &self, @@ -194,17 +263,15 @@ struct CliFactoryServices { cjs_tracker: Deferred>, cli_options: Deferred>, code_cache: Deferred>, - deno_resolver: Deferred>, + deno_dir_path_provider: Deferred>, + deno_dir_provider: Deferred>, emit_cache: Deferred>, emitter: Deferred>, feature_checker: Deferred>, file_fetcher: Deferred>, found_pkg_json_dep_flag: Arc, fs: Deferred>, - global_http_cache: Deferred>, - http_cache: Deferred>, http_client_provider: Deferred>, - in_npm_pkg_checker: Deferred, main_graph_container: Deferred>, maybe_file_watcher_reporter: Deferred>, maybe_inspector_server: Deferred>>, @@ -213,36 +280,39 @@ struct CliFactoryServices { module_info_cache: Deferred>, module_load_preparer: Deferred>, node_code_translator: Deferred>, - node_resolver: Deferred>, npm_cache: Deferred>, - npm_cache_dir: Deferred>, npm_cache_http_client: Deferred>, npm_graph_resolver: Deferred>, npm_installer: Deferred>, npm_registry_info_provider: Deferred>, - npm_req_resolver: Deferred>, - npm_resolution: Arc, npm_resolution_initializer: Deferred>, npm_resolution_installer: Deferred>, - npm_resolver: Deferred, npm_tarball_cache: Deferred>, parsed_source_cache: Deferred>, permission_desc_parser: Deferred>>, - pkg_json_resolver: Deferred>, resolver: Deferred>, + resolver_factory: Deferred>, root_cert_store_provider: Deferred>, root_permissions_container: Deferred, - sloppy_imports_resolver: Deferred>>, text_only_progress_bar: Deferred, type_checker: Deferred>, - workspace_resolver: Deferred>, + workspace_factory: Deferred>, + workspace_external_import_map_loader: + Deferred>, +} + +#[derive(Debug, Default)] +struct CliFactoryOverrides { + initial_cwd: Option, + workspace_directory: Option>, } pub struct CliFactory { watcher_communicator: Option>, flags: Arc, services: CliFactoryServices, + overrides: CliFactoryOverrides, } impl CliFactory { @@ -251,18 +321,7 @@ impl CliFactory { flags, watcher_communicator: None, services: Default::default(), - } - } - - pub fn from_cli_options(cli_options: Arc) -> Self { - let (cli_options, flags) = cli_options.into_self_and_flags(); - CliFactory { - watcher_communicator: None, - flags, - services: CliFactoryServices { - cli_options: Deferred::from_value(cli_options), - ..Default::default() - }, + overrides: Default::default(), } } @@ -274,27 +333,63 @@ impl CliFactory { watcher_communicator: Some(watcher_communicator), flags, services: Default::default(), + overrides: Default::default(), } } + pub fn set_initial_cwd(&mut self, initial_cwd: PathBuf) { + self.overrides.initial_cwd = Some(initial_cwd); + } + + pub fn set_workspace_dir(&mut self, dir: Arc) { + self.overrides.workspace_directory = Some(dir); + } + pub fn cli_options(&self) -> Result<&Arc, AnyError> { self.services.cli_options.get_or_try_init(|| { - CliOptions::from_flags(&self.sys(), self.flags.clone()).map(Arc::new) + let workspace_factory = self.workspace_factory()?; + let workspace_directory = workspace_factory.workspace_directory()?; + let maybe_external_import_map = + self.workspace_external_import_map_loader()?.get_or_load()?; + CliOptions::from_flags( + &self.sys(), + self.flags.clone(), + workspace_factory.initial_cwd().clone(), + maybe_external_import_map, + workspace_directory.clone(), + ) + .map(Arc::new) }) } - pub fn deno_dir_provider(&self) -> Result<&Arc, AnyError> { - Ok(&self.cli_options()?.deno_dir_provider) + pub fn deno_dir_path_provider(&self) -> &Arc { + self.services.deno_dir_path_provider.get_or_init(|| { + Arc::new(CliDenoDirPathProvider::new( + self.sys(), + DenoDirPathProviderOptions { + maybe_custom_root: self.flags.internal.cache_path.clone(), + }, + )) + }) + } + + pub fn deno_dir_provider(&self) -> &Arc { + self.services.deno_dir_provider.get_or_init(|| { + Arc::new(DenoDirProvider::new( + self.sys(), + self.deno_dir_path_provider().clone(), + )) + }) } pub fn deno_dir(&self) -> Result<&DenoDir, AnyError> { - Ok(self.deno_dir_provider()?.get_or_create()?) + Ok(self.deno_dir_provider().get_or_create()?) } pub fn caches(&self) -> Result<&Arc, AnyError> { self.services.caches.get_or_try_init(|| { let cli_options = self.cli_options()?; - let caches = Arc::new(Caches::new(self.deno_dir_provider()?.clone())); + let caches = Arc::new(Caches::new(self.deno_dir_provider().clone())); // Warm up the caches we know we'll likely need based on the CLI mode match cli_options.sub_command() { DenoSubcommand::Run(_) @@ -340,29 +435,11 @@ impl CliFactory { } pub fn global_http_cache(&self) -> Result<&Arc, AnyError> { - self.services.global_http_cache.get_or_try_init(|| { - Ok(Arc::new(GlobalHttpCache::new( - self.sys(), - self.deno_dir()?.remote_folder_path(), - ))) - }) + Ok(self.workspace_factory()?.global_http_cache()?) } pub fn http_cache(&self) -> Result<&Arc, AnyError> { - self.services.http_cache.get_or_try_init(|| { - let global_cache = self.global_http_cache()?.clone(); - match self.cli_options()?.vendor_dir_path() { - Some(local_path) => { - let local_cache = LocalHttpCache::new( - local_path.clone(), - global_cache, - deno_cache_dir::GlobalToLocalCopy::Allow, - ); - Ok(Arc::new(local_cache)) - } - None => Ok(global_cache), - } - }) + Ok(self.workspace_factory()?.http_cache()?) } pub fn http_client_provider(&self) -> &Arc { @@ -401,22 +478,7 @@ impl CliFactory { pub fn in_npm_pkg_checker( &self, ) -> Result<&DenoInNpmPackageChecker, AnyError> { - self.services.in_npm_pkg_checker.get_or_try_init(|| { - let cli_options = self.cli_options()?; - let options = if cli_options.use_byonm() { - CreateInNpmPkgCheckerOptions::Byonm - } else { - CreateInNpmPkgCheckerOptions::Managed( - ManagedInNpmPkgCheckerCreateOptions { - root_cache_dir_url: self.npm_cache_dir()?.root_dir_url(), - maybe_node_modules_path: cli_options - .node_modules_dir_path() - .map(|p| p.as_path()), - }, - ) - }; - Ok(DenoInNpmPackageChecker::new(options)) - }) + self.resolver_factory()?.in_npm_package_checker() } pub fn npm_cache(&self) -> Result<&Arc, AnyError> { @@ -426,21 +488,13 @@ impl CliFactory { self.npm_cache_dir()?.clone(), self.sys(), NpmCacheSetting::from_cache_setting(&cli_options.cache_setting()), - cli_options.npmrc().clone(), + self.npmrc()?.clone(), ))) }) } pub fn npm_cache_dir(&self) -> Result<&Arc, AnyError> { - self.services.npm_cache_dir.get_or_try_init(|| { - let global_path = self.deno_dir()?.npm_folder_path(); - let cli_options = self.cli_options()?; - Ok(Arc::new(NpmCacheDir::new( - &self.sys(), - global_path, - cli_options.npmrc().get_all_known_registries_urls(), - ))) - }) + Ok(self.workspace_factory()?.npm_cache_dir()?) } pub fn npm_cache_http_client(&self) -> &Arc { @@ -469,8 +523,7 @@ impl CliFactory { pub fn npm_installer_if_managed( &self, ) -> Result>, AnyError> { - let options = self.cli_options()?; - if options.use_byonm() || options.no_npm() { + if self.resolver_factory()?.use_byonm()? || self.cli_options()?.no_npm() { Ok(None) } else { Ok(Some(self.npm_installer()?)) @@ -480,19 +533,22 @@ impl CliFactory { pub fn npm_installer(&self) -> Result<&Arc, AnyError> { self.services.npm_installer.get_or_try_init(|| { let cli_options = self.cli_options()?; + let workspace_factory = self.workspace_factory()?; Ok(Arc::new(NpmInstaller::new( self.npm_cache()?.clone(), Arc::new(NpmInstallDepsProvider::from_workspace( cli_options.workspace(), )), - self.npm_resolution().clone(), + self.npm_resolution()?.clone(), self.npm_resolution_initializer()?.clone(), self.npm_resolution_installer()?.clone(), self.text_only_progress_bar(), self.sys(), self.npm_tarball_cache()?.clone(), cli_options.maybe_lockfile().cloned(), - cli_options.node_modules_dir_path().cloned(), + workspace_factory + .node_modules_dir_path()? + .map(|p| p.to_path_buf()), cli_options.lifecycle_scripts_config(), cli_options.npm_system_info(), ))) @@ -506,17 +562,16 @@ impl CliFactory { .services .npm_registry_info_provider .get_or_try_init(|| { - let cli_options = self.cli_options()?; Ok(Arc::new(CliNpmRegistryInfoProvider::new( self.npm_cache()?.clone(), self.npm_cache_http_client().clone(), - cli_options.npmrc().clone(), + self.npmrc()?.clone(), ))) }) } - pub fn npm_resolution(&self) -> &Arc { - &self.services.npm_resolution + pub fn npm_resolution(&self) -> Result<&Arc, AnyError> { + Ok(self.resolver_factory()?.npm_resolution()) } pub fn npm_resolution_initializer( @@ -529,8 +584,8 @@ impl CliFactory { let cli_options = self.cli_options()?; Ok(Arc::new(NpmResolutionInitializer::new( self.npm_registry_info_provider()?.clone(), - self.npm_resolution().clone(), - match cli_options.resolve_npm_resolution_snapshot()? { + self.npm_resolution()?.clone(), + match resolve_npm_resolution_snapshot()? { Some(snapshot) => { CliNpmResolverManagedSnapshotOption::Specified(Some(snapshot)) } @@ -554,71 +609,59 @@ impl CliFactory { let cli_options = self.cli_options()?; Ok(Arc::new(NpmResolutionInstaller::new( self.npm_registry_info_provider()?.clone(), - self.npm_resolution().clone(), + self.npm_resolution()?.clone(), cli_options.maybe_lockfile().cloned(), ))) }) } pub async fn npm_resolver(&self) -> Result<&CliNpmResolver, AnyError> { - self - .services - .npm_resolver - .get_or_try_init_async( - async { - let cli_options = self.cli_options()?; - Ok(CliNpmResolver::new(if cli_options.use_byonm() { - CliNpmResolverCreateOptions::Byonm( - CliByonmNpmResolverCreateOptions { - sys: self.sys(), - pkg_json_resolver: self.pkg_json_resolver().clone(), - root_node_modules_dir: Some( - match cli_options.node_modules_dir_path() { - Some(node_modules_path) => node_modules_path.to_path_buf(), - // path needs to be canonicalized for node resolution - // (node_modules_dir_path above is already canonicalized) - None => canonicalize_path_maybe_not_exists( - cli_options.initial_cwd(), - )? - .join("node_modules"), - }, - ), - }, - ) - } else { - self - .npm_resolution_initializer()? - .ensure_initialized() - .await?; - CliNpmResolverCreateOptions::Managed( - CliManagedNpmResolverCreateOptions { - sys: self.sys(), - npm_resolution: self.npm_resolution().clone(), - npm_cache_dir: self.npm_cache_dir()?.clone(), - maybe_node_modules_path: cli_options - .node_modules_dir_path() - .cloned(), - npm_system_info: cli_options.npm_system_info(), - npmrc: cli_options.npmrc().clone(), - }, - ) - })) - } - .boxed_local(), - ) - .await + self.initialize_npm_resolution_if_managed().await?; + self.resolver_factory()?.npm_resolver() } pub fn npm_tarball_cache( &self, ) -> Result<&Arc, AnyError> { self.services.npm_tarball_cache.get_or_try_init(|| { - let cli_options = self.cli_options()?; Ok(Arc::new(CliNpmTarballCache::new( self.npm_cache()?.clone(), self.npm_cache_http_client().clone(), self.sys(), - cli_options.npmrc().clone(), + self.npmrc()?.clone(), + ))) + }) + } + + pub fn npmrc(&self) -> Result<&Arc, AnyError> { + Ok(self.workspace_factory()?.npmrc()?) + } + + pub fn resolver_factory(&self) -> Result<&Arc, AnyError> { + self.services.resolver_factory.get_or_try_init(|| { + Ok(Arc::new(CliResolverFactory::new( + self.workspace_factory()?.clone(), + ResolverFactoryOptions { + conditions_from_resolution_mode: Default::default(), + no_sloppy_imports_cache: false, + npm_system_info: self.flags.subcommand.npm_system_info(), + specified_import_map: Some(Box::new(CliSpecifiedImportMapProvider { + cli_options: self.cli_options()?.clone(), + file_fetcher: self.file_fetcher()?.clone(), + workspace_external_import_map_loader: self + .workspace_external_import_map_loader()? + .clone(), + })), + unstable_sloppy_imports: self.flags.unstable_config.sloppy_imports, + package_json_dep_resolution: match &self.flags.subcommand { + DenoSubcommand::Publish(_) => { + // the node_modules directory is not published to jsr, so resolve + // dependencies via the package.json rather than using node resolution + Some(deno_config::workspace::PackageJsonDepResolution::Enabled) + } + _ => None, + }, + }, ))) }) } @@ -626,82 +669,48 @@ impl CliFactory { pub fn sloppy_imports_resolver( &self, ) -> Result>, AnyError> { - self - .services - .sloppy_imports_resolver - .get_or_try_init(|| { - Ok(self.cli_options()?.unstable_sloppy_imports().then(|| { - Arc::new(CliSloppyImportsResolver::new(SloppyImportsCachedFs::new( - self.sys(), - ))) - })) - }) - .map(|maybe| maybe.as_ref()) + self.resolver_factory()?.sloppy_imports_resolver() + } + + pub fn workspace_directory( + &self, + ) -> Result<&Arc, AnyError> { + Ok(self.workspace_factory()?.workspace_directory()?) + } + + fn workspace_factory(&self) -> Result<&Arc, AnyError> { + self.services.workspace_factory.get_or_try_init(|| { + let initial_cwd = match self.overrides.initial_cwd.clone() { + Some(v) => v, + None => self + .sys() + .env_current_dir() + .with_context(|| "Failed getting cwd.")?, + }; + let options = new_workspace_factory_options( + &initial_cwd, + &self.flags, + self.deno_dir_path_provider().clone(), + ); + let mut factory = + CliWorkspaceFactory::new(self.sys(), initial_cwd, options); + if let Some(workspace_dir) = &self.overrides.workspace_directory { + factory.set_workspace_directory(workspace_dir.clone()); + } + Ok(Arc::new(factory)) + }) } pub async fn workspace_resolver( &self, ) -> Result<&Arc, AnyError> { - self - .services - .workspace_resolver - .get_or_try_init_async(async { - let cli_options = self.cli_options()?; - let resolver = cli_options - .create_workspace_resolver( - self.file_fetcher()?, - if cli_options.use_byonm() - && !matches!( - cli_options.sub_command(), - DenoSubcommand::Publish(_) - ) - { - PackageJsonDepResolution::Disabled - } else { - // todo(dsherret): this should be false for nodeModulesDir: true - PackageJsonDepResolution::Enabled - }, - ) - .await?; - if !resolver.diagnostics().is_empty() { - warn!( - "Import map diagnostics:\n{}", - resolver - .diagnostics() - .iter() - .map(|d| format!(" - {d}")) - .collect::>() - .join("\n") - ); - } - Ok(Arc::new(resolver)) - }) - .await + self.initialize_npm_resolution_if_managed().await?; + self.resolver_factory()?.workspace_resolver().await } pub async fn deno_resolver(&self) -> Result<&Arc, AnyError> { - self - .services - .deno_resolver - .get_or_try_init_async(async { - let cli_options = self.cli_options()?; - Ok(Arc::new(CliDenoResolver::new(DenoResolverOptions { - in_npm_pkg_checker: self.in_npm_pkg_checker()?.clone(), - node_and_req_resolver: if cli_options.no_npm() { - None - } else { - Some(NodeAndNpmReqResolver { - node_resolver: self.node_resolver().await?.clone(), - npm_req_resolver: self.npm_req_resolver().await?.clone(), - }) - }, - sloppy_imports_resolver: self.sloppy_imports_resolver()?.cloned(), - workspace_resolver: self.workspace_resolver().await?.clone(), - is_byonm: cli_options.use_byonm(), - maybe_vendor_dir: cli_options.vendor_dir_path(), - }))) - }) - .await + self.initialize_npm_resolution_if_managed().await?; + self.resolver_factory()?.deno_resolver().await } pub async fn resolver(&self) -> Result<&Arc, AnyError> { @@ -787,23 +796,19 @@ impl CliFactory { } pub async fn node_resolver(&self) -> Result<&Arc, AnyError> { - self - .services - .node_resolver - .get_or_try_init_async( - async { - Ok(Arc::new(CliNodeResolver::new( - self.in_npm_pkg_checker()?.clone(), - RealIsBuiltInNodeModuleChecker, - self.npm_resolver().await?.clone(), - self.pkg_json_resolver().clone(), - self.sys(), - node_resolver::ConditionsFromResolutionMode::default(), - ))) - } - .boxed_local(), - ) - .await + self.initialize_npm_resolution_if_managed().await?; + self.resolver_factory()?.node_resolver() + } + + async fn initialize_npm_resolution_if_managed(&self) -> Result<(), AnyError> { + let npm_resolver = self.resolver_factory()?.npm_resolver()?; + if npm_resolver.is_managed() { + self + .npm_resolution_initializer()? + .ensure_initialized() + .await?; + } + Ok(()) } pub async fn node_code_translator( @@ -821,7 +826,7 @@ impl CliFactory { self.in_npm_pkg_checker()?.clone(), node_resolver, self.npm_resolver().await?.clone(), - self.pkg_json_resolver().clone(), + self.pkg_json_resolver()?.clone(), self.sys(), ))) }) @@ -839,29 +844,14 @@ impl CliFactory { )) } - pub async fn npm_req_resolver( - &self, - ) -> Result<&Arc, AnyError> { - self - .services - .npm_req_resolver - .get_or_try_init_async(async { - let npm_resolver = self.npm_resolver().await?; - Ok(Arc::new(CliNpmReqResolver::new(NpmReqResolverOptions { - sys: self.sys(), - in_npm_pkg_checker: self.in_npm_pkg_checker()?.clone(), - node_resolver: self.node_resolver().await?.clone(), - npm_resolver: npm_resolver.clone(), - }))) - }) - .await + pub fn npm_req_resolver(&self) -> Result<&Arc, AnyError> { + self.resolver_factory()?.npm_req_resolver() } - pub fn pkg_json_resolver(&self) -> &Arc { - self - .services - .pkg_json_resolver - .get_or_init(|| Arc::new(CliPackageJsonResolver::new(self.sys()))) + pub fn pkg_json_resolver( + &self, + ) -> Result<&Arc, AnyError> { + Ok(self.resolver_factory()?.pkg_json_resolver()) } pub async fn type_checker(&self) -> Result<&Arc, AnyError> { @@ -987,7 +977,7 @@ impl CliFactory { let options = self.cli_options()?; Ok(Arc::new(CliCjsTracker::new( self.in_npm_pkg_checker()?.clone(), - self.pkg_json_resolver().clone(), + self.pkg_json_resolver()?.clone(), if options.is_node_main() || options.unstable_detect_cjs() { IsCjsResolutionMode::ImplicitTypeCommonJs } else if options.detect_cjs() { @@ -1056,6 +1046,20 @@ impl CliFactory { }) } + fn workspace_external_import_map_loader( + &self, + ) -> Result<&Arc, AnyError> { + self + .services + .workspace_external_import_map_loader + .get_or_try_init(|| { + Ok(Arc::new(WorkspaceExternalImportMapLoader::new( + self.sys(), + self.workspace_directory()?.workspace.clone(), + ))) + }) + } + pub async fn create_cli_main_worker_factory( &self, ) -> Result { @@ -1072,14 +1076,18 @@ impl CliFactory { }; let node_code_translator = self.node_code_translator().await?; let cjs_tracker = self.cjs_tracker()?.clone(); - let pkg_json_resolver = self.pkg_json_resolver().clone(); - let npm_req_resolver = self.npm_req_resolver().await?; + let pkg_json_resolver = self.pkg_json_resolver()?.clone(); + let npm_req_resolver = self.npm_req_resolver()?; + let workspace_factory = self.workspace_factory()?; let npm_registry_permission_checker = { - let mode = if cli_options.use_byonm() { + let mode = if self.resolver_factory()?.use_byonm()? { NpmRegistryReadPermissionCheckerMode::Byonm - } else if let Some(node_modules_dir) = cli_options.node_modules_dir_path() + } else if let Some(node_modules_dir) = + workspace_factory.node_modules_dir_path()? { - NpmRegistryReadPermissionCheckerMode::Local(node_modules_dir.clone()) + NpmRegistryReadPermissionCheckerMode::Local( + node_modules_dir.to_path_buf(), + ) } else { NpmRegistryReadPermissionCheckerMode::Global( self.npm_cache_dir()?.root_dir().to_path_buf(), @@ -1151,6 +1159,7 @@ impl CliFactory { &self, ) -> Result { let cli_options = self.cli_options()?; + let workspace_factory = self.workspace_factory()?; Ok(LibMainWorkerOptions { argv: cli_options.argv().clone(), // This optimization is only available for "run" subcommand @@ -1160,7 +1169,9 @@ impl CliFactory { log_level: cli_options.log_level().unwrap_or(log::Level::Info).into(), enable_op_summary_metrics: cli_options.enable_op_summary_metrics(), enable_testing_features: cli_options.enable_testing_features(), - has_node_modules_dir: cli_options.has_node_modules_dir(), + has_node_modules_dir: workspace_factory + .node_modules_dir_path()? + .is_some(), inspect_brk: cli_options.inspect_brk().is_some(), inspect_wait: cli_options.inspect_wait().is_some(), strace_ops: cli_options.strace_ops().clone(), @@ -1223,3 +1234,57 @@ impl CliFactory { }) } } + +fn new_workspace_factory_options( + initial_cwd: &Path, + flags: &Flags, + deno_dir_path_provider: Arc, +) -> deno_resolver::factory::WorkspaceFactoryOptions { + deno_resolver::factory::WorkspaceFactoryOptions { + additional_config_file_names: if matches!( + flags.subcommand, + DenoSubcommand::Publish(..) + ) { + &["jsr.json", "jsr.jsonc"] + } else { + &[] + }, + config_discovery: match &flags.config_flag { + ConfigFlag::Discover => { + if let Some(start_paths) = flags.config_path_args(initial_cwd) { + ConfigDiscoveryOption::Discover { start_paths } + } else { + ConfigDiscoveryOption::Disabled + } + } + ConfigFlag::Path(path) => { + ConfigDiscoveryOption::Path(PathBuf::from(path)) + } + ConfigFlag::Disabled => ConfigDiscoveryOption::Disabled, + }, + deno_dir_path_provider: Some(deno_dir_path_provider), + // For `deno install/add/remove/init` we want to force the managed + // resolver so it can set up the `node_modules/` directory. + is_package_manager_subcommand: matches!( + flags.subcommand, + DenoSubcommand::Install(_) + | DenoSubcommand::Add(_) + | DenoSubcommand::Remove(_) + | DenoSubcommand::Init(_) + | DenoSubcommand::Outdated(_) + ), + no_npm: flags.no_npm, + node_modules_dir: flags.node_modules_dir, + + npm_process_state: NPM_PROCESS_STATE.as_ref().map(|s| { + NpmProcessStateOptions { + node_modules_dir: s + .local_node_modules_path + .as_ref() + .map(|s| Cow::Borrowed(s.as_str())), + is_byonm: matches!(s.kind, NpmProcessStateKind::Byonm), + } + }), + vendor: flags.vendor, + } +} diff --git a/cli/lib/Cargo.toml b/cli/lib/Cargo.toml index 945e189f45..b6bcaa7e43 100644 --- a/cli/lib/Cargo.toml +++ b/cli/lib/Cargo.toml @@ -15,7 +15,7 @@ path = "lib.rs" [dependencies] capacity_builder.workspace = true -deno_config.workspace = true +deno_config = { workspace = true, features = ["sync", "workspace"] } deno_error.workspace = true deno_fs = { workspace = true, features = ["sync_fs"] } deno_media_type.workspace = true diff --git a/cli/lib/args.rs b/cli/lib/args.rs index 3e64f5ab4c..22bebdf5d9 100644 --- a/cli/lib/args.rs +++ b/cli/lib/args.rs @@ -7,6 +7,8 @@ use std::io::Seek; use std::path::PathBuf; use std::sync::LazyLock; +use deno_npm::resolution::PackageIdNotFoundError; +use deno_npm::resolution::ValidSerializedNpmResolutionSnapshot; use deno_runtime::colors; use deno_runtime::deno_tls::deno_native_certs::load_native_certs; use deno_runtime::deno_tls::rustls; @@ -32,8 +34,10 @@ pub fn has_trace_permissions_enabled() -> bool { } pub fn has_flag_env_var(name: &str) -> bool { - let value = std::env::var(name); - matches!(value.as_ref().map(|s| s.as_str()), Ok("1")) + match std::env::var_os(name) { + Some(value) => value == "1", + None => false, + } } #[derive(Clone, Debug, Eq, PartialEq)] @@ -187,6 +191,19 @@ pub static NPM_PROCESS_STATE: LazyLock> = Some(state) }); +pub fn resolve_npm_resolution_snapshot( +) -> Result, PackageIdNotFoundError> +{ + if let Some(NpmProcessStateKind::Snapshot(snapshot)) = + NPM_PROCESS_STATE.as_ref().map(|s| &s.kind) + { + // TODO(bartlomieju): remove this clone + Ok(Some(snapshot.clone().into_valid()?)) + } else { + Ok(None) + } +} + #[derive(Clone, Default, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct UnstableConfig { // TODO(bartlomieju): remove in Deno 2.5 diff --git a/cli/lsp/cache.rs b/cli/lsp/cache.rs index 97fbbaff14..9ac2a80d46 100644 --- a/cli/lsp/cache.rs +++ b/cli/lsp/cache.rs @@ -94,8 +94,10 @@ impl LspCache { .ok() }); let sys = CliSys::default(); - let deno_dir = DenoDir::new(sys.clone(), global_cache_path) - .expect("should be infallible with absolute custom root"); + let deno_dir_root = + deno_cache_dir::resolve_deno_dir(&sys, global_cache_path) + .expect("should be infallible with absolute custom root"); + let deno_dir = DenoDir::new(sys.clone(), deno_dir_root); let global = Arc::new(GlobalHttpCache::new(sys, deno_dir.remote_folder_path())); Self { diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs index a0456c14ed..1882a0f567 100644 --- a/cli/lsp/config.rs +++ b/cli/lsp/config.rs @@ -47,6 +47,7 @@ use deno_lint::linter::LintConfig as DenoLintConfig; use deno_npm::npm_rc::ResolvedNpmRc; use deno_package_json::PackageJsonCache; use deno_path_util::url_to_file_path; +use deno_resolver::npmrc::discover_npmrc_from_workspace; use deno_resolver::sloppy_imports::SloppyImportsCachedFs; use deno_runtime::deno_node::PackageJson; use indexmap::IndexSet; @@ -56,7 +57,6 @@ use tower_lsp::lsp_types as lsp; use super::logging::lsp_log; use super::lsp_custom; use super::urls::url_to_uri; -use crate::args::discover_npmrc_from_workspace; use crate::args::CliLockfile; use crate::args::CliLockfileReadFromPathOptions; use crate::args::ConfigFile; @@ -1362,21 +1362,22 @@ impl ConfigData { } // todo(dsherret): cache this so we don't load this so many times - let npmrc = discover_npmrc_from_workspace(&member_dir.workspace) - .inspect(|(_, path)| { - if let Some(path) = path { - lsp_log!(" Resolved .npmrc: \"{}\"", path.display()); + let npmrc = + discover_npmrc_from_workspace(&CliSys::default(), &member_dir.workspace) + .inspect(|(_, path)| { + if let Some(path) = path { + lsp_log!(" Resolved .npmrc: \"{}\"", path.display()); - if let Ok(specifier) = ModuleSpecifier::from_file_path(path) { - add_watched_file(specifier, ConfigWatchedFileType::NpmRc); + if let Ok(specifier) = ModuleSpecifier::from_file_path(path) { + add_watched_file(specifier, ConfigWatchedFileType::NpmRc); + } } - } - }) - .inspect_err(|err| { - lsp_warn!(" Couldn't read .npmrc for \"{scope}\": {err}"); - }) - .map(|(r, _)| r) - .ok(); + }) + .inspect_err(|err| { + lsp_warn!(" Couldn't read .npmrc for \"{scope}\": {err}"); + }) + .map(|(r, _)| Arc::new(r)) + .ok(); let default_file_pattern_base = scope.to_file_path().unwrap_or_else(|_| PathBuf::from("/")); let fmt_config = Arc::new( diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index 8bb77681a2..76982fb332 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -13,8 +13,6 @@ use std::sync::Arc; use deno_ast::MediaType; use deno_cache_dir::file_fetcher::CacheSetting; -use deno_config::workspace::WorkspaceDirectory; -use deno_config::workspace::WorkspaceDiscoverOptions; use deno_core::anyhow::anyhow; use deno_core::error::AnyError; use deno_core::resolve_url; @@ -28,7 +26,6 @@ use deno_core::ModuleSpecifier; use deno_graph::GraphKind; use deno_graph::Resolution; use deno_lib::args::get_root_cert_store; -use deno_lib::args::has_flag_env_var; use deno_lib::args::CaData; use deno_lib::version::DENO_VERSION_INFO; use deno_path_util::url_to_file_path; @@ -97,8 +94,6 @@ use super::tsc::TsServer; use super::urls; use super::urls::uri_to_url; use super::urls::url_to_uri; -use crate::args::create_default_npmrc; -use crate::args::CliOptions; use crate::args::Flags; use crate::args::InternalFlags; use crate::args::UnstableFmtOptions; @@ -255,7 +250,7 @@ impl LanguageServer { force_global_cache: bool, ) -> LspResult> { async fn create_graph_for_caching( - cli_options: CliOptions, + factory: CliFactory, roots: Vec, open_docs: Vec>, ) -> Result<(), AnyError> { @@ -263,8 +258,6 @@ impl LanguageServer { .into_iter() .map(|d| (d.specifier().clone(), d)) .collect::>(); - let cli_options = Arc::new(cli_options); - let factory = CliFactory::from_cli_options(cli_options.clone()); let module_graph_builder = factory.module_graph_builder().await?; let module_graph_creator = factory.module_graph_creator().await?; let mut inner_loader = module_graph_builder.create_graph_loader(); @@ -293,9 +286,11 @@ impl LanguageServer { // Update the lockfile on the file system with anything new // found after caching - if let Some(lockfile) = cli_options.maybe_lockfile() { - if let Err(err) = &lockfile.write_if_changed() { - lsp_warn!("{:#}", err); + if let Ok(cli_options) = factory.cli_options() { + if let Some(lockfile) = cli_options.maybe_lockfile() { + if let Err(err) = &lockfile.write_if_changed() { + lsp_warn!("{:#}", err); + } } } @@ -319,11 +314,11 @@ impl LanguageServer { match prepare_cache_result { Ok(result) => { // cache outside the lock - let cli_options = result.cli_options; + let cli_factory = result.cli_factory; let roots = result.roots; let open_docs = result.open_docs; let handle = spawn(async move { - create_graph_for_caching(cli_options, roots, open_docs).await + create_graph_for_caching(cli_factory, roots, open_docs).await }); if let Err(err) = handle.await.unwrap() { @@ -3492,7 +3487,7 @@ impl tower_lsp::LanguageServer for LanguageServer { } struct PrepareCacheResult { - cli_options: CliOptions, + cli_factory: CliFactory, roots: Vec, open_docs: Vec>, } @@ -3626,66 +3621,44 @@ impl Inner { let initial_cwd = config_data .and_then(|d| d.scope.to_file_path().ok()) .unwrap_or_else(|| self.initial_cwd.clone()); - let workspace = match config_data { - Some(d) => d.member_dir.clone(), - None => Arc::new(WorkspaceDirectory::discover( - &CliSys::default(), - deno_config::workspace::WorkspaceDiscoverStart::Paths(&[ - initial_cwd.clone() - ]), - &WorkspaceDiscoverOptions { - deno_json_cache: None, - pkg_json_cache: None, - workspace_cache: None, - additional_config_file_names: &[], - discover_pkg_json: !has_flag_env_var("DENO_NO_PACKAGE_JSON"), - maybe_vendor_override: if force_global_cache { - Some(deno_config::workspace::VendorEnablement::Disable) - } else { - None - }, - }, - )?), - }; - let cli_options = CliOptions::new( - &CliSys::default(), - Arc::new(Flags { - internal: InternalFlags { - cache_path: Some(self.cache.deno_dir().root.clone()), - ..Default::default() - }, - ca_stores: workspace_settings.certificate_stores.clone(), - ca_data: workspace_settings.tls_certificate.clone().map(CaData::File), - unsafely_ignore_certificate_errors: workspace_settings - .unsafely_ignore_certificate_errors - .clone(), - import_map_path: config_data.and_then(|d| { - d.import_map_from_settings - .as_ref() - .map(|url| url.to_string()) - }), - // bit of a hack to force the lsp to cache the @types/node package - type_check_mode: crate::args::TypeCheckMode::Local, - permissions: crate::args::PermissionFlags { - // allow remote import permissions in the lsp for now - allow_import: Some(vec![]), - ..Default::default() - }, + let mut cli_factory = CliFactory::from_flags(Arc::new(Flags { + internal: InternalFlags { + cache_path: Some(self.cache.deno_dir().root.clone()), ..Default::default() + }, + ca_stores: workspace_settings.certificate_stores.clone(), + ca_data: workspace_settings.tls_certificate.clone().map(CaData::File), + unsafely_ignore_certificate_errors: workspace_settings + .unsafely_ignore_certificate_errors + .clone(), + import_map_path: config_data.and_then(|d| { + d.import_map_from_settings + .as_ref() + .map(|url| url.to_string()) }), - initial_cwd, - config_data.and_then(|d| d.lockfile.clone()), - config_data - .and_then(|d| d.npmrc.clone()) - .unwrap_or_else(create_default_npmrc), - workspace, - force_global_cache, - None, - )?; + // bit of a hack to force the lsp to cache the @types/node package + type_check_mode: crate::args::TypeCheckMode::Local, + permissions: crate::args::PermissionFlags { + // allow remote import permissions in the lsp for now + allow_import: Some(vec![]), + ..Default::default() + }, + vendor: if force_global_cache { + Some(false) + } else { + None + }, + no_lock: force_global_cache, + ..Default::default() + })); + cli_factory.set_initial_cwd(initial_cwd); + if let Some(d) = &config_data { + cli_factory.set_workspace_dir(d.member_dir.clone()); + }; let open_docs = self.documents.documents(DocumentsFilter::OpenDiagnosable); Ok(PrepareCacheResult { - cli_options, + cli_factory, open_docs, roots, }) diff --git a/cli/lsp/npm.rs b/cli/lsp/npm.rs index d53c8cb2ab..20e48ba49b 100644 --- a/cli/lsp/npm.rs +++ b/cli/lsp/npm.rs @@ -6,16 +6,18 @@ use dashmap::DashMap; use deno_core::anyhow::anyhow; use deno_core::error::AnyError; use deno_core::serde_json; +use deno_core::url::Url; use deno_npm::npm_rc::NpmRc; use deno_semver::package::PackageNv; use deno_semver::Version; +use once_cell::sync::Lazy; use serde::Deserialize; use super::search::PackageSearchApi; -use crate::args::npm_registry_url; use crate::file_fetcher::CliFileFetcher; use crate::file_fetcher::TextDecodedFile; use crate::npm::NpmFetchResolver; +use crate::sys::CliSys; #[derive(Debug)] pub struct CliNpmSearchApi { @@ -111,6 +113,14 @@ fn parse_npm_search_response(source: &str) -> Result, AnyError> { Ok(objects.into_iter().map(|o| o.package.name).collect()) } +// this is buried here because generally you want to use the ResolvedNpmRc instead of this. +fn npm_registry_url() -> &'static Url { + static NPM_REGISTRY_DEFAULT_URL: Lazy = + Lazy::new(|| deno_resolver::npmrc::npm_registry_url(&CliSys::default())); + + &NPM_REGISTRY_DEFAULT_URL +} + #[cfg(test)] mod tests { use super::*; diff --git a/cli/lsp/resolver.rs b/cli/lsp/resolver.rs index 1b393ad22b..87154c6eb2 100644 --- a/cli/lsp/resolver.rs +++ b/cli/lsp/resolver.rs @@ -28,20 +28,20 @@ use deno_resolver::npm::managed::NpmResolutionCell; use deno_resolver::npm::CreateInNpmPkgCheckerOptions; use deno_resolver::npm::DenoInNpmPackageChecker; use deno_resolver::npm::NpmReqResolverOptions; +use deno_resolver::npmrc::create_default_npmrc; use deno_resolver::DenoResolverOptions; use deno_resolver::NodeAndNpmReqResolver; -use deno_runtime::deno_node::RealIsBuiltInNodeModuleChecker; use deno_semver::jsr::JsrPackageReqReference; use deno_semver::npm::NpmPackageReqReference; use deno_semver::package::PackageNv; use deno_semver::package::PackageReq; use indexmap::IndexMap; +use node_resolver::DenoIsBuiltInNodeModuleChecker; use node_resolver::NodeResolutionKind; use node_resolver::ResolutionMode; use super::cache::LspCache; use super::jsr::JsrCacheResolver; -use crate::args::create_default_npmrc; use crate::args::CliLockfile; use crate::args::LifecycleScriptsConfig; use crate::args::NpmCachingStrategy; @@ -263,18 +263,19 @@ impl LspScopeResolver { } CliNpmResolver::Managed(managed_npm_resolver) => { CliNpmResolverCreateOptions::Managed({ + let sys = CliSys::default(); let npmrc = self .config_data .as_ref() .and_then(|d| d.npmrc.clone()) - .unwrap_or_else(create_default_npmrc); + .unwrap_or_else(|| Arc::new(create_default_npmrc(&sys))); let npm_cache_dir = Arc::new(NpmCacheDir::new( - &CliSys::default(), + &sys, managed_npm_resolver.global_cache_root_path().to_path_buf(), npmrc.get_all_known_registries_urls(), )); CliManagedNpmResolverCreateOptions { - sys: CliSys::default(), + sys, npm_cache_dir, maybe_node_modules_path: managed_npm_resolver .root_node_modules_path() @@ -710,7 +711,7 @@ impl<'a> ResolverFactory<'a> { let npmrc = self .config_data .and_then(|d| d.npmrc.clone()) - .unwrap_or_else(create_default_npmrc); + .unwrap_or_else(|| Arc::new(create_default_npmrc(&sys))); let npm_cache_dir = Arc::new(NpmCacheDir::new( &sys, cache.deno_dir().npm_folder_path(), @@ -917,7 +918,7 @@ impl<'a> ResolverFactory<'a> { let npm_resolver = self.services.npm_resolver.as_ref()?; Some(Arc::new(CliNodeResolver::new( self.in_npm_pkg_checker().clone(), - RealIsBuiltInNodeModuleChecker, + DenoIsBuiltInNodeModuleChecker, npm_resolver.clone(), self.pkg_json_resolver.clone(), self.sys.clone(), diff --git a/cli/main.rs b/cli/main.rs index 8501a3487f..d4e9f28fb9 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -134,7 +134,7 @@ async fn run_subcommand(flags: Arc) -> Result { tools::check::check(flags, check_flags).await }), DenoSubcommand::Clean => spawn_subcommand(async move { - tools::clean::clean() + tools::clean::clean(flags) }), DenoSubcommand::Compile(compile_flags) => spawn_subcommand(async { tools::compile::compile(flags, compile_flags).await diff --git a/cli/module_loader.rs b/cli/module_loader.rs index 279fc6422e..70900abc69 100644 --- a/cli/module_loader.rs +++ b/cli/module_loader.rs @@ -49,10 +49,10 @@ use deno_runtime::code_cache; use deno_runtime::deno_node::create_host_defined_options; use deno_runtime::deno_node::ops::require::UnableToGetCwdError; use deno_runtime::deno_node::NodeRequireLoader; -use deno_runtime::deno_node::RealIsBuiltInNodeModuleChecker; use deno_runtime::deno_permissions::PermissionsContainer; use deno_semver::npm::NpmPackageReqReference; use node_resolver::errors::ClosestPkgJsonError; +use node_resolver::DenoIsBuiltInNodeModuleChecker; use node_resolver::InNpmPackageChecker; use node_resolver::NodeResolutionKind; use node_resolver::ResolutionMode; @@ -91,7 +91,7 @@ use crate::util::text_encoding::source_map_from_code; pub type CliNpmModuleLoader = deno_lib::loader::NpmModuleLoader< CliCjsCodeAnalyzer, DenoInNpmPackageChecker, - RealIsBuiltInNodeModuleChecker, + DenoIsBuiltInNodeModuleChecker, CliNpmResolver, CliSys, >; diff --git a/cli/node.rs b/cli/node.rs index 90fd5645c7..2e87035cae 100644 --- a/cli/node.rs +++ b/cli/node.rs @@ -9,11 +9,11 @@ use deno_error::JsErrorBox; use deno_graph::ParsedSourceStore; use deno_resolver::npm::DenoInNpmPackageChecker; use deno_runtime::deno_fs; -use deno_runtime::deno_node::RealIsBuiltInNodeModuleChecker; use node_resolver::analyze::CjsAnalysis as ExtNodeCjsAnalysis; use node_resolver::analyze::CjsAnalysisExports; use node_resolver::analyze::CjsCodeAnalyzer; use node_resolver::analyze::NodeCodeTranslator; +use node_resolver::DenoIsBuiltInNodeModuleChecker; use serde::Deserialize; use serde::Serialize; @@ -27,7 +27,7 @@ use crate::sys::CliSys; pub type CliNodeCodeTranslator = NodeCodeTranslator< CliCjsCodeAnalyzer, DenoInNpmPackageChecker, - RealIsBuiltInNodeModuleChecker, + DenoIsBuiltInNodeModuleChecker, CliNpmResolver, CliSys, >; diff --git a/cli/resolver.rs b/cli/resolver.rs index 35ae25da9e..5dcdadb7ff 100644 --- a/cli/resolver.rs +++ b/cli/resolver.rs @@ -18,8 +18,8 @@ use deno_resolver::sloppy_imports::SloppyImportsCachedFs; use deno_resolver::sloppy_imports::SloppyImportsResolver; use deno_runtime::colors; use deno_runtime::deno_node::is_builtin_node_module; -use deno_runtime::deno_node::RealIsBuiltInNodeModuleChecker; use deno_semver::package::PackageReq; +use node_resolver::DenoIsBuiltInNodeModuleChecker; use node_resolver::NodeResolutionKind; use node_resolver::ResolutionMode; @@ -40,14 +40,14 @@ pub type CliSloppyImportsResolver = SloppyImportsResolver; pub type CliDenoResolver = deno_resolver::DenoResolver< DenoInNpmPackageChecker, - RealIsBuiltInNodeModuleChecker, + DenoIsBuiltInNodeModuleChecker, CliNpmResolver, CliSloppyImportsCachedFs, CliSys, >; pub type CliNpmReqResolver = deno_resolver::npm::NpmReqResolver< DenoInNpmPackageChecker, - RealIsBuiltInNodeModuleChecker, + DenoIsBuiltInNodeModuleChecker, CliNpmResolver, CliSys, >; diff --git a/cli/rt/Cargo.toml b/cli/rt/Cargo.toml index 71d9586137..f31af40f84 100644 --- a/cli/rt/Cargo.toml +++ b/cli/rt/Cargo.toml @@ -26,14 +26,14 @@ deno_runtime = { workspace = true, features = ["include_js_files_for_snapshottin deno_core = { workspace = true, features = ["include_js_files_for_snapshotting"] } [dependencies] -deno_cache_dir.workspace = true -deno_config.workspace = true +deno_cache_dir = { workspace = true, features = ["sync"] } +deno_config = { workspace = true, features = ["sync", "workspace"] } deno_core = { workspace = true, features = ["include_js_files_for_snapshotting"] } deno_error.workspace = true deno_lib.workspace = true deno_media_type = { workspace = true, features = ["data_url", "decoding"] } deno_npm.workspace = true -deno_package_json.workspace = true +deno_package_json = { workspace = true, features = ["sync"] } deno_path_util.workspace = true deno_resolver = { workspace = true, features = ["sync"] } deno_runtime = { workspace = true, features = ["include_js_files_for_snapshotting"] } diff --git a/cli/rt/node.rs b/cli/rt/node.rs index ef4f99cc8a..5d2ba5c4e8 100644 --- a/cli/rt/node.rs +++ b/cli/rt/node.rs @@ -11,10 +11,10 @@ use deno_media_type::MediaType; use deno_resolver::npm::DenoInNpmPackageChecker; use deno_resolver::npm::NpmReqResolver; use deno_runtime::deno_fs::FileSystem; -use deno_runtime::deno_node::RealIsBuiltInNodeModuleChecker; use node_resolver::analyze::CjsAnalysis; use node_resolver::analyze::CjsAnalysisExports; use node_resolver::analyze::NodeCodeTranslator; +use node_resolver::DenoIsBuiltInNodeModuleChecker; use crate::binary::StandaloneModules; use crate::file_system::DenoRtSys; @@ -25,14 +25,14 @@ pub type DenoRtNpmResolver = deno_resolver::npm::NpmResolver; pub type DenoRtNpmModuleLoader = NpmModuleLoader< CjsCodeAnalyzer, DenoInNpmPackageChecker, - RealIsBuiltInNodeModuleChecker, + DenoIsBuiltInNodeModuleChecker, DenoRtNpmResolver, DenoRtSys, >; pub type DenoRtNodeCodeTranslator = NodeCodeTranslator< CjsCodeAnalyzer, DenoInNpmPackageChecker, - RealIsBuiltInNodeModuleChecker, + DenoIsBuiltInNodeModuleChecker, DenoRtNpmResolver, DenoRtSys, >; @@ -43,7 +43,7 @@ pub type DenoRtNodeResolver = deno_runtime::deno_node::NodeResolver< >; pub type DenoRtNpmReqResolver = NpmReqResolver< DenoInNpmPackageChecker, - RealIsBuiltInNodeModuleChecker, + DenoIsBuiltInNodeModuleChecker, DenoRtNpmResolver, DenoRtSys, >; diff --git a/cli/rt/run.rs b/cli/rt/run.rs index 6f5c05b467..f970777b22 100644 --- a/cli/rt/run.rs +++ b/cli/rt/run.rs @@ -63,7 +63,6 @@ use deno_runtime::code_cache::CodeCache; use deno_runtime::deno_fs::FileSystem; use deno_runtime::deno_node::create_host_defined_options; use deno_runtime::deno_node::NodeRequireLoader; -use deno_runtime::deno_node::RealIsBuiltInNodeModuleChecker; use deno_runtime::deno_permissions::Permissions; use deno_runtime::deno_permissions::PermissionsContainer; use deno_runtime::deno_tls::rustls::RootCertStore; @@ -75,6 +74,7 @@ use deno_runtime::WorkerLogLevel; use deno_semver::npm::NpmPackageReqReference; use node_resolver::analyze::NodeCodeTranslator; use node_resolver::errors::ClosestPkgJsonError; +use node_resolver::DenoIsBuiltInNodeModuleChecker; use node_resolver::NodeResolutionKind; use node_resolver::NodeResolver; use node_resolver::PackageJsonResolver; @@ -760,7 +760,7 @@ pub async fn run( let has_node_modules_dir = npm_resolver.root_node_modules_path().is_some(); let node_resolver = Arc::new(NodeResolver::new( in_npm_pkg_checker.clone(), - RealIsBuiltInNodeModuleChecker, + DenoIsBuiltInNodeModuleChecker, npm_resolver.clone(), pkg_json_resolver.clone(), sys.clone(), diff --git a/cli/tools/clean.rs b/cli/tools/clean.rs index e6f8c1e52b..9e681840db 100644 --- a/cli/tools/clean.rs +++ b/cli/tools/clean.rs @@ -1,14 +1,15 @@ // Copyright 2018-2025 the Deno authors. MIT license. use std::path::Path; +use std::sync::Arc; use deno_core::anyhow::Context; use deno_core::error::AnyError; -use crate::cache::DenoDir; +use crate::args::Flags; use crate::colors; use crate::display; -use crate::sys::CliSys; +use crate::factory::CliFactory; use crate::util::progress_bar::ProgressBar; use crate::util::progress_bar::ProgressBarStyle; use crate::util::progress_bar::ProgressMessagePrompt; @@ -29,8 +30,9 @@ impl CleanState { } } -pub fn clean() -> Result<(), AnyError> { - let deno_dir = DenoDir::new(CliSys::default(), None)?; +pub fn clean(flags: Arc) -> Result<(), AnyError> { + let factory = CliFactory::from_flags(flags); + let deno_dir = factory.deno_dir()?; if deno_dir.root.exists() { let no_of_files = walkdir::WalkDir::new(&deno_dir.root).into_iter().count(); let progress_bar = ProgressBar::new(ProgressBarStyle::ProgressBars); diff --git a/cli/tools/fmt.rs b/cli/tools/fmt.rs index 4619a546fc..f47ab57420 100644 --- a/cli/tools/fmt.rs +++ b/cli/tools/fmt.rs @@ -59,7 +59,8 @@ pub async fn format( fmt_flags: FmtFlags, ) -> Result<(), AnyError> { if fmt_flags.is_stdin() { - let cli_options = CliOptions::from_flags(&CliSys::default(), flags)?; + let factory = CliFactory::from_flags(flags); + let cli_options = factory.cli_options()?; let start_dir = &cli_options.start_dir; let fmt_config = start_dir .to_fmt_config(FilePatterns::new_with_base(start_dir.dir_path()))?; diff --git a/cli/tools/info.rs b/cli/tools/info.rs index 1b2542d427..765156793f 100644 --- a/cli/tools/info.rs +++ b/cli/tools/info.rs @@ -50,7 +50,7 @@ pub async fn info( let npm_resolver = factory.npm_resolver().await?; let maybe_lockfile = cli_options.maybe_lockfile(); let resolver = factory.workspace_resolver().await?.clone(); - let npmrc = cli_options.npmrc(); + let npmrc = factory.npmrc()?; let node_resolver = factory.node_resolver().await?; let cwd_url = @@ -185,7 +185,7 @@ fn print_cache_info( ) -> Result<(), AnyError> { let dir = factory.deno_dir()?; #[allow(deprecated)] - let modules_cache = factory.global_http_cache()?.get_global_cache_location(); + let modules_cache = factory.global_http_cache()?.dir_path(); let npm_cache = factory.deno_dir()?.npm_folder_path(); let typescript_cache = &dir.gen_cache.location; let registry_cache = dir.registries_folder_path(); diff --git a/cli/tools/installer.rs b/cli/tools/installer.rs index 0f14782a48..53429ef893 100644 --- a/cli/tools/installer.rs +++ b/cli/tools/installer.rs @@ -306,9 +306,7 @@ async fn install_local( InstallFlagsLocal::TopLevel => { let factory = CliFactory::from_flags(flags); // surface any errors in the package.json - if let Some(npm_installer) = factory.npm_installer_if_managed()? { - npm_installer.ensure_no_pkg_json_dep_errors()?; - } + factory.npm_installer()?.ensure_no_pkg_json_dep_errors()?; crate::tools::registry::cache_top_level_deps(&factory, None).await?; if let Some(lockfile) = factory.cli_options()?.maybe_lockfile() { @@ -376,7 +374,7 @@ async fn install_global( log::Level::Trace, ); - let npmrc = factory.cli_options().unwrap().npmrc(); + let npmrc = factory.npmrc()?; let deps_file_fetcher = Arc::new(deps_file_fetcher); let jsr_resolver = Arc::new(JsrFetchResolver::new(deps_file_fetcher.clone())); diff --git a/cli/tools/registry/pm.rs b/cli/tools/registry/pm.rs index 0b27b1a3e3..6f02adf8e0 100644 --- a/cli/tools/registry/pm.rs +++ b/cli/tools/registry/pm.rs @@ -423,7 +423,7 @@ pub async fn add( log::Level::Trace, ); - let npmrc = cli_factory.cli_options().unwrap().npmrc(); + let npmrc = cli_factory.npmrc()?; let deps_file_fetcher = Arc::new(deps_file_fetcher); let jsr_resolver = Arc::new(JsrFetchResolver::new(deps_file_fetcher.clone())); diff --git a/cli/tools/registry/pm/cache_deps.rs b/cli/tools/registry/pm/cache_deps.rs index 5683a30cc8..312d1af562 100644 --- a/cli/tools/registry/pm/cache_deps.rs +++ b/cli/tools/registry/pm/cache_deps.rs @@ -19,15 +19,13 @@ pub async fn cache_top_level_deps( factory: &CliFactory, jsr_resolver: Option>, ) -> Result<(), AnyError> { - let npm_installer = factory.npm_installer_if_managed()?; + let npm_installer = factory.npm_installer()?; let cli_options = factory.cli_options()?; - if let Some(npm_installer) = &npm_installer { - npm_installer - .ensure_top_level_package_json_install() - .await?; - if let Some(lockfile) = cli_options.maybe_lockfile() { - lockfile.error_if_changed()?; - } + npm_installer + .ensure_top_level_package_json_install() + .await?; + if let Some(lockfile) = cli_options.maybe_lockfile() { + lockfile.error_if_changed()?; } // cache as many entries in the import map as we can let resolver = factory.workspace_resolver().await?; @@ -141,9 +139,7 @@ pub async fn cache_top_level_deps( maybe_graph_error = graph_builder.graph_roots_valid(graph, &roots); } - if let Some(npm_installer) = &npm_installer { - npm_installer.cache_packages(PackageCaching::All).await?; - } + npm_installer.cache_packages(PackageCaching::All).await?; maybe_graph_error?; diff --git a/cli/tools/registry/pm/outdated.rs b/cli/tools/registry/pm/outdated.rs index 610ad48c1d..1afe7a5034 100644 --- a/cli/tools/registry/pm/outdated.rs +++ b/cli/tools/registry/pm/outdated.rs @@ -194,7 +194,7 @@ pub async fn outdated( let file_fetcher = Arc::new(file_fetcher); let npm_fetch_resolver = Arc::new(NpmFetchResolver::new( file_fetcher.clone(), - cli_options.npmrc().clone(), + factory.npmrc()?.clone(), )); let jsr_fetch_resolver = Arc::new(JsrFetchResolver::new(file_fetcher.clone())); diff --git a/ext/node/lib.rs b/ext/node/lib.rs index a0efc12657..dcb779060d 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -15,6 +15,7 @@ use deno_core::v8; use deno_core::v8::ExternalReference; use deno_error::JsErrorBox; use node_resolver::errors::ClosestPkgJsonError; +use node_resolver::DenoIsBuiltInNodeModuleChecker; use node_resolver::InNpmPackageChecker; use node_resolver::IsBuiltInNodeModuleChecker; use node_resolver::NpmPackageFolderResolver; @@ -25,24 +26,25 @@ extern crate libz_sys as zlib; mod global; pub mod ops; -mod polyfill; pub use deno_package_json::PackageJson; use deno_permissions::PermissionCheckError; pub use node_resolver::PathClean; +pub use node_resolver::DENO_SUPPORTED_BUILTIN_NODE_MODULES as SUPPORTED_BUILTIN_NODE_MODULES; pub use ops::ipc::ChildPipeFd; use ops::vm; pub use ops::vm::create_v8_context; pub use ops::vm::init_global_template; pub use ops::vm::ContextInitMode; pub use ops::vm::VM_CONTEXT_INDEX; -pub use polyfill::is_builtin_node_module; -pub use polyfill::SUPPORTED_BUILTIN_NODE_MODULES; -pub use polyfill::SUPPORTED_BUILTIN_NODE_MODULES_WITH_PREFIX; use crate::global::global_object_middleware; use crate::global::global_template_middleware; +pub fn is_builtin_node_module(module_name: &str) -> bool { + DenoIsBuiltInNodeModuleChecker.is_builtin_node_module(module_name) +} + pub trait NodePermissions { fn check_net_url( &mut self, @@ -813,16 +815,6 @@ deno_core::extension!(deno_node, }, ); -#[derive(Debug)] -pub struct RealIsBuiltInNodeModuleChecker; - -impl IsBuiltInNodeModuleChecker for RealIsBuiltInNodeModuleChecker { - #[inline] - fn is_builtin_node_module(&self, specifier: &str) -> bool { - is_builtin_node_module(specifier) - } -} - pub trait ExtNodeSys: sys_traits::BaseFsCanonicalize + sys_traits::BaseFsMetadata @@ -837,7 +829,7 @@ impl ExtNodeSys for sys_traits::impls::RealSys {} pub type NodeResolver = node_resolver::NodeResolver< TInNpmPackageChecker, - RealIsBuiltInNodeModuleChecker, + DenoIsBuiltInNodeModuleChecker, TNpmPackageFolderResolver, TSys, >; diff --git a/ext/node/polyfill.rs b/ext/node/polyfill.rs deleted file mode 100644 index 6e6e9d09c2..0000000000 --- a/ext/node/polyfill.rs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2018-2025 the Deno authors. MIT license. - -/// e.g. `is_builtin_node_module("assert")` -pub fn is_builtin_node_module(module_name: &str) -> bool { - SUPPORTED_BUILTIN_NODE_MODULES - .iter() - .any(|m| *m == module_name) -} - -macro_rules! generate_builtin_node_module_lists { - ($( $module_name:literal ,)+) => { - pub static SUPPORTED_BUILTIN_NODE_MODULES: &[&str] = &[ - $( - $module_name, - )+ - ]; - - pub static SUPPORTED_BUILTIN_NODE_MODULES_WITH_PREFIX: &[&str] = &[ - $( - concat!("node:", $module_name), - )+ - ]; - }; -} - -// NOTE(bartlomieju): keep this list in sync with `ext/node/polyfills/01_require.js` -generate_builtin_node_module_lists! { - "_http_agent", - "_http_common", - "_http_outgoing", - "_http_server", - "_stream_duplex", - "_stream_passthrough", - "_stream_readable", - "_stream_transform", - "_stream_writable", - "_tls_common", - "_tls_wrap", - "assert", - "assert/strict", - "async_hooks", - "buffer", - "child_process", - "cluster", - "console", - "constants", - "crypto", - "dgram", - "diagnostics_channel", - "dns", - "dns/promises", - "domain", - "events", - "fs", - "fs/promises", - "http", - "http2", - "https", - "inspector", - "inspector/promises", - "module", - "net", - "os", - "path", - "path/posix", - "path/win32", - "perf_hooks", - "process", - "punycode", - "querystring", - "readline", - "readline/promises", - "repl", - "stream", - "stream/consumers", - "stream/promises", - "stream/web", - "string_decoder", - "sys", - "test", - "timers", - "timers/promises", - "tls", - "tty", - "url", - "util", - "util/types", - "v8", - "vm", - "wasi", - "worker_threads", - "zlib", -} - -#[test] -fn test_builtins_are_sorted() { - let mut builtins_list = SUPPORTED_BUILTIN_NODE_MODULES.to_vec(); - builtins_list.sort(); - assert_eq!(SUPPORTED_BUILTIN_NODE_MODULES, builtins_list); -} diff --git a/resolvers/deno/Cargo.toml b/resolvers/deno/Cargo.toml index bb2827afe1..6ec8a4621a 100644 --- a/resolvers/deno/Cargo.toml +++ b/resolvers/deno/Cargo.toml @@ -14,10 +14,11 @@ description = "Deno resolution algorithm" path = "lib.rs" [features] -sync = ["dashmap", "deno_package_json/sync", "node_resolver/sync"] +sync = ["dashmap", "deno_package_json/sync", "node_resolver/sync", "deno_config/sync", "deno_cache_dir/sync"] [dependencies] anyhow.workspace = true +async-once-cell.workspace = true async-trait.workspace = true base32.workspace = true boxed_error.workspace = true @@ -30,8 +31,11 @@ deno_npm.workspace = true deno_package_json.workspace = true deno_path_util.workspace = true deno_semver.workspace = true +deno_terminal.workspace = true +futures.workspace = true log.workspace = true node_resolver.workspace = true +once_cell.workspace = true parking_lot.workspace = true sys_traits.workspace = true thiserror.workspace = true diff --git a/resolvers/deno/factory.rs b/resolvers/deno/factory.rs new file mode 100644 index 0000000000..dea0ceab52 --- /dev/null +++ b/resolvers/deno/factory.rs @@ -0,0 +1,893 @@ +// Copyright 2018-2025 the Deno authors. MIT license. + +use std::borrow::Cow; +use std::path::Path; +use std::path::PathBuf; + +use boxed_error::Boxed; +use deno_cache_dir::npm::NpmCacheDir; +use deno_cache_dir::DenoDirResolutionError; +use deno_cache_dir::GlobalHttpCacheRc; +use deno_cache_dir::HttpCacheRc; +use deno_cache_dir::LocalHttpCache; +use deno_config::deno_json::NodeModulesDirMode; +use deno_config::workspace::FolderConfigs; +use deno_config::workspace::PackageJsonDepResolution; +use deno_config::workspace::VendorEnablement; +use deno_config::workspace::WorkspaceDirectory; +use deno_config::workspace::WorkspaceDirectoryEmptyOptions; +use deno_config::workspace::WorkspaceDiscoverError; +use deno_config::workspace::WorkspaceDiscoverOptions; +use deno_config::workspace::WorkspaceDiscoverStart; +use deno_npm::NpmSystemInfo; +use deno_path_util::fs::canonicalize_path_maybe_not_exists; +use deno_path_util::normalize_path; +use futures::future::FutureExt; +use node_resolver::ConditionsFromResolutionMode; +use node_resolver::DenoIsBuiltInNodeModuleChecker; +use node_resolver::NodeResolver; +use node_resolver::NodeResolverRc; +use node_resolver::PackageJsonResolver; +use node_resolver::PackageJsonResolverRc; +use sys_traits::EnvCacheDir; +use sys_traits::EnvCurrentDir; +use sys_traits::EnvHomeDir; +use sys_traits::EnvVar; +use sys_traits::FsCanonicalize; +use sys_traits::FsCreateDirAll; +use sys_traits::FsMetadata; +use sys_traits::FsOpen; +use sys_traits::FsRead; +use sys_traits::FsReadDir; +use sys_traits::FsRemoveFile; +use sys_traits::FsRename; +use sys_traits::SystemRandom; +use sys_traits::SystemTimeNow; +use sys_traits::ThreadSleep; +use thiserror::Error; + +use crate::npm::managed::ManagedInNpmPkgCheckerCreateOptions; +use crate::npm::managed::ManagedNpmResolverCreateOptions; +use crate::npm::managed::NpmResolutionCellRc; +use crate::npm::ByonmNpmResolverCreateOptions; +use crate::npm::CreateInNpmPkgCheckerOptions; +use crate::npm::DenoInNpmPackageChecker; +use crate::npm::NpmReqResolver; +use crate::npm::NpmReqResolverOptions; +use crate::npm::NpmReqResolverRc; +use crate::npm::NpmResolver; +use crate::npm::NpmResolverCreateOptions; +use crate::npmrc::discover_npmrc_from_workspace; +use crate::npmrc::NpmRcDiscoverError; +use crate::npmrc::ResolvedNpmRcRc; +use crate::sloppy_imports::SloppyImportsCachedFs; +use crate::sloppy_imports::SloppyImportsResolver; +use crate::sloppy_imports::SloppyImportsResolverRc; +use crate::sync::new_rc; +use crate::sync::MaybeSend; +use crate::sync::MaybeSync; +use crate::DefaultDenoResolverRc; +use crate::DenoResolver; +use crate::DenoResolverOptions; +use crate::NodeAndNpmReqResolver; +use crate::NpmCacheDirRc; +use crate::WorkspaceResolverRc; + +// todo(https://github.com/rust-lang/rust/issues/109737): remove once_cell after get_or_try_init is stabilized +#[cfg(feature = "sync")] +type Deferred = once_cell::sync::OnceCell; +#[cfg(not(feature = "sync"))] +type Deferred = once_cell::unsync::OnceCell; + +#[allow(clippy::disallowed_types)] +type WorkspaceDirectoryRc = crate::sync::MaybeArc; + +#[derive(Debug, Boxed)] +pub struct HttpCacheCreateError(pub Box); + +#[derive(Debug, Error)] +pub enum HttpCacheCreateErrorKind { + #[error(transparent)] + DenoDirResolution(#[from] DenoDirResolutionError), + #[error(transparent)] + WorkspaceDiscover(#[from] WorkspaceDiscoverError), +} + +#[derive(Debug, Boxed)] +pub struct NpmCacheDirCreateError(pub Box); + +#[derive(Debug, Error)] +pub enum NpmCacheDirCreateErrorKind { + #[error(transparent)] + DenoDirResolution(#[from] DenoDirResolutionError), + #[error(transparent)] + NpmRcCreate(#[from] NpmRcCreateError), +} + +#[derive(Debug, Boxed)] +pub struct NpmRcCreateError(pub Box); + +#[derive(Debug, Error)] +pub enum NpmRcCreateErrorKind { + #[error(transparent)] + WorkspaceDiscover(#[from] WorkspaceDiscoverError), + #[error(transparent)] + NpmRcDiscover(#[from] NpmRcDiscoverError), +} + +#[derive(Debug, Default)] +pub enum ConfigDiscoveryOption { + #[default] + DiscoverCwd, + Discover { + start_paths: Vec, + }, + Path(PathBuf), + Disabled, +} + +#[async_trait::async_trait(?Send)] +pub trait SpecifiedImportMapProvider: + std::fmt::Debug + MaybeSend + MaybeSync +{ + async fn get( + &self, + ) -> Result, anyhow::Error>; +} + +#[derive(Debug, Clone)] +pub struct DenoDirPathProviderOptions { + pub maybe_custom_root: Option, +} + +#[allow(clippy::disallowed_types)] +pub type DenoDirPathProviderRc = + crate::sync::MaybeArc>; + +/// Lazily creates the deno dir which might be useful in scenarios +/// where functionality wants to continue if the DENO_DIR can't be created. +#[derive(Debug)] +pub struct DenoDirPathProvider< + TSys: EnvCacheDir + EnvHomeDir + EnvVar + EnvCurrentDir, +> { + sys: TSys, + options: DenoDirPathProviderOptions, + deno_dir: Deferred, +} + +impl + DenoDirPathProvider +{ + pub fn new(sys: TSys, options: DenoDirPathProviderOptions) -> Self { + Self { + sys, + options, + deno_dir: Default::default(), + } + } + + pub fn get_or_create(&self) -> Result<&PathBuf, DenoDirResolutionError> { + self.deno_dir.get_or_try_init(|| { + deno_cache_dir::resolve_deno_dir( + &self.sys, + self.options.maybe_custom_root.clone(), + ) + }) + } +} + +#[derive(Debug)] +pub struct NpmProcessStateOptions { + pub node_modules_dir: Option>, + pub is_byonm: bool, +} + +#[derive(Debug, Default)] +pub struct WorkspaceFactoryOptions< + TSys: EnvCacheDir + EnvHomeDir + EnvVar + EnvCurrentDir + FsCanonicalize, +> { + pub additional_config_file_names: &'static [&'static str], + pub config_discovery: ConfigDiscoveryOption, + pub deno_dir_path_provider: Option>, + pub is_package_manager_subcommand: bool, + pub node_modules_dir: Option, + pub no_npm: bool, + /// The process sate if using ext/node and the current process was "forked". + /// This value is found at `deno_lib::args::NPM_PROCESS_STATE` + /// but in most scenarios this can probably just be `None`. + pub npm_process_state: Option, + pub vendor: Option, +} + +#[allow(clippy::disallowed_types)] +pub type WorkspaceFactoryRc = + crate::sync::MaybeArc>; + +pub struct WorkspaceFactory< + TSys: EnvCacheDir + + EnvHomeDir + + EnvVar + + EnvCurrentDir + + FsCanonicalize + + FsCreateDirAll + + FsMetadata + + FsOpen + + FsRead + + FsReadDir + + FsRemoveFile + + FsRename + + SystemRandom + + SystemTimeNow + + ThreadSleep + + std::fmt::Debug + + MaybeSend + + MaybeSync + + Clone + + 'static, +> { + sys: TSys, + deno_dir_path: DenoDirPathProviderRc, + global_http_cache: Deferred>, + http_cache: Deferred, + node_modules_dir_path: Deferred>, + npm_cache_dir: Deferred, + npmrc: Deferred, + node_modules_dir_mode: Deferred, + workspace_directory: Deferred, + initial_cwd: PathBuf, + options: WorkspaceFactoryOptions, +} + +impl< + TSys: EnvCacheDir + + EnvHomeDir + + EnvVar + + EnvCurrentDir + + FsCanonicalize + + FsCreateDirAll + + FsMetadata + + FsOpen + + FsRead + + FsReadDir + + FsRemoveFile + + FsRename + + SystemRandom + + SystemTimeNow + + ThreadSleep + + std::fmt::Debug + + MaybeSend + + MaybeSync + + Clone + + 'static, + > WorkspaceFactory +{ + pub fn new( + sys: TSys, + initial_cwd: PathBuf, + mut options: WorkspaceFactoryOptions, + ) -> Self { + Self { + deno_dir_path: options.deno_dir_path_provider.take().unwrap_or_else( + || { + new_rc(DenoDirPathProvider::new( + sys.clone(), + DenoDirPathProviderOptions { + maybe_custom_root: None, + }, + )) + }, + ), + sys, + global_http_cache: Default::default(), + http_cache: Default::default(), + node_modules_dir_path: Default::default(), + npm_cache_dir: Default::default(), + npmrc: Default::default(), + node_modules_dir_mode: Default::default(), + workspace_directory: Default::default(), + initial_cwd, + options, + } + } + + pub fn set_workspace_directory( + &mut self, + workspace_directory: WorkspaceDirectoryRc, + ) { + self.workspace_directory = Deferred::from(workspace_directory); + } + + pub fn initial_cwd(&self) -> &PathBuf { + &self.initial_cwd + } + + pub fn no_npm(&self) -> bool { + self.options.no_npm + } + + pub fn node_modules_dir_mode( + &self, + ) -> Result { + self + .node_modules_dir_mode + .get_or_try_init(|| { + let raw_resolve = || -> Result<_, anyhow::Error> { + if let Some(process_state) = &self.options.npm_process_state { + if process_state.is_byonm { + return Ok(NodeModulesDirMode::Manual); + } + if process_state.node_modules_dir.is_some() { + return Ok(NodeModulesDirMode::Auto); + } else { + return Ok(NodeModulesDirMode::None); + } + } + if let Some(flag) = self.options.node_modules_dir { + return Ok(flag); + } + let workspace = &self.workspace_directory()?.workspace; + if let Some(mode) = workspace.node_modules_dir()? { + return Ok(mode); + } + + let workspace = &self.workspace_directory()?.workspace; + + if let Some(pkg_json) = workspace.root_pkg_json() { + if let Ok(deno_dir) = self.deno_dir_path() { + // `deno_dir` can be symlink in macOS or on the CI + if let Ok(deno_dir) = + canonicalize_path_maybe_not_exists(&self.sys, deno_dir) + { + if pkg_json.path.starts_with(deno_dir) { + // if the package.json is in deno_dir, then do not use node_modules + // next to it as local node_modules dir + return Ok(NodeModulesDirMode::None); + } + } + } + + Ok(NodeModulesDirMode::Manual) + } else if workspace.vendor_dir_path().is_some() { + Ok(NodeModulesDirMode::Auto) + } else { + // use the global cache + Ok(NodeModulesDirMode::None) + } + }; + + let mode = raw_resolve()?; + if mode == NodeModulesDirMode::Manual + && self.options.is_package_manager_subcommand + { + // force using the managed resolver for package management + // sub commands so that it sets up the node_modules directory + Ok(NodeModulesDirMode::Auto) + } else { + Ok(mode) + } + }) + .copied() + } + + /// Resolves the path to use for a local node_modules folder. + pub fn node_modules_dir_path(&self) -> Result, anyhow::Error> { + fn resolve_from_root(root_folder: &FolderConfigs, cwd: &Path) -> PathBuf { + root_folder + .deno_json + .as_ref() + .map(|c| Cow::Owned(c.dir_path())) + .or_else(|| { + root_folder + .pkg_json + .as_ref() + .map(|c| Cow::Borrowed(c.dir_path())) + }) + .unwrap_or(Cow::Borrowed(cwd)) + .join("node_modules") + } + + self + .node_modules_dir_path + .get_or_try_init(|| { + if let Some(process_state) = &self.options.npm_process_state { + return Ok( + process_state + .node_modules_dir + .as_ref() + .map(|p| PathBuf::from(p.as_ref())), + ); + } + + let mode = self.node_modules_dir_mode()?; + let workspace = &self.workspace_directory()?.workspace; + let root_folder = workspace.root_folder_configs(); + if !mode.uses_node_modules_dir() { + return Ok(None); + } + + let node_modules_dir = + resolve_from_root(root_folder, &self.initial_cwd); + + Ok(Some(canonicalize_path_maybe_not_exists( + &self.sys, + &node_modules_dir, + )?)) + }) + .map(|p| p.as_deref()) + } + + pub fn deno_dir_path(&self) -> Result<&PathBuf, DenoDirResolutionError> { + self.deno_dir_path.get_or_create() + } + + pub fn global_http_cache( + &self, + ) -> Result<&GlobalHttpCacheRc, DenoDirResolutionError> { + self.global_http_cache.get_or_try_init(|| { + let global_cache_dir = self.deno_dir_path()?.join("remote"); + let global_http_cache = new_rc(deno_cache_dir::GlobalHttpCache::new( + self.sys.clone(), + global_cache_dir, + )); + Ok(global_http_cache) + }) + } + + pub fn http_cache(&self) -> Result<&HttpCacheRc, HttpCacheCreateError> { + self.http_cache.get_or_try_init(|| { + let global_cache = self.global_http_cache()?.clone(); + match self.workspace_directory()?.workspace.vendor_dir_path() { + Some(local_path) => { + let local_cache = LocalHttpCache::new( + local_path.clone(), + global_cache, + deno_cache_dir::GlobalToLocalCopy::Allow, + ); + Ok(new_rc(local_cache)) + } + None => Ok(global_cache), + } + }) + } + + pub fn npm_cache_dir( + &self, + ) -> Result<&NpmCacheDirRc, NpmCacheDirCreateError> { + self.npm_cache_dir.get_or_try_init(|| { + let npm_cache_dir = self.deno_dir_path()?.join("npm"); + Ok(new_rc(NpmCacheDir::new( + &self.sys, + npm_cache_dir, + self.npmrc()?.get_all_known_registries_urls(), + ))) + }) + } + + pub fn npmrc(&self) -> Result<&ResolvedNpmRcRc, NpmRcCreateError> { + self.npmrc.get_or_try_init(|| { + let (npmrc, _) = discover_npmrc_from_workspace( + &self.sys, + &self.workspace_directory()?.workspace, + )?; + Ok(new_rc(npmrc)) + }) + } + + pub fn sys(&self) -> &TSys { + &self.sys + } + + pub fn workspace_directory( + &self, + ) -> Result<&WorkspaceDirectoryRc, WorkspaceDiscoverError> { + self.workspace_directory.get_or_try_init(|| { + let maybe_vendor_override = self.options.vendor.map(|v| match v { + true => VendorEnablement::Enable { + cwd: &self.initial_cwd, + }, + false => VendorEnablement::Disable, + }); + let resolve_workspace_discover_options = || { + let discover_pkg_json = !self.options.no_npm + && !self.has_flag_env_var("DENO_NO_PACKAGE_JSON"); + if !discover_pkg_json { + log::debug!("package.json auto-discovery is disabled"); + } + WorkspaceDiscoverOptions { + deno_json_cache: None, + pkg_json_cache: Some(&node_resolver::PackageJsonThreadLocalCache), + workspace_cache: None, + additional_config_file_names: self + .options + .additional_config_file_names, + discover_pkg_json, + maybe_vendor_override, + } + }; + let resolve_empty_options = || WorkspaceDirectoryEmptyOptions { + root_dir: new_rc( + deno_path_util::url_from_directory_path(&self.initial_cwd).unwrap(), + ), + use_vendor_dir: maybe_vendor_override + .unwrap_or(VendorEnablement::Disable), + }; + + let dir = match &self.options.config_discovery { + ConfigDiscoveryOption::DiscoverCwd => WorkspaceDirectory::discover( + &self.sys, + WorkspaceDiscoverStart::Paths(&[self.initial_cwd.clone()]), + &resolve_workspace_discover_options(), + )?, + ConfigDiscoveryOption::Discover { start_paths } => { + WorkspaceDirectory::discover( + &self.sys, + WorkspaceDiscoverStart::Paths(start_paths), + &resolve_workspace_discover_options(), + )? + } + ConfigDiscoveryOption::Path(path) => { + let config_path = normalize_path(self.initial_cwd.join(path)); + WorkspaceDirectory::discover( + &self.sys, + WorkspaceDiscoverStart::ConfigFile(&config_path), + &resolve_workspace_discover_options(), + )? + } + ConfigDiscoveryOption::Disabled => { + WorkspaceDirectory::empty(resolve_empty_options()) + } + }; + Ok(new_rc(dir)) + }) + } + + fn has_flag_env_var(&self, name: &str) -> bool { + let value = self.sys.env_var_os(name); + match value { + Some(value) => value == "1", + None => false, + } + } +} + +#[derive(Debug, Default)] +pub struct ResolverFactoryOptions { + pub conditions_from_resolution_mode: ConditionsFromResolutionMode, + pub no_sloppy_imports_cache: bool, + pub npm_system_info: NpmSystemInfo, + pub package_json_dep_resolution: Option, + pub specified_import_map: Option>, + pub unstable_sloppy_imports: bool, +} + +pub struct ResolverFactory< + TSys: EnvCacheDir + + EnvCurrentDir + + EnvHomeDir + + EnvVar + + FsCanonicalize + + FsCreateDirAll + + FsMetadata + + FsOpen + + FsRead + + FsReadDir + + FsRemoveFile + + FsRename + + ThreadSleep + + SystemRandom + + SystemTimeNow + + std::fmt::Debug + + MaybeSend + + MaybeSync + + Clone + + 'static, +> { + options: ResolverFactoryOptions, + deno_resolver: async_once_cell::OnceCell>, + in_npm_package_checker: Deferred, + node_resolver: Deferred< + NodeResolverRc< + DenoInNpmPackageChecker, + DenoIsBuiltInNodeModuleChecker, + NpmResolver, + TSys, + >, + >, + npm_req_resolver: Deferred< + NpmReqResolverRc< + DenoInNpmPackageChecker, + DenoIsBuiltInNodeModuleChecker, + NpmResolver, + TSys, + >, + >, + npm_resolver: Deferred>, + npm_resolution: NpmResolutionCellRc, + pkg_json_resolver: Deferred>, + sloppy_imports_resolver: + Deferred>>>, + workspace_factory: WorkspaceFactoryRc, + workspace_resolver: async_once_cell::OnceCell, +} + +impl< + TSys: EnvCacheDir + + EnvCurrentDir + + EnvHomeDir + + EnvVar + + FsCanonicalize + + FsCreateDirAll + + FsMetadata + + FsOpen + + FsRead + + FsReadDir + + FsRemoveFile + + FsRename + + ThreadSleep + + SystemRandom + + SystemTimeNow + + std::fmt::Debug + + MaybeSend + + MaybeSync + + Clone + + 'static, + > ResolverFactory +{ + pub fn new( + workspace_factory: WorkspaceFactoryRc, + options: ResolverFactoryOptions, + ) -> Self { + Self { + options, + deno_resolver: Default::default(), + in_npm_package_checker: Default::default(), + node_resolver: Default::default(), + npm_req_resolver: Default::default(), + npm_resolution: Default::default(), + npm_resolver: Default::default(), + pkg_json_resolver: Default::default(), + sloppy_imports_resolver: Default::default(), + workspace_factory, + workspace_resolver: Default::default(), + } + } + + pub async fn deno_resolver( + &self, + ) -> Result<&DefaultDenoResolverRc, anyhow::Error> { + self + .deno_resolver + .get_or_try_init( + async { + Ok(new_rc(DenoResolver::new(DenoResolverOptions { + in_npm_pkg_checker: self.in_npm_package_checker()?.clone(), + node_and_req_resolver: if self.workspace_factory.no_npm() { + None + } else { + Some(NodeAndNpmReqResolver { + node_resolver: self.node_resolver()?.clone(), + npm_req_resolver: self.npm_req_resolver()?.clone(), + }) + }, + is_byonm: self.use_byonm()?, + maybe_vendor_dir: self + .workspace_factory + .workspace_directory()? + .workspace + .vendor_dir_path(), + sloppy_imports_resolver: self.sloppy_imports_resolver()?.cloned(), + workspace_resolver: self.workspace_resolver().await?.clone(), + }))) + } + // boxed to prevent the futures getting big and exploding the stack + .boxed_local(), + ) + .await + } + + pub fn in_npm_package_checker( + &self, + ) -> Result<&DenoInNpmPackageChecker, anyhow::Error> { + self.in_npm_package_checker.get_or_try_init(|| { + let options = match self.use_byonm()? { + true => CreateInNpmPkgCheckerOptions::Byonm, + false => CreateInNpmPkgCheckerOptions::Managed( + ManagedInNpmPkgCheckerCreateOptions { + root_cache_dir_url: self + .workspace_factory + .npm_cache_dir()? + .root_dir_url(), + maybe_node_modules_path: self + .workspace_factory + .node_modules_dir_path()?, + }, + ), + }; + Ok(DenoInNpmPackageChecker::new(options)) + }) + } + + pub fn node_resolver( + &self, + ) -> Result< + &NodeResolverRc< + DenoInNpmPackageChecker, + DenoIsBuiltInNodeModuleChecker, + NpmResolver, + TSys, + >, + anyhow::Error, + > { + self.node_resolver.get_or_try_init(|| { + Ok(new_rc(NodeResolver::new( + self.in_npm_package_checker()?.clone(), + DenoIsBuiltInNodeModuleChecker, + self.npm_resolver()?.clone(), + self.pkg_json_resolver().clone(), + self.workspace_factory.sys.clone(), + self.options.conditions_from_resolution_mode.clone(), + ))) + }) + } + + pub fn npm_resolution(&self) -> &NpmResolutionCellRc { + &self.npm_resolution + } + + pub fn npm_req_resolver( + &self, + ) -> Result< + &NpmReqResolverRc< + DenoInNpmPackageChecker, + DenoIsBuiltInNodeModuleChecker, + NpmResolver, + TSys, + >, + anyhow::Error, + > { + self.npm_req_resolver.get_or_try_init(|| { + Ok(new_rc(NpmReqResolver::new(NpmReqResolverOptions { + in_npm_pkg_checker: self.in_npm_package_checker()?.clone(), + node_resolver: self.node_resolver()?.clone(), + npm_resolver: self.npm_resolver()?.clone(), + sys: self.workspace_factory.sys.clone(), + }))) + }) + } + + pub fn npm_resolver(&self) -> Result<&NpmResolver, anyhow::Error> { + self.npm_resolver.get_or_try_init(|| { + Ok(NpmResolver::::new::(if self.use_byonm()? { + NpmResolverCreateOptions::Byonm(ByonmNpmResolverCreateOptions { + sys: self.workspace_factory.sys.clone(), + pkg_json_resolver: self.pkg_json_resolver().clone(), + root_node_modules_dir: Some( + match self.workspace_factory.node_modules_dir_path()? { + Some(node_modules_path) => node_modules_path.to_path_buf(), + // path needs to be canonicalized for node resolution + // (node_modules_dir_path above is already canonicalized) + None => canonicalize_path_maybe_not_exists( + &self.workspace_factory.sys, + self.workspace_factory.initial_cwd(), + )? + .join("node_modules"), + }, + ), + }) + } else { + NpmResolverCreateOptions::Managed(ManagedNpmResolverCreateOptions { + sys: self.workspace_factory.sys.clone(), + npm_resolution: self.npm_resolution().clone(), + npm_cache_dir: self.workspace_factory.npm_cache_dir()?.clone(), + maybe_node_modules_path: self + .workspace_factory + .node_modules_dir_path()? + .map(|p| p.to_path_buf()), + npm_system_info: self.options.npm_system_info.clone(), + npmrc: self.workspace_factory.npmrc()?.clone(), + }) + })) + }) + } + + pub fn pkg_json_resolver(&self) -> &PackageJsonResolverRc { + self.pkg_json_resolver.get_or_init(|| { + new_rc(PackageJsonResolver::new(self.workspace_factory.sys.clone())) + }) + } + + pub fn sloppy_imports_resolver( + &self, + ) -> Result< + Option<&SloppyImportsResolverRc>>, + anyhow::Error, + > { + self + .sloppy_imports_resolver + .get_or_try_init(|| { + let enabled = self.options.unstable_sloppy_imports + || self + .workspace_factory + .workspace_directory()? + .workspace + .has_unstable("sloppy-imports"); + if enabled { + Ok(Some(new_rc(SloppyImportsResolver::new( + if self.options.no_sloppy_imports_cache { + SloppyImportsCachedFs::new_without_stat_cache( + self.workspace_factory.sys.clone(), + ) + } else { + SloppyImportsCachedFs::new(self.workspace_factory.sys.clone()) + }, + )))) + } else { + Ok(None) + } + }) + .map(|v| v.as_ref()) + } + + pub async fn workspace_resolver( + &self, + ) -> Result<&WorkspaceResolverRc, anyhow::Error> { + self + .workspace_resolver + .get_or_try_init( + async { + let directory = self.workspace_factory.workspace_directory()?; + let workspace = &directory.workspace; + let specified_import_map = match &self.options.specified_import_map { + Some(import_map) => import_map.get().await?, + None => None, + }; + let options = deno_config::workspace::CreateResolverOptions { + pkg_json_dep_resolution: match self + .options + .package_json_dep_resolution + { + Some(value) => value, + None => { + match self.workspace_factory.node_modules_dir_mode()? { + NodeModulesDirMode::Manual => { + PackageJsonDepResolution::Disabled + } + NodeModulesDirMode::Auto | NodeModulesDirMode::None => { + // todo(dsherret): should this be disabled for auto? + PackageJsonDepResolution::Enabled + } + } + } + }, + specified_import_map, + }; + let resolver = + workspace.create_resolver(&self.workspace_factory.sys, options)?; + if !resolver.diagnostics().is_empty() { + // todo(dsherret): do not log this in this crate... that should be + // a CLI responsibility + log::warn!( + "Import map diagnostics:\n{}", + resolver + .diagnostics() + .iter() + .map(|d| format!(" - {d}")) + .collect::>() + .join("\n") + ); + } + Ok(new_rc(resolver)) + } + // boxed to prevent the futures getting big and exploding the stack + .boxed_local(), + ) + .await + } + + pub fn use_byonm(&self) -> Result { + Ok( + self.workspace_factory.node_modules_dir_mode()? + == NodeModulesDirMode::Manual, + ) + } +} diff --git a/resolvers/deno/lib.rs b/resolvers/deno/lib.rs index 454c7f12e2..49f741f95d 100644 --- a/resolvers/deno/lib.rs +++ b/resolvers/deno/lib.rs @@ -13,7 +13,6 @@ use deno_config::workspace::MappedResolutionError; use deno_config::workspace::WorkspaceResolvePkgJsonFolderError; use deno_config::workspace::WorkspaceResolver; use deno_error::JsError; -use deno_npm::npm_rc::ResolvedNpmRc; use deno_package_json::PackageJsonDepValue; use deno_package_json::PackageJsonDepValueParseError; use deno_semver::npm::NpmPackageReqReference; @@ -43,16 +42,15 @@ use thiserror::Error; use url::Url; pub mod cjs; +pub mod factory; pub mod npm; +pub mod npmrc; pub mod sloppy_imports; mod sync; #[allow(clippy::disallowed_types)] pub type WorkspaceResolverRc = crate::sync::MaybeArc; -#[allow(clippy::disallowed_types)] -pub(crate) type ResolvedNpmRcRc = crate::sync::MaybeArc; - #[allow(clippy::disallowed_types)] pub(crate) type NpmCacheDirRc = crate::sync::MaybeArc; @@ -148,6 +146,33 @@ pub struct DenoResolverOptions< pub maybe_vendor_dir: Option<&'a PathBuf>, } +#[allow(clippy::disallowed_types)] +pub type DenoResolverRc< + TInNpmPackageChecker, + TIsBuiltInNodeModuleChecker, + TNpmPackageFolderResolver, + TSloppyImportResolverFs, + TSys, +> = crate::sync::MaybeArc< + DenoResolver< + TInNpmPackageChecker, + TIsBuiltInNodeModuleChecker, + TNpmPackageFolderResolver, + TSloppyImportResolverFs, + TSys, + >, +>; + +/// Helper type for a DenoResolverRc that has the implementations +/// used by the Deno CLI. +pub type DefaultDenoResolverRc = DenoResolverRc< + npm::DenoInNpmPackageChecker, + node_resolver::DenoIsBuiltInNodeModuleChecker, + npm::NpmResolver, + sloppy_imports::SloppyImportsCachedFs, + TSys, +>; + /// A resolver that takes care of resolution, taking into account loaded /// import map, JSX settings. #[derive(Debug)] diff --git a/resolvers/deno/npm/managed/global.rs b/resolvers/deno/npm/managed/global.rs index 271851c79c..4518108f5a 100644 --- a/resolvers/deno/npm/managed/global.rs +++ b/resolvers/deno/npm/managed/global.rs @@ -17,7 +17,7 @@ use url::Url; use super::resolution::NpmResolutionCellRc; use super::NpmCacheDirRc; -use crate::ResolvedNpmRcRc; +use crate::npmrc::ResolvedNpmRcRc; /// Resolves packages from the global npm cache. #[derive(Debug)] diff --git a/resolvers/deno/npm/managed/mod.rs b/resolvers/deno/npm/managed/mod.rs index 291e299bc8..9065db13cd 100644 --- a/resolvers/deno/npm/managed/mod.rs +++ b/resolvers/deno/npm/managed/mod.rs @@ -28,8 +28,8 @@ use self::global::GlobalNpmPackageResolver; use self::local::LocalNpmPackageResolver; pub use self::resolution::NpmResolutionCell; pub use self::resolution::NpmResolutionCellRc; +use crate::npmrc::ResolvedNpmRcRc; use crate::NpmCacheDirRc; -use crate::ResolvedNpmRcRc; #[derive(Debug, thiserror::Error, deno_error::JsError)] pub enum ResolvePkgFolderFromDenoModuleError { @@ -268,6 +268,7 @@ impl InNpmPackageChecker for ManagedInNpmPackageChecker { } } +#[derive(Debug)] pub struct ManagedInNpmPkgCheckerCreateOptions<'a> { pub root_cache_dir_url: &'a Url, pub maybe_node_modules_path: Option<&'a Path>, diff --git a/resolvers/deno/npm/mod.rs b/resolvers/deno/npm/mod.rs index fed3d388bf..6c6c29d63f 100644 --- a/resolvers/deno/npm/mod.rs +++ b/resolvers/deno/npm/mod.rs @@ -50,6 +50,7 @@ mod byonm; mod local; pub mod managed; +#[derive(Debug)] pub enum CreateInNpmPkgCheckerOptions<'a> { Managed(ManagedInNpmPkgCheckerCreateOptions<'a>), Byonm, diff --git a/resolvers/deno/npmrc.rs b/resolvers/deno/npmrc.rs new file mode 100644 index 0000000000..7fb5dd482f --- /dev/null +++ b/resolvers/deno/npmrc.rs @@ -0,0 +1,200 @@ +// Copyright 2018-2025 the Deno authors. MIT license. + +use std::borrow::Cow; +use std::path::Path; +use std::path::PathBuf; + +use boxed_error::Boxed; +use deno_config::workspace::Workspace; +use deno_npm::npm_rc::NpmRc; +use deno_npm::npm_rc::ResolvedNpmRc; +use sys_traits::EnvHomeDir; +use sys_traits::EnvVar; +use sys_traits::FsRead; +use thiserror::Error; +use url::Url; + +#[allow(clippy::disallowed_types)] +pub type ResolvedNpmRcRc = crate::sync::MaybeArc; + +#[derive(Debug, Boxed)] +pub struct NpmRcDiscoverError(pub Box); + +#[derive(Debug, Error)] +pub enum NpmRcDiscoverErrorKind { + #[error(transparent)] + Load(#[from] NpmRcLoadError), + #[error(transparent)] + Parse(#[from] NpmRcParseError), + #[error(transparent)] + Resolve(#[from] NpmRcOptionsResolveError), + #[error(transparent)] + UrlToFilePath(#[from] deno_path_util::UrlToFilePathError), +} + +#[derive(Debug, Error)] +#[error("Error loading .npmrc at {}.", path.display())] +pub struct NpmRcLoadError { + path: PathBuf, + #[source] + source: std::io::Error, +} + +#[derive(Debug, Error)] +#[error("Failed to parse .npmrc at {}.", path.display())] +pub struct NpmRcParseError { + path: PathBuf, + #[source] + source: std::io::Error, +} + +#[derive(Debug, Error)] +#[error("Failed to resolve .npmrc options at {}.", path.display())] +pub struct NpmRcOptionsResolveError { + path: PathBuf, + #[source] + source: deno_npm::npm_rc::ResolveError, +} + +/// Discover `.npmrc` file - currently we only support it next to `package.json`, +/// next to `deno.json`, or in the user's home directory. +/// +/// In the future we will need to support it in the global directory +/// as per https://docs.npmjs.com/cli/v10/configuring-npm/npmrc#files. +pub fn discover_npmrc_from_workspace( + sys: &TSys, + workspace: &Workspace, +) -> Result<(ResolvedNpmRc, Option), NpmRcDiscoverError> { + let root_folder = workspace.root_folder_configs(); + discover_npmrc( + sys, + root_folder.pkg_json.as_ref().map(|p| p.path.clone()), + match &root_folder.deno_json { + Some(cf) if cf.specifier.scheme() == "file" => { + Some(deno_path_util::url_to_file_path(&cf.specifier)?) + } + _ => None, + }, + ) +} + +fn discover_npmrc( + sys: &TSys, + maybe_package_json_path: Option, + maybe_deno_json_path: Option, +) -> Result<(ResolvedNpmRc, Option), NpmRcDiscoverError> { + const NPMRC_NAME: &str = ".npmrc"; + + fn try_to_read_npmrc( + sys: &impl FsRead, + dir: &Path, + ) -> Result, PathBuf)>, NpmRcLoadError> { + let path = dir.join(NPMRC_NAME); + let maybe_source = match sys.fs_read_to_string(&path) { + Ok(source) => Some(source), + Err(err) if err.kind() == std::io::ErrorKind::NotFound => None, + Err(err) => return Err(NpmRcLoadError { path, source: err }), + }; + + Ok(maybe_source.map(|source| (source, path))) + } + + fn try_to_parse_npmrc( + sys: &impl EnvVar, + source: &str, + path: &Path, + ) -> Result { + let npmrc = NpmRc::parse(source, &|name| sys.env_var(name).ok()).map_err( + |source| { + NpmRcParseError { + path: path.to_path_buf(), + // todo(dsherret): use source directly here once it's no longer an internal type + source: std::io::Error::new(std::io::ErrorKind::InvalidData, source), + } + }, + )?; + let resolved = + npmrc + .as_resolved(&npm_registry_url(sys)) + .map_err(|source| NpmRcOptionsResolveError { + path: path.to_path_buf(), + source, + })?; + log::debug!(".npmrc found at: '{}'", path.display()); + Ok(resolved) + } + + // 1. Try `.npmrc` next to `package.json` + if let Some(package_json_path) = maybe_package_json_path { + if let Some(package_json_dir) = package_json_path.parent() { + if let Some((source, path)) = try_to_read_npmrc(sys, package_json_dir)? { + return try_to_parse_npmrc(sys, &source, &path) + .map(|r| (r, Some(path))); + } + } + } + + // 2. Try `.npmrc` next to `deno.json(c)` + if let Some(deno_json_path) = maybe_deno_json_path { + if let Some(deno_json_dir) = deno_json_path.parent() { + if let Some((source, path)) = try_to_read_npmrc(sys, deno_json_dir)? { + return try_to_parse_npmrc(sys, &source, &path) + .map(|r| (r, Some(path))); + } + } + } + + // TODO(bartlomieju): update to read both files - one in the project root and one and + // home dir and then merge them. + // 3. Try `.npmrc` in the user's home directory + if let Some(home_dir) = sys.env_home_dir() { + match try_to_read_npmrc(sys, &home_dir) { + Ok(Some((source, path))) => { + return try_to_parse_npmrc(sys, &source, &path) + .map(|r| (r, Some(path))); + } + Ok(None) => {} + Err(err) if err.source.kind() == std::io::ErrorKind::PermissionDenied => { + log::debug!( + "Skipping .npmrc in home directory due to permission denied error. {:#}", + err + ); + } + Err(err) => { + return Err(err.into()); + } + } + } + + log::debug!("No .npmrc file found"); + Ok((create_default_npmrc(sys), None)) +} + +pub fn create_default_npmrc(sys: &impl EnvVar) -> ResolvedNpmRc { + ResolvedNpmRc { + default_config: deno_npm::npm_rc::RegistryConfigWithUrl { + registry_url: npm_registry_url(sys).clone(), + config: Default::default(), + }, + scopes: Default::default(), + registry_configs: Default::default(), + } +} + +pub fn npm_registry_url(sys: &impl EnvVar) -> Url { + let env_var_name = "NPM_CONFIG_REGISTRY"; + if let Ok(registry_url) = sys.env_var(env_var_name) { + // ensure there is a trailing slash for the directory + let registry_url = format!("{}/", registry_url.trim_end_matches('/')); + match Url::parse(®istry_url) { + Ok(url) => { + return url; + } + Err(err) => { + log::debug!("Invalid {} environment variable: {:#}", env_var_name, err,); + } + } + } + + Url::parse("https://registry.npmjs.org").unwrap() +} diff --git a/resolvers/node/builtin_modules.rs b/resolvers/node/builtin_modules.rs new file mode 100644 index 0000000000..7b7a7cb1f3 --- /dev/null +++ b/resolvers/node/builtin_modules.rs @@ -0,0 +1,103 @@ +// Copyright 2018-2025 the Deno authors. MIT license. + +pub trait IsBuiltInNodeModuleChecker: std::fmt::Debug { + /// e.g. `is_builtin_node_module("assert")` + fn is_builtin_node_module(&self, module_name: &str) -> bool; +} + +/// An implementation of IsBuiltInNodeModuleChecker that uses +/// the list of built-in node_modules that are supported by Deno +/// in the `deno_node` crate (ext/node). +#[derive(Debug)] +pub struct DenoIsBuiltInNodeModuleChecker; + +impl IsBuiltInNodeModuleChecker for DenoIsBuiltInNodeModuleChecker { + #[inline(always)] + fn is_builtin_node_module(&self, module_name: &str) -> bool { + DENO_SUPPORTED_BUILTIN_NODE_MODULES + .binary_search(&module_name) + .is_ok() + } +} + +/// Collection of built-in node_modules supported by Deno. +pub static DENO_SUPPORTED_BUILTIN_NODE_MODULES: &[&str] = &[ + // NOTE(bartlomieju): keep this list in sync with `ext/node/polyfills/01_require.js` + "_http_agent", + "_http_common", + "_http_outgoing", + "_http_server", + "_stream_duplex", + "_stream_passthrough", + "_stream_readable", + "_stream_transform", + "_stream_writable", + "_tls_common", + "_tls_wrap", + "assert", + "assert/strict", + "async_hooks", + "buffer", + "child_process", + "cluster", + "console", + "constants", + "crypto", + "dgram", + "diagnostics_channel", + "dns", + "dns/promises", + "domain", + "events", + "fs", + "fs/promises", + "http", + "http2", + "https", + "inspector", + "inspector/promises", + "module", + "net", + "os", + "path", + "path/posix", + "path/win32", + "perf_hooks", + "process", + "punycode", + "querystring", + "readline", + "readline/promises", + "repl", + "stream", + "stream/consumers", + "stream/promises", + "stream/web", + "string_decoder", + "sys", + "test", + "timers", + "timers/promises", + "tls", + "tty", + "url", + "util", + "util/types", + "v8", + "vm", + "wasi", + "worker_threads", + "zlib", +]; + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_builtins_are_sorted() { + let mut builtins_list = DENO_SUPPORTED_BUILTIN_NODE_MODULES.to_vec(); + builtins_list.sort(); + assert_eq!(DENO_SUPPORTED_BUILTIN_NODE_MODULES, builtins_list); + } +} diff --git a/resolvers/node/lib.rs b/resolvers/node/lib.rs index c0e6383237..ac945422dd 100644 --- a/resolvers/node/lib.rs +++ b/resolvers/node/lib.rs @@ -4,6 +4,7 @@ #![deny(clippy::print_stdout)] pub mod analyze; +mod builtin_modules; pub mod errors; mod npm; mod package_json; @@ -12,6 +13,9 @@ mod resolution; mod sync; +pub use builtin_modules::DenoIsBuiltInNodeModuleChecker; +pub use builtin_modules::IsBuiltInNodeModuleChecker; +pub use builtin_modules::DENO_SUPPORTED_BUILTIN_NODE_MODULES; pub use deno_package_json::PackageJson; pub use npm::InNpmPackageChecker; pub use npm::NpmPackageFolderResolver; @@ -22,7 +26,6 @@ pub use path::PathClean; pub use resolution::parse_npm_pkg_name; pub use resolution::resolve_specifier_into_node_modules; pub use resolution::ConditionsFromResolutionMode; -pub use resolution::IsBuiltInNodeModuleChecker; pub use resolution::NodeResolution; pub use resolution::NodeResolutionKind; pub use resolution::NodeResolver; diff --git a/resolvers/node/resolution.rs b/resolvers/node/resolution.rs index 9ea5e17e41..a1dba83ef9 100644 --- a/resolvers/node/resolution.rs +++ b/resolvers/node/resolution.rs @@ -47,6 +47,7 @@ use crate::errors::TypesNotFoundErrorData; use crate::errors::UnsupportedDirImportError; use crate::errors::UnsupportedEsmUrlSchemeError; use crate::InNpmPackageChecker; +use crate::IsBuiltInNodeModuleChecker; use crate::NpmPackageFolderResolver; use crate::PackageJsonResolverRc; use crate::PathClean; @@ -55,11 +56,12 @@ pub static DEFAULT_CONDITIONS: &[&str] = &["deno", "node", "import"]; pub static REQUIRE_CONDITIONS: &[&str] = &["require", "node"]; static TYPES_ONLY_CONDITIONS: &[&str] = &["types"]; -type ConditionsFromResolutionModeFn = Box< +#[allow(clippy::disallowed_types)] +type ConditionsFromResolutionModeFn = crate::sync::MaybeArc< dyn Fn(ResolutionMode) -> &'static [&'static str] + Send + Sync + 'static, >; -#[derive(Default)] +#[derive(Default, Clone)] pub struct ConditionsFromResolutionMode(Option); impl Debug for ConditionsFromResolutionMode { @@ -132,10 +134,6 @@ impl NodeResolution { } } -pub trait IsBuiltInNodeModuleChecker: std::fmt::Debug { - fn is_builtin_node_module(&self, specifier: &str) -> bool; -} - #[allow(clippy::disallowed_types)] pub type NodeResolverRc< TInNpmPackageChecker, diff --git a/resolvers/npm_cache/Cargo.toml b/resolvers/npm_cache/Cargo.toml index 48c249554a..26be813596 100644 --- a/resolvers/npm_cache/Cargo.toml +++ b/resolvers/npm_cache/Cargo.toml @@ -17,7 +17,7 @@ path = "lib.rs" async-trait.workspace = true base64.workspace = true boxed_error.workspace = true -deno_cache_dir.workspace = true +deno_cache_dir = { workspace = true, features = ["sync"] } deno_error = { workspace = true, features = ["serde", "serde_json", "tokio"] } deno_npm.workspace = true deno_path_util.workspace = true diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 58154ebe6a..c2fbaca52f 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -65,7 +65,7 @@ deno_os.workspace = true deno_path_util.workspace = true deno_permissions.workspace = true deno_process.workspace = true -deno_resolver.workspace = true +deno_resolver = { workspace = true, features = ["sync"] } deno_telemetry.workspace = true deno_terminal.workspace = true deno_tls.workspace = true diff --git a/tests/integration/npm_tests.rs b/tests/integration/npm_tests.rs index 4406982cc8..bcbcefe731 100644 --- a/tests/integration/npm_tests.rs +++ b/tests/integration/npm_tests.rs @@ -41,6 +41,7 @@ itest!(require_resolve_url_paths { cwd: Some("npm/require_resolve_url/"), copy_temp_dir: Some("npm/require_resolve_url/"), }); + #[test] fn parallel_downloading() { let (out, _err) = util::run_and_collect_output_with_args(