diff options
Diffstat (limited to 'cli/standalone')
-rw-r--r-- | cli/standalone/binary.rs | 313 | ||||
-rw-r--r-- | cli/standalone/mod.rs | 449 | ||||
-rw-r--r-- | cli/standalone/virtual_fs.rs | 41 |
3 files changed, 491 insertions, 312 deletions
diff --git a/cli/standalone/binary.rs b/cli/standalone/binary.rs index 98af5fa77..bf035577c 100644 --- a/cli/standalone/binary.rs +++ b/cli/standalone/binary.rs @@ -2,6 +2,7 @@ use std::borrow::Cow; use std::collections::BTreeMap; +use std::collections::VecDeque; use std::env::current_exe; use std::ffi::OsString; use std::fs; @@ -15,8 +16,8 @@ use std::path::PathBuf; use std::process::Command; use deno_ast::ModuleSpecifier; -use deno_config::package_json::PackageJsonDepValueParseError; -use deno_config::package_json::PackageJsonDeps; +use deno_config::workspace::PackageJsonDepResolution; +use deno_config::workspace::Workspace; use deno_core::anyhow::bail; use deno_core::anyhow::Context; use deno_core::error::AnyError; @@ -26,9 +27,12 @@ use deno_core::futures::AsyncSeekExt; use deno_core::serde_json; use deno_core::url::Url; use deno_npm::NpmSystemInfo; +use deno_runtime::deno_node::PackageJson; use deno_semver::npm::NpmVersionReqParseError; use deno_semver::package::PackageReq; use deno_semver::VersionReqSpecifierParseError; +use eszip::EszipRelativeFileBaseUrl; +use indexmap::IndexMap; use log::Level; use serde::Deserialize; use serde::Serialize; @@ -36,7 +40,7 @@ use serde::Serialize; use crate::args::CaData; use crate::args::CliOptions; use crate::args::CompileFlags; -use crate::args::PackageJsonDepsProvider; +use crate::args::PackageJsonInstallDepsProvider; use crate::args::PermissionFlags; use crate::args::UnstableConfig; use crate::cache::DenoDir; @@ -44,6 +48,8 @@ use crate::file_fetcher::FileFetcher; use crate::http_util::HttpClientProvider; use crate::npm::CliNpmResolver; use crate::npm::InnerCliNpmResolverRef; +use crate::standalone::virtual_fs::VfsEntry; +use crate::util::fs::canonicalize_path_maybe_not_exists; use crate::util::progress_bar::ProgressBar; use crate::util::progress_bar::ProgressBarStyle; @@ -54,82 +60,31 @@ use super::virtual_fs::VirtualDirectory; const MAGIC_TRAILER: &[u8; 8] = b"d3n0l4nd"; -#[derive(Serialize, Deserialize)] -enum SerializablePackageJsonDepValueParseError { - VersionReq(String), - Unsupported { scheme: String }, -} - -impl SerializablePackageJsonDepValueParseError { - pub fn from_err(err: PackageJsonDepValueParseError) -> Self { - match err { - PackageJsonDepValueParseError::VersionReq(err) => { - Self::VersionReq(err.source.to_string()) - } - PackageJsonDepValueParseError::Unsupported { scheme } => { - Self::Unsupported { scheme } - } - } - } - - pub fn into_err(self) -> PackageJsonDepValueParseError { - match self { - SerializablePackageJsonDepValueParseError::VersionReq(source) => { - PackageJsonDepValueParseError::VersionReq(NpmVersionReqParseError { - source: monch::ParseErrorFailureError::new(source), - }) - } - SerializablePackageJsonDepValueParseError::Unsupported { scheme } => { - PackageJsonDepValueParseError::Unsupported { scheme } - } - } - } -} - -#[derive(Serialize, Deserialize)] -pub struct SerializablePackageJsonDeps( - BTreeMap< - String, - Result<PackageReq, SerializablePackageJsonDepValueParseError>, - >, -); - -impl SerializablePackageJsonDeps { - pub fn from_deps(deps: PackageJsonDeps) -> Self { - Self( - deps - .into_iter() - .map(|(name, req)| { - let res = - req.map_err(SerializablePackageJsonDepValueParseError::from_err); - (name, res) - }) - .collect(), - ) - } - - pub fn into_deps(self) -> PackageJsonDeps { - self - .0 - .into_iter() - .map(|(name, res)| (name, res.map_err(|err| err.into_err()))) - .collect() - } -} - #[derive(Deserialize, Serialize)] pub enum NodeModules { Managed { - /// Whether this uses a node_modules directory (true) or the global cache (false). - node_modules_dir: bool, - package_json_deps: Option<SerializablePackageJsonDeps>, + /// Relative path for the node_modules directory in the vfs. + node_modules_dir: Option<String>, }, Byonm { - package_json_deps: Option<SerializablePackageJsonDeps>, + root_node_modules_dir: String, }, } #[derive(Deserialize, Serialize)] +pub struct SerializedWorkspaceResolverImportMap { + pub specifier: String, + pub json: String, +} + +#[derive(Deserialize, Serialize)] +pub struct SerializedWorkspaceResolver { + pub import_map: Option<SerializedWorkspaceResolverImportMap>, + pub package_jsons: BTreeMap<String, serde_json::Value>, + pub pkg_json_resolution: PackageJsonDepResolution, +} + +#[derive(Deserialize, Serialize)] pub struct Metadata { pub argv: Vec<String>, pub seed: Option<u64>, @@ -140,8 +95,8 @@ pub struct Metadata { pub ca_stores: Option<Vec<String>>, pub ca_data: Option<Vec<u8>>, pub unsafely_ignore_certificate_errors: Option<Vec<String>>, - pub maybe_import_map: Option<(Url, String)>, - pub entrypoint: ModuleSpecifier, + pub workspace_resolver: SerializedWorkspaceResolver, + pub entrypoint_key: String, pub node_modules: Option<NodeModules>, pub disable_deprecated_api_warning: bool, pub unstable_config: UnstableConfig, @@ -415,13 +370,13 @@ pub fn unpack_into_dir( fs::remove_file(&archive_path)?; Ok(exe_path) } + pub struct DenoCompileBinaryWriter<'a> { deno_dir: &'a DenoDir, file_fetcher: &'a FileFetcher, http_client_provider: &'a HttpClientProvider, npm_resolver: &'a dyn CliNpmResolver, npm_system_info: NpmSystemInfo, - package_json_deps_provider: &'a PackageJsonDepsProvider, } impl<'a> DenoCompileBinaryWriter<'a> { @@ -432,7 +387,6 @@ impl<'a> DenoCompileBinaryWriter<'a> { http_client_provider: &'a HttpClientProvider, npm_resolver: &'a dyn CliNpmResolver, npm_system_info: NpmSystemInfo, - package_json_deps_provider: &'a PackageJsonDepsProvider, ) -> Self { Self { deno_dir, @@ -440,7 +394,6 @@ impl<'a> DenoCompileBinaryWriter<'a> { http_client_provider, npm_resolver, npm_system_info, - package_json_deps_provider, } } @@ -448,7 +401,8 @@ impl<'a> DenoCompileBinaryWriter<'a> { &self, writer: &mut impl Write, eszip: eszip::EszipV2, - module_specifier: &ModuleSpecifier, + root_dir_url: EszipRelativeFileBaseUrl<'_>, + entrypoint: &ModuleSpecifier, compile_flags: &CompileFlags, cli_options: &CliOptions, ) -> Result<(), AnyError> { @@ -465,13 +419,13 @@ impl<'a> DenoCompileBinaryWriter<'a> { } set_windows_binary_to_gui(&mut original_binary)?; } - self .write_standalone_binary( writer, original_binary, eszip, - module_specifier, + root_dir_url, + entrypoint, cli_options, compile_flags, ) @@ -557,11 +511,13 @@ impl<'a> DenoCompileBinaryWriter<'a> { /// This functions creates a standalone deno binary by appending a bundle /// and magic trailer to the currently executing binary. + #[allow(clippy::too_many_arguments)] async fn write_standalone_binary( &self, writer: &mut impl Write, original_bin: Vec<u8>, mut eszip: eszip::EszipV2, + root_dir_url: EszipRelativeFileBaseUrl<'_>, entrypoint: &ModuleSpecifier, cli_options: &CliOptions, compile_flags: &CompileFlags, @@ -574,48 +530,60 @@ impl<'a> DenoCompileBinaryWriter<'a> { Some(CaData::Bytes(bytes)) => Some(bytes.clone()), None => None, }; - let maybe_import_map = cli_options - .resolve_import_map(self.file_fetcher) - .await? - .map(|import_map| (import_map.base_url().clone(), import_map.to_json())); - let (npm_vfs, npm_files, node_modules) = - match self.npm_resolver.as_inner() { - InnerCliNpmResolverRef::Managed(managed) => { - let snapshot = - managed.serialized_valid_snapshot_for_system(&self.npm_system_info); - if !snapshot.as_serialized().packages.is_empty() { - let (root_dir, files) = self.build_vfs()?.into_dir_and_files(); - eszip.add_npm_snapshot(snapshot); - ( - Some(root_dir), - files, - Some(NodeModules::Managed { - node_modules_dir: self - .npm_resolver - .root_node_modules_path() - .is_some(), - package_json_deps: self.package_json_deps_provider.deps().map( - |deps| SerializablePackageJsonDeps::from_deps(deps.clone()), - ), - }), - ) - } else { - (None, Vec::new(), None) - } - } - InnerCliNpmResolverRef::Byonm(_) => { - let (root_dir, files) = self.build_vfs()?.into_dir_and_files(); + let workspace_resolver = cli_options + .create_workspace_resolver(self.file_fetcher) + .await?; + let root_path = root_dir_url.inner().to_file_path().unwrap(); + let (npm_vfs, npm_files, node_modules) = match self.npm_resolver.as_inner() + { + InnerCliNpmResolverRef::Managed(managed) => { + let snapshot = + managed.serialized_valid_snapshot_for_system(&self.npm_system_info); + if !snapshot.as_serialized().packages.is_empty() { + let (root_dir, files) = self + .build_vfs(&root_path, cli_options)? + .into_dir_and_files(); + eszip.add_npm_snapshot(snapshot); ( Some(root_dir), files, - Some(NodeModules::Byonm { - package_json_deps: self.package_json_deps_provider.deps().map( - |deps| SerializablePackageJsonDeps::from_deps(deps.clone()), + Some(NodeModules::Managed { + node_modules_dir: self.npm_resolver.root_node_modules_path().map( + |path| { + root_dir_url + .specifier_key( + &ModuleSpecifier::from_directory_path(path).unwrap(), + ) + .into_owned() + }, ), }), ) + } else { + (None, Vec::new(), None) } - }; + } + InnerCliNpmResolverRef::Byonm(resolver) => { + let (root_dir, files) = self + .build_vfs(&root_path, cli_options)? + .into_dir_and_files(); + ( + Some(root_dir), + files, + Some(NodeModules::Byonm { + root_node_modules_dir: root_dir_url + .specifier_key( + &ModuleSpecifier::from_directory_path( + // will always be set for byonm + resolver.root_node_modules_path().unwrap(), + ) + .unwrap(), + ) + .into_owned(), + }), + ) + } + }; let metadata = Metadata { argv: compile_flags.args.clone(), @@ -629,8 +597,32 @@ impl<'a> DenoCompileBinaryWriter<'a> { log_level: cli_options.log_level(), ca_stores: cli_options.ca_stores().clone(), ca_data, - entrypoint: entrypoint.clone(), - maybe_import_map, + entrypoint_key: root_dir_url.specifier_key(entrypoint).into_owned(), + workspace_resolver: SerializedWorkspaceResolver { + import_map: workspace_resolver.maybe_import_map().map(|i| { + SerializedWorkspaceResolverImportMap { + specifier: if i.base_url().scheme() == "file" { + root_dir_url.specifier_key(i.base_url()).into_owned() + } else { + // just make a remote url local + "deno.json".to_string() + }, + json: i.to_json(), + } + }), + package_jsons: workspace_resolver + .package_jsons() + .map(|pkg_json| { + ( + root_dir_url + .specifier_key(&pkg_json.specifier()) + .into_owned(), + serde_json::to_value(pkg_json).unwrap(), + ) + }) + .collect(), + pkg_json_resolution: workspace_resolver.pkg_json_dep_resolution(), + }, node_modules, disable_deprecated_api_warning: cli_options .disable_deprecated_api_warning, @@ -653,7 +645,11 @@ impl<'a> DenoCompileBinaryWriter<'a> { ) } - fn build_vfs(&self) -> Result<VfsBuilder, AnyError> { + fn build_vfs( + &self, + root_path: &Path, + cli_options: &CliOptions, + ) -> Result<VfsBuilder, AnyError> { fn maybe_warn_different_system(system_info: &NpmSystemInfo) { if system_info != &NpmSystemInfo::default() { log::warn!("{} The node_modules directory may be incompatible with the target system.", crate::colors::yellow("Warning")); @@ -664,7 +660,7 @@ impl<'a> DenoCompileBinaryWriter<'a> { InnerCliNpmResolverRef::Managed(npm_resolver) => { if let Some(node_modules_path) = npm_resolver.root_node_modules_path() { maybe_warn_different_system(&self.npm_system_info); - let mut builder = VfsBuilder::new(node_modules_path.clone())?; + let mut builder = VfsBuilder::new(root_path.to_path_buf())?; builder.add_dir_recursive(node_modules_path)?; Ok(builder) } else { @@ -678,23 +674,82 @@ impl<'a> DenoCompileBinaryWriter<'a> { npm_resolver.resolve_pkg_folder_from_pkg_id(&package.id)?; builder.add_dir_recursive(&folder)?; } - // overwrite the root directory's name to obscure the user's registry url - builder.set_root_dir_name("node_modules".to_string()); + + // Flatten all the registries folders into a single "node_modules/localhost" folder + // that will be used by denort when loading the npm cache. This avoids us exposing + // the user's private registry information and means we don't have to bother + // serializing all the different registry config into the binary. + builder.with_root_dir(|root_dir| { + root_dir.name = "node_modules".to_string(); + let mut new_entries = Vec::with_capacity(root_dir.entries.len()); + let mut localhost_entries = IndexMap::new(); + for entry in std::mem::take(&mut root_dir.entries) { + match entry { + VfsEntry::Dir(dir) => { + for entry in dir.entries { + log::debug!( + "Flattening {} into node_modules", + entry.name() + ); + if let Some(existing) = + localhost_entries.insert(entry.name().to_string(), entry) + { + panic!( + "Unhandled scenario where a duplicate entry was found: {:?}", + existing + ); + } + } + } + VfsEntry::File(_) | VfsEntry::Symlink(_) => { + new_entries.push(entry); + } + } + } + new_entries.push(VfsEntry::Dir(VirtualDirectory { + name: "localhost".to_string(), + entries: localhost_entries.into_iter().map(|(_, v)| v).collect(), + })); + // needs to be sorted by name + new_entries.sort_by(|a, b| a.name().cmp(b.name())); + root_dir.entries = new_entries; + }); + Ok(builder) } } - InnerCliNpmResolverRef::Byonm(npm_resolver) => { + InnerCliNpmResolverRef::Byonm(_) => { maybe_warn_different_system(&self.npm_system_info); - // the root_node_modules directory will always exist for byonm - let node_modules_path = npm_resolver.root_node_modules_path().unwrap(); - let parent_path = node_modules_path.parent().unwrap(); - let mut builder = VfsBuilder::new(parent_path.to_path_buf())?; - let package_json_path = parent_path.join("package.json"); - if package_json_path.exists() { - builder.add_file_at_path(&package_json_path)?; + let mut builder = VfsBuilder::new(root_path.to_path_buf())?; + for pkg_json in cli_options.workspace.package_jsons() { + builder.add_file_at_path(&pkg_json.path)?; } - if node_modules_path.exists() { - builder.add_dir_recursive(node_modules_path)?; + // traverse and add all the node_modules directories in the workspace + let mut pending_dirs = VecDeque::new(); + pending_dirs.push_back( + cli_options + .workspace + .root_folder() + .0 + .to_file_path() + .unwrap(), + ); + while let Some(pending_dir) = pending_dirs.pop_front() { + let entries = fs::read_dir(&pending_dir).with_context(|| { + format!("Failed reading: {}", pending_dir.display()) + })?; + for entry in entries { + let entry = entry?; + let path = entry.path(); + if !path.is_dir() { + continue; + } + if path.ends_with("node_modules") { + builder.add_dir_recursive(&path)?; + } else { + pending_dirs.push_back(path); + } + } } Ok(builder) } diff --git a/cli/standalone/mod.rs b/cli/standalone/mod.rs index 24ba7c9db..cbd14db4f 100644 --- a/cli/standalone/mod.rs +++ b/cli/standalone/mod.rs @@ -10,7 +10,7 @@ use crate::args::get_root_cert_store; use crate::args::npm_pkg_req_ref_to_binary_command; use crate::args::CaData; use crate::args::CacheSetting; -use crate::args::PackageJsonDepsProvider; +use crate::args::PackageJsonInstallDepsProvider; use crate::args::StorageKeyResolver; use crate::cache::Caches; use crate::cache::DenoDirProvider; @@ -25,7 +25,6 @@ use crate::npm::CliNpmResolverManagedSnapshotOption; use crate::npm::NpmCacheDir; use crate::resolver::CjsResolutionStore; use crate::resolver::CliNodeResolver; -use crate::resolver::MappedSpecifierResolver; use crate::resolver::NpmModuleLoader; use crate::util::progress_bar::ProgressBar; use crate::util::progress_bar::ProgressBarStyle; @@ -35,6 +34,10 @@ use crate::worker::CliMainWorkerOptions; use crate::worker::ModuleLoaderAndSourceMapGetter; use crate::worker::ModuleLoaderFactory; use deno_ast::MediaType; +use deno_config::package_json::PackageJsonDepValue; +use deno_config::workspace::MappedResolution; +use deno_config::workspace::MappedResolutionError; +use deno_config::workspace::WorkspaceResolver; use deno_core::anyhow::Context; use deno_core::error::generic_error; use deno_core::error::type_error; @@ -48,6 +51,7 @@ use deno_core::ModuleSpecifier; use deno_core::ModuleType; use deno_core::RequestedModuleType; use deno_core::ResolutionKind; +use deno_npm::npm_rc::ResolvedNpmRc; use deno_runtime::deno_fs; use deno_runtime::deno_node::analyze::NodeCodeTranslator; use deno_runtime::deno_node::NodeResolutionMode; @@ -59,7 +63,9 @@ use deno_runtime::deno_tls::RootCertStoreProvider; use deno_runtime::WorkerExecutionMode; use deno_runtime::WorkerLogLevel; use deno_semver::npm::NpmPackageReqReference; +use eszip::EszipRelativeFileBaseUrl; use import_map::parse_from_json; +use std::borrow::Cow; use std::rc::Rc; use std::sync::Arc; @@ -75,9 +81,43 @@ use self::binary::load_npm_vfs; use self::binary::Metadata; use self::file_system::DenoCompileFileSystem; -struct SharedModuleLoaderState { +struct WorkspaceEszipModule { + specifier: ModuleSpecifier, + inner: eszip::Module, +} + +struct WorkspaceEszip { eszip: eszip::EszipV2, - mapped_specifier_resolver: MappedSpecifierResolver, + root_dir_url: ModuleSpecifier, +} + +impl WorkspaceEszip { + pub fn get_module( + &self, + specifier: &ModuleSpecifier, + ) -> Option<WorkspaceEszipModule> { + if specifier.scheme() == "file" { + let specifier_key = EszipRelativeFileBaseUrl::new(&self.root_dir_url) + .specifier_key(specifier); + let module = self.eszip.get_module(&specifier_key)?; + let specifier = self.root_dir_url.join(&module.specifier).unwrap(); + Some(WorkspaceEszipModule { + specifier, + inner: module, + }) + } else { + let module = self.eszip.get_module(specifier.as_str())?; + Some(WorkspaceEszipModule { + specifier: ModuleSpecifier::parse(&module.specifier).unwrap(), + inner: module, + }) + } + } +} + +struct SharedModuleLoaderState { + eszip: WorkspaceEszip, + workspace_resolver: WorkspaceResolver, node_resolver: Arc<CliNodeResolver>, npm_module_loader: Arc<NpmModuleLoader>, } @@ -122,44 +162,92 @@ impl ModuleLoader for EmbeddedModuleLoader { }; } - let maybe_mapped = self - .shared - .mapped_specifier_resolver - .resolve(specifier, &referrer)? - .into_specifier(); - - // npm specifier - let specifier_text = maybe_mapped - .as_ref() - .map(|r| r.as_str()) - .unwrap_or(specifier); - if let Ok(reference) = NpmPackageReqReference::from_str(specifier_text) { - return self - .shared - .node_resolver - .resolve_req_reference( - &reference, - &referrer, - NodeResolutionMode::Execution, - ) - .map(|res| res.into_url()); - } + let mapped_resolution = + self.shared.workspace_resolver.resolve(specifier, &referrer); - let specifier = match maybe_mapped { - Some(resolved) => resolved, - None => deno_core::resolve_import(specifier, referrer.as_str())?, - }; + match mapped_resolution { + Ok(MappedResolution::PackageJson { + dep_result, + sub_path, + alias, + .. + }) => match dep_result.as_ref().map_err(|e| AnyError::from(e.clone()))? { + PackageJsonDepValue::Req(req) => self + .shared + .node_resolver + .resolve_req_with_sub_path( + req, + sub_path.as_deref(), + &referrer, + NodeResolutionMode::Execution, + ) + .map(|res| res.into_url()), + PackageJsonDepValue::Workspace(version_req) => { + let pkg_folder = self + .shared + .workspace_resolver + .resolve_workspace_pkg_json_folder_for_pkg_json_dep( + alias, + version_req, + )?; + Ok( + self + .shared + .node_resolver + .resolve_package_sub_path_from_deno_module( + pkg_folder, + sub_path.as_deref(), + &referrer, + NodeResolutionMode::Execution, + )? + .into_url(), + ) + } + }, + Ok(MappedResolution::Normal(specifier)) + | Ok(MappedResolution::ImportMap(specifier)) => { + if let Ok(reference) = + NpmPackageReqReference::from_specifier(&specifier) + { + return self + .shared + .node_resolver + .resolve_req_reference( + &reference, + &referrer, + NodeResolutionMode::Execution, + ) + .map(|res| res.into_url()); + } + + if specifier.scheme() == "jsr" { + if let Some(module) = self.shared.eszip.get_module(&specifier) { + return Ok(module.specifier); + } + } - if specifier.scheme() == "jsr" { - if let Some(module) = self.shared.eszip.get_module(specifier.as_str()) { - return Ok(ModuleSpecifier::parse(&module.specifier).unwrap()); + self + .shared + .node_resolver + .handle_if_in_node_modules(specifier) } + Err(err) + if err.is_unmapped_bare_specifier() && referrer.scheme() == "file" => + { + // todo(dsherret): return a better error from node resolution so that + // we can more easily tell whether to surface it or not + let node_result = self.shared.node_resolver.resolve( + specifier, + &referrer, + NodeResolutionMode::Execution, + ); + if let Ok(Some(res)) = node_result { + return Ok(res.into_url()); + } + Err(err.into()) + } + Err(err) => Err(err.into()), } - - self - .shared - .node_resolver - .handle_if_in_node_modules(specifier) } fn load( @@ -215,27 +303,23 @@ impl ModuleLoader for EmbeddedModuleLoader { ); } - let Some(module) = - self.shared.eszip.get_module(original_specifier.as_str()) - else { + let Some(module) = self.shared.eszip.get_module(original_specifier) else { return deno_core::ModuleLoadResponse::Sync(Err(type_error(format!( "Module not found: {}", original_specifier )))); }; let original_specifier = original_specifier.clone(); - let found_specifier = - ModuleSpecifier::parse(&module.specifier).expect("invalid url in eszip"); deno_core::ModuleLoadResponse::Async( async move { - let code = module.source().await.ok_or_else(|| { + let code = module.inner.source().await.ok_or_else(|| { type_error(format!("Module not found: {}", original_specifier)) })?; let code = arc_u8_to_arc_str(code) .map_err(|_| type_error("Module source is not utf-8"))?; Ok(deno_core::ModuleSource::new_with_redirect( - match module.kind { + match module.inner.kind { eszip::ModuleKind::JavaScript => ModuleType::JavaScript, eszip::ModuleKind::Json => ModuleType::Json, eszip::ModuleKind::Jsonc => { @@ -247,7 +331,7 @@ impl ModuleLoader for EmbeddedModuleLoader { }, ModuleSourceCode::String(code.into()), &original_specifier, - &found_specifier, + &module.specifier, None, )) } @@ -324,10 +408,10 @@ pub async fn run( mut eszip: eszip::EszipV2, metadata: Metadata, ) -> Result<i32, AnyError> { - let main_module = &metadata.entrypoint; let current_exe_path = std::env::current_exe().unwrap(); let current_exe_name = current_exe_path.file_name().unwrap().to_string_lossy(); + let maybe_cwd = std::env::current_dir().ok(); let deno_dir_provider = Arc::new(DenoDirProvider::new(None)); let root_cert_store_provider = Arc::new(StandaloneRootCertStoreProvider { ca_stores: metadata.ca_stores, @@ -341,119 +425,109 @@ pub async fn run( )); // use a dummy npm registry url let npm_registry_url = ModuleSpecifier::parse("https://localhost/").unwrap(); - let root_path = std::env::temp_dir() - .join(format!("deno-compile-{}", current_exe_name)) - .join("node_modules"); - let npm_cache_dir = - NpmCacheDir::new(root_path.clone(), vec![npm_registry_url.clone()]); + let root_path = + std::env::temp_dir().join(format!("deno-compile-{}", current_exe_name)); + let root_dir_url = ModuleSpecifier::from_directory_path(&root_path).unwrap(); + let main_module = root_dir_url.join(&metadata.entrypoint_key).unwrap(); + let root_node_modules_path = root_path.join("node_modules"); + let npm_cache_dir = NpmCacheDir::new( + root_node_modules_path.clone(), + vec![npm_registry_url.clone()], + ); let npm_global_cache_dir = npm_cache_dir.get_cache_location(); let cache_setting = CacheSetting::Only; - let (package_json_deps_provider, fs, npm_resolver, maybe_vfs_root) = - match metadata.node_modules { - Some(binary::NodeModules::Managed { - node_modules_dir, - package_json_deps, - }) => { - // this will always have a snapshot - let snapshot = eszip.take_npm_snapshot().unwrap(); - let vfs_root_dir_path = if node_modules_dir { - root_path - } else { - npm_cache_dir.root_dir().to_owned() - }; - let vfs = load_npm_vfs(vfs_root_dir_path.clone()) - .context("Failed to load npm vfs.")?; - let maybe_node_modules_path = if node_modules_dir { - Some(vfs.root().to_path_buf()) - } else { - None - }; - let package_json_deps_provider = - Arc::new(PackageJsonDepsProvider::new( - package_json_deps.map(|serialized| serialized.into_deps()), - )); - let fs = Arc::new(DenoCompileFileSystem::new(vfs)) - as Arc<dyn deno_fs::FileSystem>; - let npm_resolver = - create_cli_npm_resolver(CliNpmResolverCreateOptions::Managed( - CliNpmResolverManagedCreateOptions { - snapshot: CliNpmResolverManagedSnapshotOption::Specified(Some( - snapshot, - )), - maybe_lockfile: None, - fs: fs.clone(), - http_client_provider: http_client_provider.clone(), - npm_global_cache_dir, - cache_setting, - text_only_progress_bar: progress_bar, - maybe_node_modules_path, - package_json_deps_provider: package_json_deps_provider.clone(), - npm_system_info: Default::default(), - // Packages from different registries are already inlined in the ESZip, - // so no need to create actual `.npmrc` configuration. - npmrc: create_default_npmrc(), - }, - )) - .await?; - ( - package_json_deps_provider, - fs, - npm_resolver, - Some(vfs_root_dir_path), - ) - } - Some(binary::NodeModules::Byonm { package_json_deps }) => { - let vfs_root_dir_path = root_path; - let vfs = load_npm_vfs(vfs_root_dir_path.clone()) - .context("Failed to load npm vfs.")?; - let node_modules_path = vfs.root().join("node_modules"); - let package_json_deps_provider = - Arc::new(PackageJsonDepsProvider::new( - package_json_deps.map(|serialized| serialized.into_deps()), - )); - let fs = Arc::new(DenoCompileFileSystem::new(vfs)) - as Arc<dyn deno_fs::FileSystem>; - let npm_resolver = - create_cli_npm_resolver(CliNpmResolverCreateOptions::Byonm( - CliNpmResolverByonmCreateOptions { - fs: fs.clone(), - root_node_modules_dir: node_modules_path, - }, - )) - .await?; - ( - package_json_deps_provider, - fs, - npm_resolver, - Some(vfs_root_dir_path), - ) - } - None => { - let package_json_deps_provider = - Arc::new(PackageJsonDepsProvider::new(None)); - let fs = Arc::new(deno_fs::RealFs) as Arc<dyn deno_fs::FileSystem>; - let npm_resolver = - create_cli_npm_resolver(CliNpmResolverCreateOptions::Managed( - CliNpmResolverManagedCreateOptions { - snapshot: CliNpmResolverManagedSnapshotOption::Specified(None), - maybe_lockfile: None, - fs: fs.clone(), - http_client_provider: http_client_provider.clone(), - npm_global_cache_dir, - cache_setting, - text_only_progress_bar: progress_bar, - maybe_node_modules_path: None, - package_json_deps_provider: package_json_deps_provider.clone(), - npm_system_info: Default::default(), - // Packages from different registries are already inlined in the ESZip, - // so no need to create actual `.npmrc` configuration. - npmrc: create_default_npmrc(), - }, - )) - .await?; - (package_json_deps_provider, fs, npm_resolver, None) - } - }; + let (fs, npm_resolver, maybe_vfs_root) = match metadata.node_modules { + Some(binary::NodeModules::Managed { node_modules_dir }) => { + // this will always have a snapshot + let snapshot = eszip.take_npm_snapshot().unwrap(); + let vfs_root_dir_path = if node_modules_dir.is_some() { + root_path.clone() + } else { + npm_cache_dir.root_dir().to_owned() + }; + let vfs = load_npm_vfs(vfs_root_dir_path.clone()) + .context("Failed to load npm vfs.")?; + let maybe_node_modules_path = node_modules_dir + .map(|node_modules_dir| vfs_root_dir_path.join(node_modules_dir)); + let fs = Arc::new(DenoCompileFileSystem::new(vfs)) + as Arc<dyn deno_fs::FileSystem>; + let npm_resolver = + create_cli_npm_resolver(CliNpmResolverCreateOptions::Managed( + CliNpmResolverManagedCreateOptions { + snapshot: CliNpmResolverManagedSnapshotOption::Specified(Some( + snapshot, + )), + maybe_lockfile: None, + fs: fs.clone(), + http_client_provider: http_client_provider.clone(), + npm_global_cache_dir, + cache_setting, + text_only_progress_bar: progress_bar, + maybe_node_modules_path, + npm_system_info: Default::default(), + package_json_deps_provider: Arc::new( + // this is only used for installing packages, which isn't necessary with deno compile + PackageJsonInstallDepsProvider::empty(), + ), + // create an npmrc that uses the fake npm_registry_url to resolve packages + npmrc: 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(), + }), + }, + )) + .await?; + (fs, npm_resolver, Some(vfs_root_dir_path)) + } + Some(binary::NodeModules::Byonm { + root_node_modules_dir, + }) => { + let vfs_root_dir_path = root_path.clone(); + let vfs = load_npm_vfs(vfs_root_dir_path.clone()) + .context("Failed to load vfs.")?; + let root_node_modules_dir = vfs.root().join(root_node_modules_dir); + let fs = Arc::new(DenoCompileFileSystem::new(vfs)) + as Arc<dyn deno_fs::FileSystem>; + let npm_resolver = create_cli_npm_resolver( + CliNpmResolverCreateOptions::Byonm(CliNpmResolverByonmCreateOptions { + fs: fs.clone(), + root_node_modules_dir, + }), + ) + .await?; + (fs, npm_resolver, Some(vfs_root_dir_path)) + } + None => { + let fs = Arc::new(deno_fs::RealFs) as Arc<dyn deno_fs::FileSystem>; + let npm_resolver = + create_cli_npm_resolver(CliNpmResolverCreateOptions::Managed( + CliNpmResolverManagedCreateOptions { + snapshot: CliNpmResolverManagedSnapshotOption::Specified(None), + maybe_lockfile: None, + fs: fs.clone(), + http_client_provider: http_client_provider.clone(), + npm_global_cache_dir, + cache_setting, + text_only_progress_bar: progress_bar, + maybe_node_modules_path: None, + npm_system_info: Default::default(), + package_json_deps_provider: Arc::new( + // this is only used for installing packages, which isn't necessary with deno compile + PackageJsonInstallDepsProvider::empty(), + ), + // Packages from different registries are already inlined in the ESZip, + // so no need to create actual `.npmrc` configuration. + npmrc: create_default_npmrc(), + }, + )) + .await?; + (fs, npm_resolver, None) + } + }; let has_node_modules_dir = npm_resolver.root_node_modules_path().is_some(); let node_resolver = Arc::new(NodeResolver::new( @@ -471,9 +545,42 @@ pub async fn run( node_resolver.clone(), npm_resolver.clone().into_npm_resolver(), )); - let maybe_import_map = metadata.maybe_import_map.map(|(base, source)| { - Arc::new(parse_from_json(base, &source).unwrap().import_map) - }); + let workspace_resolver = { + let import_map = match metadata.workspace_resolver.import_map { + Some(import_map) => Some( + import_map::parse_from_json_with_options( + root_dir_url.join(&import_map.specifier).unwrap(), + &import_map.json, + import_map::ImportMapOptions { + address_hook: None, + expand_imports: true, + }, + )? + .import_map, + ), + None => None, + }; + let pkg_jsons = metadata + .workspace_resolver + .package_jsons + .into_iter() + .map(|(relative_path, json)| { + let path = root_dir_url + .join(&relative_path) + .unwrap() + .to_file_path() + .unwrap(); + let pkg_json = + deno_config::package_json::PackageJson::load_from_value(path, json); + Arc::new(pkg_json) + }) + .collect(); + WorkspaceResolver::new_raw( + import_map, + pkg_jsons, + metadata.workspace_resolver.pkg_json_resolution, + ) + }; let cli_node_resolver = Arc::new(CliNodeResolver::new( Some(cjs_resolutions.clone()), fs.clone(), @@ -482,11 +589,11 @@ pub async fn run( )); let module_loader_factory = StandaloneModuleLoaderFactory { shared: Arc::new(SharedModuleLoaderState { - eszip, - mapped_specifier_resolver: MappedSpecifierResolver::new( - maybe_import_map.clone(), - package_json_deps_provider.clone(), - ), + eszip: WorkspaceEszip { + eszip, + root_dir_url, + }, + workspace_resolver, node_resolver: cli_node_resolver.clone(), npm_module_loader: Arc::new(NpmModuleLoader::new( cjs_resolutions, @@ -498,7 +605,6 @@ pub async fn run( }; let permissions = { - let maybe_cwd = std::env::current_dir().ok(); let mut permissions = metadata.permissions.to_options(maybe_cwd.as_deref())?; // if running with an npm vfs, grant read access to it @@ -561,7 +667,7 @@ pub async fn run( is_npm_main: main_module.scheme() == "npm", skip_op_registration: true, location: metadata.location, - argv0: NpmPackageReqReference::from_specifier(main_module) + argv0: NpmPackageReqReference::from_specifier(&main_module) .ok() .map(|req_ref| npm_pkg_req_ref_to_binary_command(&req_ref)) .or(std::env::args().next()), @@ -571,7 +677,6 @@ pub async fn run( unsafely_ignore_certificate_errors: metadata .unsafely_ignore_certificate_errors, unstable: metadata.unstable_config.legacy_flag_enabled, - maybe_root_package_json_deps: package_json_deps_provider.deps().cloned(), create_hmr_runner: None, create_coverage_collector: None, }, @@ -592,11 +697,7 @@ pub async fn run( deno_core::JsRuntime::init_platform(None); let mut worker = worker_factory - .create_main_worker( - WorkerExecutionMode::Run, - main_module.clone(), - permissions, - ) + .create_main_worker(WorkerExecutionMode::Run, main_module, permissions) .await?; let exit_code = worker.run().await?; diff --git a/cli/standalone/virtual_fs.rs b/cli/standalone/virtual_fs.rs index 3e6823d50..ee91b9f7f 100644 --- a/cli/standalone/virtual_fs.rs +++ b/cli/standalone/virtual_fs.rs @@ -12,6 +12,7 @@ use std::path::PathBuf; use std::rc::Rc; use std::sync::Arc; +use deno_core::anyhow::anyhow; use deno_core::anyhow::Context; use deno_core::error::AnyError; use deno_core::parking_lot::Mutex; @@ -55,9 +56,8 @@ impl VfsBuilder { root_dir: VirtualDirectory { name: root_path .file_stem() - .unwrap() - .to_string_lossy() - .into_owned(), + .map(|s| s.to_string_lossy().into_owned()) + .unwrap_or("root".to_string()), entries: Vec::new(), }, root_path, @@ -67,13 +67,19 @@ impl VfsBuilder { }) } - pub fn set_root_dir_name(&mut self, name: String) { - self.root_dir.name = name; + pub fn with_root_dir<R>( + &mut self, + with_root: impl FnOnce(&mut VirtualDirectory) -> R, + ) -> R { + with_root(&mut self.root_dir) } pub fn add_dir_recursive(&mut self, path: &Path) -> Result<(), AnyError> { - let path = canonicalize_path(path)?; - self.add_dir_recursive_internal(&path) + let target_path = canonicalize_path(path)?; + if path != target_path { + self.add_symlink(path, &target_path)?; + } + self.add_dir_recursive_internal(&target_path) } fn add_dir_recursive_internal( @@ -92,7 +98,7 @@ impl VfsBuilder { if file_type.is_dir() { self.add_dir_recursive_internal(&path)?; } else if file_type.is_file() { - self.add_file_at_path(&path)?; + self.add_file_at_path_not_symlink(&path)?; } else if file_type.is_symlink() { match util::fs::canonicalize_path(&path) { Ok(target) => { @@ -175,6 +181,17 @@ impl VfsBuilder { } pub fn add_file_at_path(&mut self, path: &Path) -> Result<(), AnyError> { + let target_path = canonicalize_path(path)?; + if target_path != path { + self.add_symlink(path, &target_path)?; + } + self.add_file_at_path_not_symlink(&target_path) + } + + pub fn add_file_at_path_not_symlink( + &mut self, + path: &Path, + ) -> Result<(), AnyError> { let file_bytes = std::fs::read(path) .with_context(|| format!("Reading {}", path.display()))?; self.add_file(path, file_bytes) @@ -195,7 +212,9 @@ impl VfsBuilder { let name = path.file_name().unwrap().to_string_lossy(); let data_len = data.len(); match dir.entries.binary_search_by(|e| e.name().cmp(&name)) { - Ok(_) => unreachable!(), + Ok(_) => { + // already added, just ignore + } Err(insert_index) => { dir.entries.insert( insert_index, @@ -228,6 +247,10 @@ impl VfsBuilder { target.display() ); let dest = self.path_relative_root(target)?; + if dest == self.path_relative_root(path)? { + // it's the same, ignore + return Ok(()); + } let dir = self.add_dir(path.parent().unwrap())?; let name = path.file_name().unwrap().to_string_lossy(); match dir.entries.binary_search_by(|e| e.name().cmp(&name)) { |