0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-02-01 20:25:12 -05:00

fix(npm): cache bust npm specifiers more aggressively (#18636)

Part 1: #18622 
Part 2: This PR

Closes #16901

---------

Co-authored-by: Luca Casonato <hello@lcas.dev>
This commit is contained in:
David Sherret 2023-04-12 08:36:11 -04:00 committed by Levente Kurusa
parent b183737fc9
commit 4fab252153
14 changed files with 289 additions and 143 deletions

16
Cargo.lock generated
View file

@ -935,9 +935,9 @@ dependencies = [
[[package]] [[package]]
name = "deno_doc" name = "deno_doc"
version = "0.60.0" version = "0.61.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "029ec20ba7a3c9d55597db7afa20576367ea8d70371a97b84f9909014cfe110f" checksum = "ae1ba6a3137da0ed19838c09c6fb9c7a07af642786b298fc29e088cc5643e729"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"deno_ast", "deno_ast",
@ -953,9 +953,9 @@ dependencies = [
[[package]] [[package]]
name = "deno_emit" name = "deno_emit"
version = "0.18.0" version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8004481b057addda0779edd5adb47e5ac9db7ae431c879300d22d535cc83cfae" checksum = "c01676751a0ee50ebad80734735f9a28c6eabb164050034e10956b72af563941"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64 0.13.1", "base64 0.13.1",
@ -1017,9 +1017,9 @@ dependencies = [
[[package]] [[package]]
name = "deno_graph" name = "deno_graph"
version = "0.46.0" version = "0.47.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fb5531f3c2be6926e51ce5888fcffa434ca83516c53d26563882533aee871d0" checksum = "3e81896f3abfe0c6410518cc0285155e6faa2aa87ca8da32fbf1670ef1254ea2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"data-url", "data-url",
@ -1734,9 +1734,9 @@ dependencies = [
[[package]] [[package]]
name = "eszip" name = "eszip"
version = "0.39.0" version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "207f6568e7dde0c18eb306af104c4e7fe91f77eb99afeffd13f9c7735de4bb4d" checksum = "5a0a0addd73b5077a769e23a914a68ec8862c310b6127e8383505f676684f65c"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64 0.21.0", "base64 0.21.0",

View file

@ -42,9 +42,9 @@ winres.workspace = true
[dependencies] [dependencies]
deno_ast = { workspace = true, features = ["bundler", "cjs", "codegen", "dep_graph", "module_specifier", "proposal", "react", "sourcemap", "transforms", "typescript", "view", "visit"] } deno_ast = { workspace = true, features = ["bundler", "cjs", "codegen", "dep_graph", "module_specifier", "proposal", "react", "sourcemap", "transforms", "typescript", "view", "visit"] }
deno_core = { workspace = true, features = ["include_js_files_for_snapshotting"] } deno_core = { workspace = true, features = ["include_js_files_for_snapshotting"] }
deno_doc = "0.60.0" deno_doc = "0.61.0"
deno_emit = "0.18.0" deno_emit = "0.19.0"
deno_graph = "=0.46.0" deno_graph = "=0.47.1"
deno_lint = { version = "0.43.0", features = ["docs"] } deno_lint = { version = "0.43.0", features = ["docs"] }
deno_lockfile.workspace = true deno_lockfile.workspace = true
deno_npm = "0.2.0" deno_npm = "0.2.0"
@ -70,7 +70,7 @@ dprint-plugin-markdown = "=0.15.2"
dprint-plugin-typescript = "=0.84.0" dprint-plugin-typescript = "=0.84.0"
encoding_rs.workspace = true encoding_rs.workspace = true
env_logger = "=0.9.0" env_logger = "=0.9.0"
eszip = "=0.39.0" eszip = "=0.40.0"
fancy-regex = "=0.10.0" fancy-regex = "=0.10.0"
flate2.workspace = true flate2.workspace = true
fs3.workspace = true fs3.workspace = true

View file

@ -64,8 +64,8 @@ use std::sync::Arc;
use crate::cache::DenoDir; use crate::cache::DenoDir;
use crate::file_fetcher::FileFetcher; use crate::file_fetcher::FileFetcher;
use crate::npm::CliNpmRegistryApi;
use crate::npm::NpmProcessState; use crate::npm::NpmProcessState;
use crate::npm::NpmRegistry;
use crate::util::fs::canonicalize_path_maybe_not_exists; use crate::util::fs::canonicalize_path_maybe_not_exists;
use crate::version; use crate::version;
@ -744,7 +744,7 @@ impl CliOptions {
pub async fn resolve_npm_resolution_snapshot( pub async fn resolve_npm_resolution_snapshot(
&self, &self,
api: &NpmRegistry, api: &CliNpmRegistryApi,
) -> Result<Option<NpmResolutionSnapshot>, AnyError> { ) -> Result<Option<NpmResolutionSnapshot>, AnyError> {
if let Some(state) = &*NPM_PROCESS_STATE { if let Some(state) = &*NPM_PROCESS_STATE {
// TODO(bartlomieju): remove this clone // TODO(bartlomieju): remove this clone

View file

@ -20,8 +20,8 @@ use crate::lsp::logging::lsp_warn;
use crate::node; use crate::node;
use crate::node::node_resolve_npm_reference; use crate::node::node_resolve_npm_reference;
use crate::node::NodeResolution; use crate::node::NodeResolution;
use crate::npm::CliNpmRegistryApi;
use crate::npm::NpmPackageResolver; use crate::npm::NpmPackageResolver;
use crate::npm::NpmRegistry;
use crate::npm::NpmResolution; use crate::npm::NpmResolution;
use crate::npm::PackageJsonDepsInstaller; use crate::npm::PackageJsonDepsInstaller;
use crate::resolver::CliGraphResolver; use crate::resolver::CliGraphResolver;
@ -1166,7 +1166,7 @@ impl Documents {
maybe_import_map: Option<Arc<import_map::ImportMap>>, maybe_import_map: Option<Arc<import_map::ImportMap>>,
maybe_config_file: Option<&ConfigFile>, maybe_config_file: Option<&ConfigFile>,
maybe_package_json: Option<&PackageJson>, maybe_package_json: Option<&PackageJson>,
npm_registry_api: NpmRegistry, npm_registry_api: CliNpmRegistryApi,
npm_resolution: NpmResolution, npm_resolution: NpmResolution,
) { ) {
fn calculate_resolver_config_hash( fn calculate_resolver_config_hash(
@ -1864,7 +1864,7 @@ console.log(b, "hello deno");
#[test] #[test]
fn test_documents_refresh_dependencies_config_change() { fn test_documents_refresh_dependencies_config_change() {
let npm_registry_api = NpmRegistry::new_uninitialized(); let npm_registry_api = CliNpmRegistryApi::new_uninitialized();
let npm_resolution = let npm_resolution =
NpmResolution::new(npm_registry_api.clone(), None, None); NpmResolution::new(npm_registry_api.clone(), None, None);

View file

@ -79,9 +79,9 @@ use crate::graph_util;
use crate::http_util::HttpClient; use crate::http_util::HttpClient;
use crate::lsp::urls::LspUrlKind; use crate::lsp::urls::LspUrlKind;
use crate::npm::create_npm_fs_resolver; use crate::npm::create_npm_fs_resolver;
use crate::npm::CliNpmRegistryApi;
use crate::npm::NpmCache; use crate::npm::NpmCache;
use crate::npm::NpmPackageResolver; use crate::npm::NpmPackageResolver;
use crate::npm::NpmRegistry;
use crate::npm::NpmResolution; use crate::npm::NpmResolution;
use crate::proc_state::ProcState; use crate::proc_state::ProcState;
use crate::tools::fmt::format_file; use crate::tools::fmt::format_file;
@ -145,7 +145,7 @@ pub struct Inner {
/// A lazily create "server" for handling test run requests. /// A lazily create "server" for handling test run requests.
maybe_testing_server: Option<testing::TestServer>, maybe_testing_server: Option<testing::TestServer>,
/// Npm's registry api. /// Npm's registry api.
npm_api: NpmRegistry, npm_api: CliNpmRegistryApi,
/// Npm cache /// Npm cache
npm_cache: NpmCache, npm_cache: NpmCache,
/// Npm resolution that is stored in memory. /// Npm resolution that is stored in memory.
@ -417,8 +417,13 @@ impl LanguageServer {
fn create_lsp_structs( fn create_lsp_structs(
dir: &DenoDir, dir: &DenoDir,
http_client: HttpClient, http_client: HttpClient,
) -> (NpmRegistry, NpmCache, NpmPackageResolver, NpmResolution) { ) -> (
let registry_url = NpmRegistry::default_url(); CliNpmRegistryApi,
NpmCache,
NpmPackageResolver,
NpmResolution,
) {
let registry_url = CliNpmRegistryApi::default_url();
let progress_bar = ProgressBar::new(ProgressBarStyle::TextOnly); let progress_bar = ProgressBar::new(ProgressBarStyle::TextOnly);
let npm_cache = NpmCache::from_deno_dir( let npm_cache = NpmCache::from_deno_dir(
dir, dir,
@ -430,7 +435,7 @@ fn create_lsp_structs(
http_client.clone(), http_client.clone(),
progress_bar.clone(), progress_bar.clone(),
); );
let api = NpmRegistry::new( let api = CliNpmRegistryApi::new(
registry_url.clone(), registry_url.clone(),
npm_cache.clone(), npm_cache.clone(),
http_client, http_client,

View file

@ -1,33 +1,69 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
use std::future::Future;
use std::sync::Arc; use std::sync::Arc;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::futures::stream::FuturesOrdered; use deno_core::futures::stream::FuturesOrdered;
use deno_core::futures::StreamExt; use deno_core::futures::StreamExt;
use deno_npm::registry::NpmRegistryApi; use deno_npm::registry::NpmRegistryApi;
use deno_npm::registry::NpmRegistryPackageInfoLoadError;
use deno_semver::npm::NpmPackageReq;
use crate::args::package_json::PackageJsonDeps; use crate::args::package_json::PackageJsonDeps;
use crate::util::sync::AtomicFlag; use crate::util::sync::AtomicFlag;
use super::NpmRegistry; use super::CliNpmRegistryApi;
use super::NpmResolution; use super::NpmResolution;
#[derive(Debug)] #[derive(Debug)]
struct PackageJsonDepsInstallerInner { struct PackageJsonDepsInstallerInner {
has_installed_flag: AtomicFlag, has_installed_flag: AtomicFlag,
npm_registry_api: NpmRegistry, npm_registry_api: CliNpmRegistryApi,
npm_resolution: NpmResolution, npm_resolution: NpmResolution,
package_deps: PackageJsonDeps, package_deps: PackageJsonDeps,
} }
impl PackageJsonDepsInstallerInner {
pub fn reqs(&self) -> Vec<&NpmPackageReq> {
let mut package_reqs = self
.package_deps
.values()
.filter_map(|r| r.as_ref().ok())
.collect::<Vec<_>>();
package_reqs.sort(); // deterministic resolution
package_reqs
}
pub fn reqs_with_info_futures(
&self,
) -> FuturesOrdered<
impl Future<
Output = Result<
(&NpmPackageReq, Arc<deno_npm::registry::NpmPackageInfo>),
NpmRegistryPackageInfoLoadError,
>,
>,
> {
let package_reqs = self.reqs();
FuturesOrdered::from_iter(package_reqs.into_iter().map(|req| {
let api = self.npm_registry_api.clone();
async move {
let info = api.package_info(&req.name).await?;
Ok::<_, NpmRegistryPackageInfoLoadError>((req, info))
}
}))
}
}
/// Holds and controls installing dependencies from package.json. /// Holds and controls installing dependencies from package.json.
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct PackageJsonDepsInstaller(Option<Arc<PackageJsonDepsInstallerInner>>); pub struct PackageJsonDepsInstaller(Option<Arc<PackageJsonDepsInstallerInner>>);
impl PackageJsonDepsInstaller { impl PackageJsonDepsInstaller {
pub fn new( pub fn new(
npm_registry_api: NpmRegistry, npm_registry_api: CliNpmRegistryApi,
npm_resolution: NpmResolution, npm_resolution: NpmResolution,
deps: Option<PackageJsonDeps>, deps: Option<PackageJsonDeps>,
) -> Self { ) -> Self {
@ -57,27 +93,23 @@ impl PackageJsonDepsInstaller {
return Ok(()); // already installed by something else return Ok(()); // already installed by something else
} }
let mut package_reqs = inner let mut reqs_with_info_futures = inner.reqs_with_info_futures();
.package_deps
.values()
.filter_map(|r| r.as_ref().ok())
.collect::<Vec<_>>();
package_reqs.sort(); // deterministic resolution
let mut req_with_infos = while let Some(result) = reqs_with_info_futures.next().await {
FuturesOrdered::from_iter(package_reqs.into_iter().map(|req| {
let api = inner.npm_registry_api.clone();
async move {
let info = api.package_info(&req.name).await?;
Ok::<_, AnyError>((req, info))
}
}));
while let Some(result) = req_with_infos.next().await {
let (req, info) = result?; let (req, info) = result?;
inner let result = inner
.npm_resolution .npm_resolution
.resolve_package_req_as_pending_with_info(req, &info)?; .resolve_package_req_as_pending_with_info(req, &info);
if let Err(err) = result {
if inner.npm_registry_api.mark_force_reload() {
log::debug!("Failed to resolve package. Retrying. Error: {err:#}");
// re-initialize
inner.npm_registry_api.clear_memory_cache();
reqs_with_info_futures = inner.reqs_with_info_futures();
} else {
return Err(err.into());
}
}
} }
Ok(()) Ok(())

View file

@ -10,7 +10,7 @@ mod tarball;
pub use cache::should_sync_download; pub use cache::should_sync_download;
pub use cache::NpmCache; pub use cache::NpmCache;
pub use installer::PackageJsonDepsInstaller; pub use installer::PackageJsonDepsInstaller;
pub use registry::NpmRegistry; pub use registry::CliNpmRegistryApi;
pub use resolution::NpmResolution; pub use resolution::NpmResolution;
pub use resolvers::create_npm_fs_resolver; pub use resolvers::create_npm_fs_resolver;
pub use resolvers::NpmPackageResolver; pub use resolvers::NpmPackageResolver;

View file

@ -53,9 +53,9 @@ static NPM_REGISTRY_DEFAULT_URL: Lazy<Url> = Lazy::new(|| {
}); });
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct NpmRegistry(Option<Arc<NpmRegistryApiInner>>); pub struct CliNpmRegistryApi(Option<Arc<CliNpmRegistryApiInner>>);
impl NpmRegistry { impl CliNpmRegistryApi {
pub fn default_url() -> &'static Url { pub fn default_url() -> &'static Url {
&NPM_REGISTRY_DEFAULT_URL &NPM_REGISTRY_DEFAULT_URL
} }
@ -66,7 +66,7 @@ impl NpmRegistry {
http_client: HttpClient, http_client: HttpClient,
progress_bar: ProgressBar, progress_bar: ProgressBar,
) -> Self { ) -> Self {
Self(Some(Arc::new(NpmRegistryApiInner { Self(Some(Arc::new(CliNpmRegistryApiInner {
base_url, base_url,
cache, cache,
force_reload_flag: Default::default(), force_reload_flag: Default::default(),
@ -104,14 +104,18 @@ impl NpmRegistry {
/// ///
/// Returns true if it was successfully set for the first time. /// Returns true if it was successfully set for the first time.
pub fn mark_force_reload(&self) -> bool { pub fn mark_force_reload(&self) -> bool {
// never force reload the registry information if reloading is disabled // never force reload the registry information if reloading
if matches!(self.inner().cache.cache_setting(), CacheSetting::Only) { // is disabled or if we're already reloading
if matches!(
self.inner().cache.cache_setting(),
CacheSetting::Only | CacheSetting::ReloadAll
) {
return false; return false;
} }
self.inner().force_reload_flag.raise() self.inner().force_reload_flag.raise()
} }
fn inner(&self) -> &Arc<NpmRegistryApiInner> { fn inner(&self) -> &Arc<CliNpmRegistryApiInner> {
// this panicking indicates a bug in the code where this // this panicking indicates a bug in the code where this
// wasn't initialized // wasn't initialized
self.0.as_ref().unwrap() self.0.as_ref().unwrap()
@ -122,7 +126,7 @@ static SYNC_DOWNLOAD_TASK_QUEUE: Lazy<TaskQueue> =
Lazy::new(TaskQueue::default); Lazy::new(TaskQueue::default);
#[async_trait] #[async_trait]
impl NpmRegistryApi for NpmRegistry { impl NpmRegistryApi for CliNpmRegistryApi {
async fn package_info( async fn package_info(
&self, &self,
name: &str, name: &str,
@ -147,16 +151,17 @@ impl NpmRegistryApi for NpmRegistry {
} }
} }
type CacheItemPendingResult =
Result<Option<Arc<NpmPackageInfo>>, Arc<AnyError>>;
#[derive(Debug)] #[derive(Debug)]
enum CacheItem { enum CacheItem {
Pending( Pending(Shared<BoxFuture<'static, CacheItemPendingResult>>),
Shared<BoxFuture<'static, Result<Option<Arc<NpmPackageInfo>>, String>>>,
),
Resolved(Option<Arc<NpmPackageInfo>>), Resolved(Option<Arc<NpmPackageInfo>>),
} }
#[derive(Debug)] #[derive(Debug)]
struct NpmRegistryApiInner { struct CliNpmRegistryApiInner {
base_url: Url, base_url: Url,
cache: NpmCache, cache: NpmCache,
force_reload_flag: AtomicFlag, force_reload_flag: AtomicFlag,
@ -166,7 +171,7 @@ struct NpmRegistryApiInner {
progress_bar: ProgressBar, progress_bar: ProgressBar,
} }
impl NpmRegistryApiInner { impl CliNpmRegistryApiInner {
pub async fn maybe_package_info( pub async fn maybe_package_info(
self: &Arc<Self>, self: &Arc<Self>,
name: &str, name: &str,
@ -196,9 +201,15 @@ impl NpmRegistryApiInner {
let future = { let future = {
let api = self.clone(); let api = self.clone();
let name = name.to_string(); let name = name.to_string();
async move { api.load_package_info_from_registry(&name).await } async move {
.boxed() api
.shared() .load_package_info_from_registry(&name)
.await
.map(|info| info.map(Arc::new))
.map_err(Arc::new)
}
.boxed()
.shared()
}; };
mem_cache mem_cache
.insert(name.to_string(), CacheItem::Pending(future.clone())); .insert(name.to_string(), CacheItem::Pending(future.clone()));
@ -220,11 +231,11 @@ impl NpmRegistryApiInner {
Err(err) => { Err(err) => {
// purge the item from the cache so it loads next time // purge the item from the cache so it loads next time
self.mem_cache.lock().remove(name); self.mem_cache.lock().remove(name);
Err(anyhow!("{}", err)) Err(anyhow!("{:#}", err))
} }
} }
} else { } else {
Ok(future.await.map_err(|err| anyhow!("{}", err))?) Ok(future.await.map_err(|err| anyhow!("{:#}", err))?)
} }
} }
@ -303,7 +314,7 @@ impl NpmRegistryApiInner {
async fn load_package_info_from_registry( async fn load_package_info_from_registry(
&self, &self,
name: &str, name: &str,
) -> Result<Option<Arc<NpmPackageInfo>>, String> { ) -> Result<Option<NpmPackageInfo>, AnyError> {
self self
.load_package_info_from_registry_inner(name) .load_package_info_from_registry_inner(name)
.await .await
@ -314,9 +325,6 @@ impl NpmRegistryApiInner {
name name
) )
}) })
.map(|info| info.map(Arc::new))
// make cloneable
.map_err(|err| format!("{err:#}"))
} }
async fn load_package_info_from_registry_inner( async fn load_package_info_from_registry_inner(

View file

@ -27,7 +27,7 @@ use deno_semver::npm::NpmPackageReqReference;
use crate::args::Lockfile; use crate::args::Lockfile;
use super::registry::NpmRegistry; use super::registry::CliNpmRegistryApi;
/// Handles updating and storing npm resolution in memory where the underlying /// Handles updating and storing npm resolution in memory where the underlying
/// snapshot can be updated concurrently. Additionally handles updating the lockfile /// snapshot can be updated concurrently. Additionally handles updating the lockfile
@ -38,7 +38,7 @@ use super::registry::NpmRegistry;
pub struct NpmResolution(Arc<NpmResolutionInner>); pub struct NpmResolution(Arc<NpmResolutionInner>);
struct NpmResolutionInner { struct NpmResolutionInner {
api: NpmRegistry, api: CliNpmRegistryApi,
snapshot: RwLock<NpmResolutionSnapshot>, snapshot: RwLock<NpmResolutionSnapshot>,
update_queue: TaskQueue, update_queue: TaskQueue,
maybe_lockfile: Option<Arc<Mutex<Lockfile>>>, maybe_lockfile: Option<Arc<Mutex<Lockfile>>>,
@ -55,7 +55,7 @@ impl std::fmt::Debug for NpmResolution {
impl NpmResolution { impl NpmResolution {
pub fn new( pub fn new(
api: NpmRegistry, api: CliNpmRegistryApi,
initial_snapshot: Option<NpmResolutionSnapshot>, initial_snapshot: Option<NpmResolutionSnapshot>,
maybe_lockfile: Option<Arc<Mutex<Lockfile>>>, maybe_lockfile: Option<Arc<Mutex<Lockfile>>>,
) -> Self { ) -> Self {
@ -247,7 +247,7 @@ impl NpmResolution {
} }
async fn add_package_reqs_to_snapshot( async fn add_package_reqs_to_snapshot(
api: &NpmRegistry, api: &CliNpmRegistryApi,
// todo(18079): it should be possible to pass &[NpmPackageReq] in here // todo(18079): it should be possible to pass &[NpmPackageReq] in here
// and avoid all these clones, but the LSP complains because of its // and avoid all these clones, but the LSP complains because of its
// `Send` requirement // `Send` requirement

View file

@ -26,9 +26,9 @@ use crate::http_util::HttpClient;
use crate::node; use crate::node;
use crate::node::NodeResolution; use crate::node::NodeResolution;
use crate::npm::create_npm_fs_resolver; use crate::npm::create_npm_fs_resolver;
use crate::npm::CliNpmRegistryApi;
use crate::npm::NpmCache; use crate::npm::NpmCache;
use crate::npm::NpmPackageResolver; use crate::npm::NpmPackageResolver;
use crate::npm::NpmRegistry;
use crate::npm::NpmResolution; use crate::npm::NpmResolution;
use crate::npm::PackageJsonDepsInstaller; use crate::npm::PackageJsonDepsInstaller;
use crate::resolver::CliGraphResolver; use crate::resolver::CliGraphResolver;
@ -95,7 +95,7 @@ pub struct Inner {
pub resolver: Arc<CliGraphResolver>, pub resolver: Arc<CliGraphResolver>,
maybe_file_watcher_reporter: Option<FileWatcherReporter>, maybe_file_watcher_reporter: Option<FileWatcherReporter>,
pub node_analysis_cache: NodeAnalysisCache, pub node_analysis_cache: NodeAnalysisCache,
pub npm_api: NpmRegistry, pub npm_api: CliNpmRegistryApi,
pub npm_cache: NpmCache, pub npm_cache: NpmCache,
pub npm_resolver: NpmPackageResolver, pub npm_resolver: NpmPackageResolver,
pub npm_resolution: NpmResolution, pub npm_resolution: NpmResolution,
@ -233,14 +233,14 @@ impl ProcState {
let lockfile = cli_options.maybe_lock_file(); let lockfile = cli_options.maybe_lock_file();
let npm_registry_url = NpmRegistry::default_url().to_owned(); let npm_registry_url = CliNpmRegistryApi::default_url().to_owned();
let npm_cache = NpmCache::from_deno_dir( let npm_cache = NpmCache::from_deno_dir(
&dir, &dir,
cli_options.cache_setting(), cli_options.cache_setting(),
http_client.clone(), http_client.clone(),
progress_bar.clone(), progress_bar.clone(),
); );
let npm_api = NpmRegistry::new( let npm_api = CliNpmRegistryApi::new(
npm_registry_url.clone(), npm_registry_url.clone(),
npm_cache.clone(), npm_cache.clone(),
http_client.clone(), http_client.clone(),

View file

@ -1,27 +1,26 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
use deno_core::anyhow::anyhow; use deno_core::anyhow::anyhow;
use deno_core::anyhow::bail;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::futures::future; use deno_core::futures::future;
use deno_core::futures::future::LocalBoxFuture; use deno_core::futures::future::LocalBoxFuture;
use deno_core::futures::FutureExt; use deno_core::futures::FutureExt;
use deno_core::ModuleSpecifier; use deno_core::ModuleSpecifier;
use deno_core::TaskQueue; use deno_core::TaskQueue;
use deno_graph::source::NpmPackageReqResolution;
use deno_graph::source::NpmResolver; use deno_graph::source::NpmResolver;
use deno_graph::source::Resolver; use deno_graph::source::Resolver;
use deno_graph::source::UnknownBuiltInNodeModuleError; use deno_graph::source::UnknownBuiltInNodeModuleError;
use deno_graph::source::DEFAULT_JSX_IMPORT_SOURCE_MODULE; use deno_graph::source::DEFAULT_JSX_IMPORT_SOURCE_MODULE;
use deno_npm::registry::NpmRegistryApi; use deno_npm::registry::NpmRegistryApi;
use deno_runtime::deno_node::is_builtin_node_module; use deno_runtime::deno_node::is_builtin_node_module;
use deno_semver::npm::NpmPackageNv;
use deno_semver::npm::NpmPackageReq; use deno_semver::npm::NpmPackageReq;
use import_map::ImportMap; use import_map::ImportMap;
use std::sync::Arc; use std::sync::Arc;
use crate::args::package_json::PackageJsonDeps; use crate::args::package_json::PackageJsonDeps;
use crate::args::JsxImportSourceConfig; use crate::args::JsxImportSourceConfig;
use crate::npm::NpmRegistry; use crate::npm::CliNpmRegistryApi;
use crate::npm::NpmResolution; use crate::npm::NpmResolution;
use crate::npm::PackageJsonDepsInstaller; use crate::npm::PackageJsonDepsInstaller;
use crate::util::sync::AtomicFlag; use crate::util::sync::AtomicFlag;
@ -34,7 +33,7 @@ pub struct CliGraphResolver {
maybe_default_jsx_import_source: Option<String>, maybe_default_jsx_import_source: Option<String>,
maybe_jsx_import_source_module: Option<String>, maybe_jsx_import_source_module: Option<String>,
no_npm: bool, no_npm: bool,
npm_registry_api: NpmRegistry, npm_registry_api: CliNpmRegistryApi,
npm_resolution: NpmResolution, npm_resolution: NpmResolution,
package_json_deps_installer: PackageJsonDepsInstaller, package_json_deps_installer: PackageJsonDepsInstaller,
found_package_json_dep_flag: Arc<AtomicFlag>, found_package_json_dep_flag: Arc<AtomicFlag>,
@ -45,7 +44,7 @@ impl Default for CliGraphResolver {
fn default() -> Self { fn default() -> Self {
// This is not ideal, but necessary for the LSP. In the future, we should // This is not ideal, but necessary for the LSP. In the future, we should
// refactor the LSP and force this to be initialized. // refactor the LSP and force this to be initialized.
let npm_registry_api = NpmRegistry::new_uninitialized(); let npm_registry_api = CliNpmRegistryApi::new_uninitialized();
let npm_resolution = let npm_resolution =
NpmResolution::new(npm_registry_api.clone(), None, None); NpmResolution::new(npm_registry_api.clone(), None, None);
Self { Self {
@ -67,7 +66,7 @@ impl CliGraphResolver {
maybe_jsx_import_source_config: Option<JsxImportSourceConfig>, maybe_jsx_import_source_config: Option<JsxImportSourceConfig>,
maybe_import_map: Option<Arc<ImportMap>>, maybe_import_map: Option<Arc<ImportMap>>,
no_npm: bool, no_npm: bool,
npm_registry_api: NpmRegistry, npm_registry_api: CliNpmRegistryApi,
npm_resolution: NpmResolution, npm_resolution: NpmResolution,
package_json_deps_installer: PackageJsonDepsInstaller, package_json_deps_installer: PackageJsonDepsInstaller,
) -> Self { ) -> Self {
@ -204,7 +203,7 @@ impl NpmResolver for CliGraphResolver {
fn load_and_cache_npm_package_info( fn load_and_cache_npm_package_info(
&self, &self,
package_name: &str, package_name: &str,
) -> LocalBoxFuture<'static, Result<(), String>> { ) -> LocalBoxFuture<'static, Result<(), AnyError>> {
if self.no_npm { if self.no_npm {
// return it succeeded and error at the import site below // return it succeeded and error at the import site below
return Box::pin(future::ready(Ok(()))); return Box::pin(future::ready(Ok(())));
@ -224,7 +223,7 @@ impl NpmResolver for CliGraphResolver {
.package_info(&package_name) .package_info(&package_name)
.await .await
.map(|_| ()) .map(|_| ())
.map_err(|err| format!("{err:#}")); .map_err(|err| err.into());
drop(permit); drop(permit);
result result
} }
@ -234,17 +233,26 @@ impl NpmResolver for CliGraphResolver {
fn resolve_npm( fn resolve_npm(
&self, &self,
package_req: &NpmPackageReq, package_req: &NpmPackageReq,
) -> Result<NpmPackageNv, AnyError> { ) -> NpmPackageReqResolution {
if self.no_npm { if self.no_npm {
bail!("npm specifiers were requested; but --no-npm is specified") return NpmPackageReqResolution::Err(anyhow!(
"npm specifiers were requested; but --no-npm is specified"
));
} }
match self
let result = self
.npm_resolution .npm_resolution
.resolve_package_req_as_pending(package_req) .resolve_package_req_as_pending(package_req);
{ match result {
Ok(nv) => Ok(nv), Ok(nv) => NpmPackageReqResolution::Ok(nv),
Err(err) => { Err(err) => {
bail!("{:#} Try retrieving the latest npm package information by running with --reload", err) if self.npm_registry_api.mark_force_reload() {
log::debug!("Restarting npm specifier resolution to check for new registry information. Error: {:#}", err);
self.npm_registry_api.clear_memory_cache();
NpmPackageReqResolution::ReloadRegistryInfo(err.into())
} else {
NpmPackageReqResolution::Err(err.into())
}
} }
} }
} }

View file

@ -1590,6 +1590,23 @@ fn reload_info_not_found_cache_but_exists_remote() {
.remove(version); .remove(version);
} }
fn remove_version_for_package(
deno_dir: &util::TempDir,
package: &str,
version: &str,
) {
let registry_json_path =
format!("npm/localhost_4545/npm/registry/{}/registry.json", package);
let mut registry_json: Value =
serde_json::from_str(&deno_dir.read_to_string(&registry_json_path))
.unwrap();
remove_version(&mut registry_json, version);
deno_dir.write(
&registry_json_path,
serde_json::to_string(&registry_json).unwrap(),
);
}
// This tests that when a local machine doesn't have a version // This tests that when a local machine doesn't have a version
// specified in a dependency that exists in the npm registry // specified in a dependency that exists in the npm registry
let test_context = TestContextBuilder::for_npm() let test_context = TestContextBuilder::for_npm()
@ -1604,68 +1621,144 @@ fn reload_info_not_found_cache_but_exists_remote() {
); );
// cache successfully to the deno_dir // cache successfully to the deno_dir
let output = test_context.new_command().args("cache main.ts").run(); let output = test_context
.new_command()
.args("cache main.ts npm:@denotest/esm-basic@1.0.0")
.run();
output.assert_matches_text(concat!( output.assert_matches_text(concat!(
"Download http://localhost:4545/npm/registry/@denotest/esm-basic\n",
"Download http://localhost:4545/npm/registry/@denotest/esm-import-cjs-default\n", "Download http://localhost:4545/npm/registry/@denotest/esm-import-cjs-default\n",
"Download http://localhost:4545/npm/registry/@denotest/cjs-default-export\n", "Download http://localhost:4545/npm/registry/@denotest/cjs-default-export\n",
"Download http://localhost:4545/npm/registry/@denotest/cjs-default-export/1.0.0.tgz\n", "Download http://localhost:4545/npm/registry/@denotest/cjs-default-export/1.0.0.tgz\n",
"Download http://localhost:4545/npm/registry/@denotest/esm-basic/1.0.0.tgz\n",
"Download http://localhost:4545/npm/registry/@denotest/esm-import-cjs-default/1.0.0.tgz\n", "Download http://localhost:4545/npm/registry/@denotest/esm-import-cjs-default/1.0.0.tgz\n",
)); ));
// modify the package information in the cache to remove the latest version // test in dependency
let registry_json_path = "npm/localhost_4545/npm/registry/@denotest/cjs-default-export/registry.json"; {
let mut registry_json: Value = // modify the package information in the cache to remove the latest version
serde_json::from_str(&deno_dir.read_to_string(registry_json_path)).unwrap(); remove_version_for_package(
remove_version(&mut registry_json, "1.0.0"); deno_dir,
deno_dir.write( "@denotest/cjs-default-export",
registry_json_path, "1.0.0",
serde_json::to_string(&registry_json).unwrap(), );
);
// should error when `--cache-only` is used now because the version is not in the cache // should error when `--cache-only` is used now because the version is not in the cache
let output = test_context let output = test_context
.new_command() .new_command()
.args("run --cached-only main.ts") .args("run --cached-only main.ts")
.run(); .run();
output.assert_exit_code(1); output.assert_exit_code(1);
output.assert_matches_text("error: Could not find npm package '@denotest/cjs-default-export' matching '^1.0.0'.\n"); output.assert_matches_text("error: Could not find npm package '@denotest/cjs-default-export' matching '^1.0.0'.\n");
// now try running without it, it should download the package now // now try running without it, it should download the package now
let output = test_context.new_command().args("run main.ts").run(); let output = test_context.new_command().args("run main.ts").run();
output.assert_matches_text(concat!( output.assert_matches_text(concat!(
"Download http://localhost:4545/npm/registry/@denotest/esm-import-cjs-default\n", "Download http://localhost:4545/npm/registry/@denotest/esm-import-cjs-default\n",
"Download http://localhost:4545/npm/registry/@denotest/cjs-default-export\n", "Download http://localhost:4545/npm/registry/@denotest/cjs-default-export\n",
"Node esm importing node cjs\n[WILDCARD]", "Node esm importing node cjs\n[WILDCARD]",
)); ));
output.assert_exit_code(0); output.assert_exit_code(0);
}
// now remove the information for the top level package // test in npm specifier
let registry_json_path = "npm/localhost_4545/npm/registry/@denotest/esm-import-cjs-default/registry.json"; {
let mut registry_json: Value = // now remove the information for the top level package
serde_json::from_str(&deno_dir.read_to_string(registry_json_path)).unwrap(); remove_version_for_package(
remove_version(&mut registry_json, "1.0.0"); deno_dir,
deno_dir.write( "@denotest/esm-import-cjs-default",
registry_json_path, "1.0.0",
serde_json::to_string(&registry_json).unwrap(), );
);
// should error again for --cached-only // should error for --cached-only
let output = test_context let output = test_context
.new_command() .new_command()
.args("run --cached-only main.ts") .args("run --cached-only main.ts")
.run(); .run();
output.assert_exit_code(1); output.assert_matches_text(concat!(
output.assert_matches_text(concat!( "error: Could not find npm package '@denotest/esm-import-cjs-default' matching '1.0.0'.\n",
"error: Could not find npm package '@denotest/esm-import-cjs-default' matching '1.0.0'. Try retrieving the latest npm package information by running with --reload\n", " at file:///[WILDCARD]/main.ts:1:8\n",
" at file:///[WILDCARD]/main.ts:1:8\n", ));
)); output.assert_exit_code(1);
// now try running without it, it currently will error, but this should work in the future // now try running, it should work
// todo(https://github.com/denoland/deno/issues/16901): fix this let output = test_context.new_command().args("run main.ts").run();
let output = test_context.new_command().args("run main.ts").run(); output.assert_matches_text(concat!(
output.assert_exit_code(1); "Download http://localhost:4545/npm/registry/@denotest/esm-import-cjs-default\n",
output.assert_matches_text(concat!( "Download http://localhost:4545/npm/registry/@denotest/cjs-default-export\n",
"error: Could not find npm package '@denotest/esm-import-cjs-default' matching '1.0.0'. Try retrieving the latest npm package information by running with --reload\n", "Node esm importing node cjs\n[WILDCARD]",
" at file:///[WILDCARD]/main.ts:1:8\n", ));
)); output.assert_exit_code(0);
}
// test matched specifier in package.json
{
// write out a package.json and a new main.ts with a bare specifier
temp_dir.write("main.ts", "import '@denotest/esm-import-cjs-default';");
temp_dir.write(
"package.json",
r#"{ "dependencies": { "@denotest/esm-import-cjs-default": "1.0.0" }}"#,
);
// remove the top level package information again
remove_version_for_package(
deno_dir,
"@denotest/esm-import-cjs-default",
"1.0.0",
);
// should error for --cached-only
let output = test_context
.new_command()
.args("run --cached-only main.ts")
.run();
output.assert_matches_text(concat!(
"error: Could not find npm package '@denotest/esm-import-cjs-default' matching '1.0.0'.\n",
));
output.assert_exit_code(1);
// now try running, it should work
let output = test_context.new_command().args("run main.ts").run();
output.assert_matches_text(concat!(
"Download http://localhost:4545/npm/registry/@denotest/esm-import-cjs-default\n",
"Download http://localhost:4545/npm/registry/@denotest/cjs-default-export\n",
"Initialize @denotest/cjs-default-export@1.0.0\n",
"Initialize @denotest/esm-import-cjs-default@1.0.0\n",
"Node esm importing node cjs\n[WILDCARD]",
));
output.assert_exit_code(0);
}
// temp other dependency in package.json
{
// write out a package.json that has another dependency
temp_dir.write(
"package.json",
r#"{ "dependencies": { "@denotest/esm-import-cjs-default": "1.0.0", "@denotest/esm-basic": "1.0.0" }}"#,
);
// remove the dependency's version
remove_version_for_package(deno_dir, "@denotest/esm-basic", "1.0.0");
// should error for --cached-only
let output = test_context
.new_command()
.args("run --cached-only main.ts")
.run();
output.assert_matches_text(concat!(
"error: Could not find npm package '@denotest/esm-basic' matching '1.0.0'.\n",
));
output.assert_exit_code(1);
// now try running, it should work and only initialize the new package
let output = test_context.new_command().args("run main.ts").run();
output.assert_matches_text(concat!(
"Download http://localhost:4545/npm/registry/@denotest/esm-basic\n",
"Download http://localhost:4545/npm/registry/@denotest/esm-import-cjs-default\n",
"Download http://localhost:4545/npm/registry/@denotest/cjs-default-export\n",
"Initialize @denotest/esm-basic@1.0.0\n",
"Node esm importing node cjs\n[WILDCARD]",
));
output.assert_exit_code(0);
}
} }

View file

@ -486,7 +486,7 @@ impl<'a> GraphDisplayContext<'a> {
colors::red("error:") colors::red("error:")
) )
} else { } else {
writeln!(writer, "{} {}", colors::red("error:"), err) writeln!(writer, "{} {:#}", colors::red("error:"), err)
} }
} }
Ok(None) => { Ok(None) => {

View file

@ -20,7 +20,7 @@ use deno_graph::ModuleGraph;
use import_map::ImportMap; use import_map::ImportMap;
use crate::cache::ParsedSourceCache; use crate::cache::ParsedSourceCache;
use crate::npm::NpmRegistry; use crate::npm::CliNpmRegistryApi;
use crate::npm::NpmResolution; use crate::npm::NpmResolution;
use crate::npm::PackageJsonDepsInstaller; use crate::npm::PackageJsonDepsInstaller;
use crate::resolver::CliGraphResolver; use crate::resolver::CliGraphResolver;
@ -264,7 +264,7 @@ async fn build_test_graph(
analyzer: &dyn deno_graph::ModuleAnalyzer, analyzer: &dyn deno_graph::ModuleAnalyzer,
) -> ModuleGraph { ) -> ModuleGraph {
let resolver = original_import_map.map(|m| { let resolver = original_import_map.map(|m| {
let npm_registry_api = NpmRegistry::new_uninitialized(); let npm_registry_api = CliNpmRegistryApi::new_uninitialized();
let npm_resolution = let npm_resolution =
NpmResolution::new(npm_registry_api.clone(), None, None); NpmResolution::new(npm_registry_api.clone(), None, None);
let deps_installer = PackageJsonDepsInstaller::new( let deps_installer = PackageJsonDepsInstaller::new(