/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
cli/tools/pm/cache_deps.rs
276 строк
9 KB
Nathan Whitaker
fix(resolver): scope sloppy import hints to prepared modules (#36221)
23 июл 2026, 04:23
Не верифицирован
23 июл 2026, 04:23
b23cb76
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. use std::borrow::Cow; use std::collections::HashMap; use std::collections::HashSet; use std::sync::Arc; use deno_core::error::AnyError; use deno_core::futures::StreamExt; use deno_core::futures::stream::FuturesUnordered; use deno_core::url::Url; use deno_graph::JsrPackageReqNotFoundError; use deno_graph::packages::JsrPackageVersionInfo; use deno_npm_installer::PackageCaching; use deno_npm_installer::graph::NpmCachingStrategy; use deno_resolver::npm::version_req_matches_including_pre; use deno_semver::Version; use deno_semver::jsr::JsrPackageReqReference; use deno_semver::npm::NpmPackageReqReference; use crate::factory::CliFactory; use crate::graph_container::ModuleGraphContainer; use crate::graph_container::ModuleGraphUpdatePermit; use crate::graph_util::BuildGraphWithNpmOptions; pub struct CacheTopLevelDepsOptions { pub lockfile_only: bool, /// Extra specifiers to resolve and cache in addition to the project's /// declared dependencies. Used by `deno add --no-save` to install packages /// without writing them to the configuration file. pub additional_roots: Vec<Url>, } pub async fn cache_top_level_deps( // todo(dsherret): don't pass the factory into this function. Instead use ctor deps factory: &CliFactory, jsr_resolver: Option<Arc<crate::jsr::JsrFetchResolver>>, options: CacheTopLevelDepsOptions, ) -> Result<(), AnyError> { let _clear_guard = factory .text_only_progress_bar() .deferred_keep_initialize_alive(); let npm_installer = factory.npm_installer().await?; npm_installer .ensure_top_level_package_json_install() .await?; if let Some(lockfile) = factory.maybe_lockfile().await? { lockfile.error_if_changed()?; } // cache as many entries in the import map as we can let resolver = factory.workspace_resolver().await?; let mut maybe_graph_error = Ok(()); let has_import_map = resolver.maybe_import_map().is_some(); if has_import_map || !options.additional_roots.is_empty() { let jsr_resolver = if let Some(resolver) = jsr_resolver { resolver } else { Arc::new(crate::jsr::JsrFetchResolver::new( factory.file_fetcher()?.clone(), factory.jsr_version_resolver()?.clone(), )) }; let mut graph_permit = factory .main_module_graph_container() .await? .acquire_update_permit() .await; let graph = graph_permit.graph_mut(); if let Some(lockfile) = factory.maybe_lockfile().await? { lockfile.fill_graph(graph); } let mut roots = Vec::new(); let mut info_futures = FuturesUnordered::new(); let mut seen_reqs = HashSet::new(); let workspace_npm_packages = resolver .package_jsons() .filter_map(|pkg_json| { pkg_json .name .as_deref() .and_then(|name| Some((name, pkg_json.version.as_deref()?))) }) .collect::<HashMap<_, _>>(); let workspace_jsr_packages = resolver.jsr_packages(); let import_map_entries = resolver .maybe_import_map() .map(|import_map| { import_map .imports() .entries() .chain( import_map .scopes() .flat_map(|scope| scope.imports.entries()), ) .collect::<Vec<_>>() }) .unwrap_or_default(); for entry in import_map_entries { let Some(specifier) = entry.value else { continue; }; match specifier.scheme() { "jsr" => { let specifier_str = specifier.as_str(); let Ok(req_ref) = JsrPackageReqReference::from_str(specifier_str) else { continue; }; if workspace_jsr_packages .iter() .any(|pkg| pkg.matches_req(req_ref.req())) { // do not install a workspace jsr package continue; } if let Some(sub_path) = req_ref.sub_path() { if sub_path.ends_with('/') { continue; } roots.push(specifier.clone()); continue; } if !seen_reqs.insert(req_ref.req().clone()) { continue; } if req_ref.req().version_req.tag().is_some() { // JSR has no dist-tags, so this dependency can never resolve. // Warn instead of failing so the rest of the project still // installs; the graph build reports a proper error when the // specifier is actually imported. Skipping here also keeps // tagged reqs away from VersionReq::matches below, which // panics on tags. log::warn!( "{} Ignoring \"jsr:{}\". Version tags are not supported in jsr specifiers.", deno_terminal::colors::yellow("Warning"), req_ref.req(), ); continue; } let resolved_req = graph.packages.mappings().get(req_ref.req()); let resolved_req = resolved_req.and_then(|nv| { // the version might end up being upgraded to a newer version that's already in // the graph (due to a reverted change), in which case our exports could end up // being wrong. to avoid that, see if there's a newer version that matches the version // req. let versions = graph.packages.versions_by_name(&req_ref.req().name)?; let mut best = nv; for version in versions { if version.version > best.version && req_ref.req().version_req.matches(&version.version) { best = version; } } Some(best) }); let jsr_resolver = jsr_resolver.clone(); info_futures.push(async move { let nv = if let Some(nv) = resolved_req { Cow::Borrowed(nv) } else if let Some(nv) = jsr_resolver.req_to_nv(req_ref.req()).await? { Cow::Owned(nv) } else { return Result::< Option<(Url, Arc<JsrPackageVersionInfo>)>, JsrPackageReqNotFoundError, >::Ok(None); }; if let Some(info) = jsr_resolver.package_version_info(&nv).await { return Ok(Some((specifier.clone(), info))); } Ok(None) }); } "npm" => { let Ok(req_ref) = NpmPackageReqReference::from_str(specifier.as_str()) else { continue; }; let version = workspace_npm_packages.get(&*req_ref.req().name); if let Some(version) = version { let Ok(version) = Version::parse_from_npm(version) else { continue; }; let version_req = &req_ref.req().version_req; // match prereleases too: a workspace member is provided explicitly // by the user, so e.g. `0.40.0-pre` should still resolve to it // instead of being fetched from the registry. if version_req_matches_including_pre(version_req, &version) { // if version req matches the workspace package's version, use that // (so it doesn't need to be installed) continue; } } roots.push(specifier.clone()) } _ => { if entry.key.ends_with('/') && specifier.as_str().ends_with('/') { continue; } if specifier.scheme() == "file" && let Ok(path) = specifier.to_file_path() && !path.is_file() { continue; } roots.push(specifier.clone()); } } } while let Some(info_future) = info_futures.next().await { if let Some((specifier, info)) = info_future? { let exports = info.exports(); for (k, _) in exports { if let Ok(spec) = specifier.join(k) { roots.push(spec); } } } } drop(info_futures); // Packages requested via `--no-save`: install them without declaring them. roots.extend(options.additional_roots.iter().cloned()); let graph_builder = factory.module_graph_builder().await?; graph_builder .build_graph_roots_with_npm_resolution( graph, roots.clone(), BuildGraphWithNpmOptions { loader: None, is_dynamic: false, npm_caching: NpmCachingStrategy::Manual, }, ) .await?; maybe_graph_error = graph_builder.graph_roots_valid( graph, &roots, crate::graph_util::GraphRootsValidOptions { allow_unknown_media_types: true, allow_unknown_jsr_exports: true, allow_sloppy_imports_hints_for_unreferenced_roots: true, }, ); } if options.lockfile_only { // do a resolution install if the npm snapshot is in a // pending state due to a config file change npm_installer.install_resolution_if_pending().await?; } else { npm_installer.cache_packages(PackageCaching::All).await?; } maybe_graph_error?; Ok(()) }