summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/args/mod.rs2
-rw-r--r--cli/factory.rs2
-rw-r--r--cli/jsr.rs8
-rw-r--r--cli/lsp/jsr.rs23
-rw-r--r--cli/lsp/language_server.rs7
-rw-r--r--cli/lsp/npm.rs38
-rw-r--r--cli/npm/mod.rs65
-rw-r--r--cli/tools/registry/pm.rs34
-rw-r--r--tests/integration/pm_tests.rs23
9 files changed, 135 insertions, 67 deletions
diff --git a/cli/args/mod.rs b/cli/args/mod.rs
index a4904d39d..af681104c 100644
--- a/cli/args/mod.rs
+++ b/cli/args/mod.rs
@@ -75,7 +75,7 @@ use deno_config::FmtConfig;
use deno_config::LintConfig;
use deno_config::TestConfig;
-pub fn npm_registry_default_url() -> &'static Url {
+pub fn npm_registry_url() -> &'static Url {
static NPM_REGISTRY_DEFAULT_URL: Lazy<Url> = Lazy::new(|| {
let env_var_name = "NPM_CONFIG_REGISTRY";
if let Ok(registry_url) = std::env::var(env_var_name) {
diff --git a/cli/factory.rs b/cli/factory.rs
index eb025a558..5bd5fe149 100644
--- a/cli/factory.rs
+++ b/cli/factory.rs
@@ -443,7 +443,7 @@ impl CliFactory {
self.package_json_deps_provider().clone(),
),
npm_system_info: self.options.npm_system_info(),
- npm_registry_url: crate::args::npm_registry_default_url().to_owned(),
+ npm_registry_url: crate::args::npm_registry_url().to_owned(),
})
}).await
}.boxed_local())
diff --git a/cli/jsr.rs b/cli/jsr.rs
index d741e6ace..1f030ce70 100644
--- a/cli/jsr.rs
+++ b/cli/jsr.rs
@@ -226,7 +226,7 @@ impl JsrFetchResolver {
if let Some(info) = self.info_by_name.get(name) {
return info.value().clone();
}
- let read_cached_package_info = || async {
+ let fetch_package_info = || async {
let meta_url = jsr_url().join(&format!("{}/meta.json", name)).ok()?;
let file = self
.file_fetcher
@@ -235,7 +235,7 @@ impl JsrFetchResolver {
.ok()?;
serde_json::from_slice::<JsrPackageInfo>(&file.source).ok()
};
- let info = read_cached_package_info().await.map(Arc::new);
+ let info = fetch_package_info().await.map(Arc::new);
self.info_by_name.insert(name.to_string(), info.clone());
info
}
@@ -247,7 +247,7 @@ impl JsrFetchResolver {
if let Some(info) = self.info_by_nv.get(nv) {
return info.value().clone();
}
- let read_cached_package_version_info = || async {
+ let fetch_package_version_info = || async {
let meta_url = jsr_url()
.join(&format!("{}/{}_meta.json", &nv.name, &nv.version))
.ok()?;
@@ -258,7 +258,7 @@ impl JsrFetchResolver {
.ok()?;
partial_jsr_package_version_info_from_slice(&file.source).ok()
};
- let info = read_cached_package_version_info().await.map(Arc::new);
+ let info = fetch_package_version_info().await.map(Arc::new);
self.info_by_nv.insert(nv.clone(), info.clone());
info
}
diff --git a/cli/lsp/jsr.rs b/cli/lsp/jsr.rs
index 29ecec60b..a9c35aad6 100644
--- a/cli/lsp/jsr.rs
+++ b/cli/lsp/jsr.rs
@@ -15,20 +15,18 @@ use std::sync::Arc;
use super::search::PackageSearchApi;
-#[derive(Debug, Clone)]
+#[derive(Debug)]
pub struct CliJsrSearchApi {
file_fetcher: FileFetcher,
- /// We only store this here so the completion system has access to a resolver
- /// that always uses the global cache.
- resolver: Arc<JsrFetchResolver>,
- search_cache: Arc<DashMap<String, Arc<Vec<String>>>>,
- versions_cache: Arc<DashMap<String, Arc<Vec<Version>>>>,
- exports_cache: Arc<DashMap<PackageNv, Arc<Vec<String>>>>,
+ resolver: JsrFetchResolver,
+ search_cache: DashMap<String, Arc<Vec<String>>>,
+ versions_cache: DashMap<String, Arc<Vec<Version>>>,
+ exports_cache: DashMap<PackageNv, Arc<Vec<String>>>,
}
impl CliJsrSearchApi {
pub fn new(file_fetcher: FileFetcher) -> Self {
- let resolver = Arc::new(JsrFetchResolver::new(file_fetcher.clone()));
+ let resolver = JsrFetchResolver::new(file_fetcher.clone());
Self {
file_fetcher,
resolver,
@@ -38,7 +36,7 @@ impl CliJsrSearchApi {
}
}
- pub fn get_resolver(&self) -> &Arc<JsrFetchResolver> {
+ pub fn get_resolver(&self) -> &JsrFetchResolver {
&self.resolver
}
}
@@ -49,12 +47,7 @@ impl PackageSearchApi for CliJsrSearchApi {
if let Some(names) = self.search_cache.get(query) {
return Ok(names.clone());
}
- let mut search_url = jsr_api_url().clone();
- search_url
- .path_segments_mut()
- .map_err(|_| anyhow!("Custom jsr URL cannot be a base."))?
- .pop_if_empty()
- .push("packages");
+ let mut search_url = jsr_api_url().join("packages")?;
search_url.query_pairs_mut().append_pair("query", query);
let file = self
.file_fetcher
diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs
index c22752e9e..3555a0545 100644
--- a/cli/lsp/language_server.rs
+++ b/cli/lsp/language_server.rs
@@ -875,9 +875,8 @@ impl Inner {
None,
);
deps_file_fetcher.set_download_log_level(super::logging::lsp_log_level());
- self.jsr_search_api = CliJsrSearchApi::new(deps_file_fetcher);
- self.npm.search_api =
- CliNpmSearchApi::new(self.module_registries.file_fetcher.clone());
+ self.jsr_search_api = CliJsrSearchApi::new(deps_file_fetcher.clone());
+ self.npm.search_api = CliNpmSearchApi::new(deps_file_fetcher);
let maybe_local_cache =
self.config.maybe_vendor_dir_path().map(|local_path| {
Arc::new(LocalLspHttpCache::new(local_path, global_cache.clone()))
@@ -1182,7 +1181,7 @@ async fn create_npm_resolver(
// do not install while resolving in the lsp—leave that to the cache command
package_json_installer:
CliNpmResolverManagedPackageJsonInstallerOption::NoInstall,
- npm_registry_url: crate::args::npm_registry_default_url().to_owned(),
+ npm_registry_url: crate::args::npm_registry_url().to_owned(),
npm_system_info: NpmSystemInfo::default(),
})
})
diff --git a/cli/lsp/npm.rs b/cli/lsp/npm.rs
index 59156fe88..33cf48675 100644
--- a/cli/lsp/npm.rs
+++ b/cli/lsp/npm.rs
@@ -4,29 +4,32 @@ use dashmap::DashMap;
use deno_core::anyhow::anyhow;
use deno_core::error::AnyError;
use deno_core::serde_json;
-use deno_npm::registry::NpmPackageInfo;
use deno_runtime::permissions::PermissionsContainer;
use deno_semver::package::PackageNv;
use deno_semver::Version;
use serde::Deserialize;
use std::sync::Arc;
-use crate::args::npm_registry_default_url;
+use crate::args::npm_registry_url;
use crate::file_fetcher::FileFetcher;
+use crate::npm::NpmFetchResolver;
use super::search::PackageSearchApi;
-#[derive(Debug, Clone)]
+#[derive(Debug)]
pub struct CliNpmSearchApi {
file_fetcher: FileFetcher,
- search_cache: Arc<DashMap<String, Arc<Vec<String>>>>,
- versions_cache: Arc<DashMap<String, Arc<Vec<Version>>>>,
+ resolver: NpmFetchResolver,
+ search_cache: DashMap<String, Arc<Vec<String>>>,
+ versions_cache: DashMap<String, Arc<Vec<Version>>>,
}
impl CliNpmSearchApi {
pub fn new(file_fetcher: FileFetcher) -> Self {
+ let resolver = NpmFetchResolver::new(file_fetcher.clone());
Self {
file_fetcher,
+ resolver,
search_cache: Default::default(),
versions_cache: Default::default(),
}
@@ -39,12 +42,7 @@ impl PackageSearchApi for CliNpmSearchApi {
if let Some(names) = self.search_cache.get(query) {
return Ok(names.clone());
}
- let mut search_url = npm_registry_default_url().clone();
- search_url
- .path_segments_mut()
- .map_err(|_| anyhow!("Custom npm registry URL cannot be a base."))?
- .pop_if_empty()
- .extend("-/v1/search".split('/'));
+ let mut search_url = npm_registry_url().join("-/v1/search")?;
search_url
.query_pairs_mut()
.append_pair("text", &format!("{} boost-exact:false", query));
@@ -62,18 +60,12 @@ impl PackageSearchApi for CliNpmSearchApi {
if let Some(versions) = self.versions_cache.get(name) {
return Ok(versions.clone());
}
- let mut info_url = npm_registry_default_url().clone();
- info_url
- .path_segments_mut()
- .map_err(|_| anyhow!("Custom npm registry URL cannot be a base."))?
- .pop_if_empty()
- .push(name);
- let file = self
- .file_fetcher
- .fetch(&info_url, PermissionsContainer::allow_all())
- .await?;
- let info = serde_json::from_slice::<NpmPackageInfo>(&file.source)?;
- let mut versions = info.versions.into_keys().collect::<Vec<_>>();
+ let info = self
+ .resolver
+ .package_info(name)
+ .await
+ .ok_or_else(|| anyhow!("npm package info not found: {}", name))?;
+ let mut versions = info.versions.keys().cloned().collect::<Vec<_>>();
versions.sort();
versions.reverse();
let versions = Arc::new(versions);
diff --git a/cli/npm/mod.rs b/cli/npm/mod.rs
index cc14bec0a..08c15941a 100644
--- a/cli/npm/mod.rs
+++ b/cli/npm/mod.rs
@@ -8,11 +8,19 @@ mod managed;
use std::path::PathBuf;
use std::sync::Arc;
+use dashmap::DashMap;
use deno_ast::ModuleSpecifier;
use deno_core::error::AnyError;
+use deno_core::serde_json;
+use deno_npm::registry::NpmPackageInfo;
use deno_runtime::deno_node::NpmResolver;
+use deno_runtime::permissions::PermissionsContainer;
+use deno_semver::package::PackageNv;
use deno_semver::package::PackageReq;
+use crate::args::npm_registry_url;
+use crate::file_fetcher::FileFetcher;
+
pub use self::byonm::ByonmCliNpmResolver;
pub use self::byonm::CliNpmResolverByonmCreateOptions;
pub use self::cache_dir::NpmCacheDir;
@@ -87,3 +95,60 @@ pub trait CliNpmResolver: NpmResolver {
/// or `None` if the state currently can't be determined.
fn check_state_hash(&self) -> Option<u64>;
}
+
+#[derive(Debug)]
+pub struct NpmFetchResolver {
+ nv_by_req: DashMap<PackageReq, Option<PackageNv>>,
+ info_by_name: DashMap<String, Option<Arc<NpmPackageInfo>>>,
+ file_fetcher: FileFetcher,
+}
+
+impl NpmFetchResolver {
+ pub fn new(file_fetcher: FileFetcher) -> Self {
+ Self {
+ nv_by_req: Default::default(),
+ info_by_name: Default::default(),
+ file_fetcher,
+ }
+ }
+
+ pub async fn req_to_nv(&self, req: &PackageReq) -> Option<PackageNv> {
+ if let Some(nv) = self.nv_by_req.get(req) {
+ return nv.value().clone();
+ }
+ let maybe_get_nv = || async {
+ let name = req.name.clone();
+ let package_info = self.package_info(&name).await?;
+ // Find the first matching version of the package which is cached.
+ let mut versions = package_info.versions.keys().collect::<Vec<_>>();
+ versions.sort();
+ let version = versions
+ .into_iter()
+ .rev()
+ .find(|v| req.version_req.tag().is_none() && req.version_req.matches(v))
+ .cloned()?;
+ Some(PackageNv { name, version })
+ };
+ let nv = maybe_get_nv().await;
+ self.nv_by_req.insert(req.clone(), nv.clone());
+ nv
+ }
+
+ pub async fn package_info(&self, name: &str) -> Option<Arc<NpmPackageInfo>> {
+ if let Some(info) = self.info_by_name.get(name) {
+ return info.value().clone();
+ }
+ let fetch_package_info = || async {
+ let info_url = npm_registry_url().join(name).ok()?;
+ let file = self
+ .file_fetcher
+ .fetch(&info_url, PermissionsContainer::allow_all())
+ .await
+ .ok()?;
+ serde_json::from_slice::<NpmPackageInfo>(&file.source).ok()
+ };
+ let info = fetch_package_info().await.map(Arc::new);
+ self.info_by_name.insert(name.to_string(), info.clone());
+ info
+ }
+}
diff --git a/cli/tools/registry/pm.rs b/cli/tools/registry/pm.rs
index 0c10c4993..e9044a5d1 100644
--- a/cli/tools/registry/pm.rs
+++ b/cli/tools/registry/pm.rs
@@ -23,6 +23,7 @@ use crate::args::Flags;
use crate::factory::CliFactory;
use crate::file_fetcher::FileFetcher;
use crate::jsr::JsrFetchResolver;
+use crate::npm::NpmFetchResolver;
pub async fn add(flags: Flags, add_flags: AddFlags) -> Result<(), AnyError> {
let cli_factory = CliFactory::from_flags(flags.clone()).await?;
@@ -77,13 +78,18 @@ pub async fn add(flags: Flags, add_flags: AddFlags) -> Result<(), AnyError> {
None,
);
deps_file_fetcher.set_download_log_level(log::Level::Trace);
- let jsr_resolver = Arc::new(JsrFetchResolver::new(deps_file_fetcher));
+ let jsr_resolver = Arc::new(JsrFetchResolver::new(deps_file_fetcher.clone()));
+ let npm_resolver = Arc::new(NpmFetchResolver::new(deps_file_fetcher));
let package_futures = package_reqs
.into_iter()
.map(move |package_req| {
- find_package_and_select_version_for_req(jsr_resolver.clone(), package_req)
- .boxed_local()
+ find_package_and_select_version_for_req(
+ jsr_resolver.clone(),
+ npm_resolver.clone(),
+ package_req,
+ )
+ .boxed_local()
})
.collect::<Vec<_>>();
@@ -183,6 +189,7 @@ enum PackageAndVersion {
async fn find_package_and_select_version_for_req(
jsr_resolver: Arc<JsrFetchResolver>,
+ npm_resolver: Arc<NpmFetchResolver>,
add_package_req: AddPackageReq,
) -> Result<PackageAndVersion, AnyError> {
match add_package_req {
@@ -203,11 +210,22 @@ async fn find_package_and_select_version_for_req(
version_req: format!("{}{}", range_symbol, &nv.version),
}))
}
- AddPackageReq::Npm(pkg_req) => {
- bail!(
- "Adding npm: packages is currently not supported. Package: npm:{}",
- pkg_req.req().name
- );
+ AddPackageReq::Npm(pkg_ref) => {
+ let req = pkg_ref.req();
+ let npm_prefixed_name = format!("npm:{}", &req.name);
+ let Some(nv) = npm_resolver.req_to_nv(req).await else {
+ return Ok(PackageAndVersion::NotFound(npm_prefixed_name));
+ };
+ let range_symbol = if req.version_req.version_text().starts_with('~') {
+ '~'
+ } else {
+ '^'
+ };
+ Ok(PackageAndVersion::Selected(SelectedPackage {
+ import_name: req.name.to_string(),
+ package_name: npm_prefixed_name,
+ version_req: format!("{}{}", range_symbol, &nv.version),
+ }))
}
}
}
diff --git a/tests/integration/pm_tests.rs b/tests/integration/pm_tests.rs
index cc5527c40..668519bdc 100644
--- a/tests/integration/pm_tests.rs
+++ b/tests/integration/pm_tests.rs
@@ -2,9 +2,7 @@
use deno_core::serde_json::json;
use test_util::assert_contains;
-use test_util::env_vars_for_jsr_tests;
-// use test_util::env_vars_for_npm_tests;
-// use test_util::itest;
+use test_util::env_vars_for_jsr_npm_tests;
use test_util::TestContextBuilder;
#[test]
@@ -110,21 +108,24 @@ fn add_multiple() {
}
#[test]
-fn add_not_supported_npm() {
+fn add_npm() {
let context = pm_context_builder().build();
+ let temp_dir = context.temp_dir().path();
- let output = context
- .new_command()
- .args("add @denotest/add npm:express")
- .run();
- output.assert_exit_code(1);
+ let output = context.new_command().args("add npm:chalk@4.1").run();
+ output.assert_exit_code(0);
let output = output.combined_output();
- assert_contains!(output, "error: Adding npm: packages is currently not supported. Package: npm:express");
+ assert_contains!(output, "Add chalk");
+ temp_dir.join("deno.json").assert_matches_json(json!({
+ "imports": {
+ "chalk": "npm:chalk@^4.1.2"
+ }
+ }));
}
fn pm_context_builder() -> TestContextBuilder {
TestContextBuilder::new()
.use_http_server()
- .envs(env_vars_for_jsr_tests())
+ .envs(env_vars_for_jsr_npm_tests())
.use_temp_cwd()
}