Skip to content

Commit 0117281

Browse files
dsherretlevex
authored andcommitted
fix(npm): reload an npm package's dependency's information when version not found (#18622)
This reloads an npm package's dependency's information when a version/version req/tag is not found. This PR applies only to dependencies of npm packages. It does NOT yet cause npm specifiers to have their dependency information cache busted. That requires a different solution, but this should help cache bust in more scenarios. Part of #16901, but doesn't close it yet
1 parent af6f2de commit 0117281

File tree

15 files changed

+258
-58
lines changed

15 files changed

+258
-58
lines changed

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cli/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ deno_emit = "0.18.0"
4747
deno_graph = "=0.46.0"
4848
deno_lint = { version = "0.43.0", features = ["docs"] }
4949
deno_lockfile.workspace = true
50-
deno_npm = "0.1.0"
50+
deno_npm = "0.2.0"
5151
deno_runtime = { workspace = true, features = ["dont_create_runtime_snapshot", "include_js_files_for_snapshotting"] }
5252
deno_semver = "0.2.0"
5353
deno_task_shell = "0.10.0"

cli/args/lockfile.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,11 @@ pub async fn snapshot_from_lockfile(
117117
let mut version_infos =
118118
FuturesOrdered::from_iter(packages.iter().map(|p| p.pkg_id.nv.clone()).map(
119119
|nv| async move {
120-
match api.package_version_info(&nv).await? {
121-
Some(version_info) => Ok(version_info),
122-
None => {
123-
bail!("could not find '{}' specified in the lockfile. Maybe try again with --reload", nv);
120+
let package_info = api.package_info(&nv.name).await?;
121+
match package_info.version_info(&nv) {
122+
Ok(version_info) => Ok(version_info),
123+
Err(err) => {
124+
bail!("Could not find '{}' specified in the lockfile. Maybe try again with --reload", err.0);
124125
}
125126
}
126127
},

cli/npm/registry.rs

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ use std::collections::HashSet;
55
use std::fs;
66
use std::io::ErrorKind;
77
use std::path::PathBuf;
8+
use std::sync::atomic::AtomicBool;
9+
use std::sync::atomic::Ordering;
810
use std::sync::Arc;
911

1012
use async_trait::async_trait;
@@ -21,6 +23,7 @@ use deno_core::url::Url;
2123
use deno_core::TaskQueue;
2224
use deno_npm::registry::NpmPackageInfo;
2325
use deno_npm::registry::NpmRegistryApi;
26+
use deno_npm::registry::NpmRegistryPackageInfoLoadError;
2427
use once_cell::sync::Lazy;
2528

2629
use crate::args::CacheSetting;
@@ -67,6 +70,7 @@ impl NpmRegistry {
6770
Self(Some(Arc::new(NpmRegistryApiInner {
6871
base_url,
6972
cache,
73+
force_reload: AtomicBool::new(false),
7074
mem_cache: Default::default(),
7175
previously_reloaded_packages: Default::default(),
7276
http_client,
@@ -96,6 +100,18 @@ impl NpmRegistry {
96100
&self.inner().base_url
97101
}
98102

103+
/// Marks that new requests for package information should retrieve it
104+
/// from the npm registry
105+
///
106+
/// Returns true if it was successfully set for the first time.
107+
pub fn mark_force_reload(&self) -> bool {
108+
// never force reload the registry information if reloading is disabled
109+
if matches!(self.inner().cache.cache_setting(), CacheSetting::Only) {
110+
return false;
111+
}
112+
!self.inner().force_reload.swap(true, Ordering::SeqCst)
113+
}
114+
99115
fn inner(&self) -> &Arc<NpmRegistryApiInner> {
100116
// this panicking indicates a bug in the code where this
101117
// wasn't initialized
@@ -108,17 +124,26 @@ static SYNC_DOWNLOAD_TASK_QUEUE: Lazy<TaskQueue> =
108124

109125
#[async_trait]
110126
impl NpmRegistryApi for NpmRegistry {
111-
async fn maybe_package_info(
127+
async fn package_info(
112128
&self,
113129
name: &str,
114-
) -> Result<Option<Arc<NpmPackageInfo>>, AnyError> {
115-
if should_sync_download() {
130+
) -> Result<Arc<NpmPackageInfo>, NpmRegistryPackageInfoLoadError> {
131+
let result = if should_sync_download() {
116132
let inner = self.inner().clone();
117133
SYNC_DOWNLOAD_TASK_QUEUE
118134
.queue(async move { inner.maybe_package_info(name).await })
119135
.await
120136
} else {
121137
self.inner().maybe_package_info(name).await
138+
};
139+
match result {
140+
Ok(Some(info)) => Ok(info),
141+
Ok(None) => Err(NpmRegistryPackageInfoLoadError::PackageNotExists {
142+
package_name: name.to_string(),
143+
}),
144+
Err(err) => {
145+
Err(NpmRegistryPackageInfoLoadError::LoadError(Arc::new(err)))
146+
}
122147
}
123148
}
124149
}
@@ -135,6 +160,7 @@ enum CacheItem {
135160
struct NpmRegistryApiInner {
136161
base_url: Url,
137162
cache: NpmCache,
163+
force_reload: AtomicBool,
138164
mem_cache: Mutex<HashMap<String, CacheItem>>,
139165
previously_reloaded_packages: Mutex<HashSet<String>>,
140166
http_client: HttpClient,
@@ -154,10 +180,10 @@ impl NpmRegistryApiInner {
154180
}
155181
Some(CacheItem::Pending(future)) => (false, future.clone()),
156182
None => {
157-
if self.cache.cache_setting().should_use_for_npm_package(name)
158-
// if this has been previously reloaded, then try loading from the
159-
// file system cache
160-
|| !self.previously_reloaded_packages.lock().insert(name.to_string())
183+
if (self.cache.cache_setting().should_use_for_npm_package(name) && !self.force_reload())
184+
// if this has been previously reloaded, then try loading from the
185+
// file system cache
186+
|| !self.previously_reloaded_packages.lock().insert(name.to_string())
161187
{
162188
// attempt to load from the file cache
163189
if let Some(info) = self.load_file_cached_package_info(name) {
@@ -203,6 +229,10 @@ impl NpmRegistryApiInner {
203229
}
204230
}
205231

232+
fn force_reload(&self) -> bool {
233+
self.force_reload.load(Ordering::SeqCst)
234+
}
235+
206236
fn load_file_cached_package_info(
207237
&self,
208238
name: &str,

cli/npm/resolution.rs

Lines changed: 49 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,13 @@ use deno_core::TaskQueue;
1010
use deno_lockfile::NpmPackageDependencyLockfileInfo;
1111
use deno_lockfile::NpmPackageLockfileInfo;
1212
use deno_npm::registry::NpmPackageInfo;
13+
use deno_npm::resolution::NpmPackageVersionResolutionError;
1314
use deno_npm::resolution::NpmPackagesPartitioned;
15+
use deno_npm::resolution::NpmResolutionError;
1416
use deno_npm::resolution::NpmResolutionSnapshot;
17+
use deno_npm::resolution::PackageNotFoundFromReferrerError;
18+
use deno_npm::resolution::PackageNvNotFoundError;
19+
use deno_npm::resolution::PackageReqNotFoundError;
1520
use deno_npm::NpmPackageCacheFolderId;
1621
use deno_npm::NpmPackageId;
1722
use deno_npm::NpmResolutionPackage;
@@ -70,13 +75,11 @@ impl NpmResolution {
7075

7176
// only allow one thread in here at a time
7277
let _permit = inner.update_queue.acquire().await;
73-
let snapshot = inner.snapshot.read().clone();
74-
7578
let snapshot = add_package_reqs_to_snapshot(
7679
&inner.api,
7780
package_reqs,
78-
snapshot,
7981
self.0.maybe_lockfile.clone(),
82+
|| inner.snapshot.read().clone(),
8083
)
8184
.await?;
8285

@@ -91,24 +94,25 @@ impl NpmResolution {
9194
let inner = &self.0;
9295
// only allow one thread in here at a time
9396
let _permit = inner.update_queue.acquire().await;
94-
let snapshot = inner.snapshot.read().clone();
95-
96-
let reqs_set = package_reqs.iter().collect::<HashSet<_>>();
97-
let has_removed_package = !snapshot
98-
.package_reqs()
99-
.keys()
100-
.all(|req| reqs_set.contains(req));
101-
// if any packages were removed, we need to completely recreate the npm resolution snapshot
102-
let snapshot = if has_removed_package {
103-
NpmResolutionSnapshot::default()
104-
} else {
105-
snapshot
106-
};
97+
98+
let reqs_set = package_reqs.iter().cloned().collect::<HashSet<_>>();
10799
let snapshot = add_package_reqs_to_snapshot(
108100
&inner.api,
109101
package_reqs,
110-
snapshot,
111102
self.0.maybe_lockfile.clone(),
103+
|| {
104+
let snapshot = inner.snapshot.read().clone();
105+
let has_removed_package = !snapshot
106+
.package_reqs()
107+
.keys()
108+
.all(|req| reqs_set.contains(req));
109+
// if any packages were removed, we need to completely recreate the npm resolution snapshot
110+
if has_removed_package {
111+
NpmResolutionSnapshot::default()
112+
} else {
113+
snapshot
114+
}
115+
},
112116
)
113117
.await?;
114118

@@ -121,13 +125,12 @@ impl NpmResolution {
121125
let inner = &self.0;
122126
// only allow one thread in here at a time
123127
let _permit = inner.update_queue.acquire().await;
124-
let snapshot = inner.snapshot.read().clone();
125128

126129
let snapshot = add_package_reqs_to_snapshot(
127130
&inner.api,
128131
Vec::new(),
129-
snapshot,
130132
self.0.maybe_lockfile.clone(),
133+
|| inner.snapshot.read().clone(),
131134
)
132135
.await?;
133136

@@ -139,7 +142,7 @@ impl NpmResolution {
139142
pub fn pkg_req_ref_to_nv_ref(
140143
&self,
141144
req_ref: NpmPackageReqReference,
142-
) -> Result<NpmPackageNvReference, AnyError> {
145+
) -> Result<NpmPackageNvReference, PackageReqNotFoundError> {
143146
let node_id = self.resolve_pkg_id_from_pkg_req(&req_ref.req)?;
144147
Ok(NpmPackageNvReference {
145148
nv: node_id.nv,
@@ -163,7 +166,7 @@ impl NpmResolution {
163166
&self,
164167
name: &str,
165168
referrer: &NpmPackageCacheFolderId,
166-
) -> Result<NpmResolutionPackage, AnyError> {
169+
) -> Result<NpmResolutionPackage, Box<PackageNotFoundFromReferrerError>> {
167170
self
168171
.0
169172
.snapshot
@@ -176,7 +179,7 @@ impl NpmResolution {
176179
pub fn resolve_pkg_id_from_pkg_req(
177180
&self,
178181
req: &NpmPackageReq,
179-
) -> Result<NpmPackageId, AnyError> {
182+
) -> Result<NpmPackageId, PackageReqNotFoundError> {
180183
self
181184
.0
182185
.snapshot
@@ -188,7 +191,7 @@ impl NpmResolution {
188191
pub fn resolve_pkg_id_from_deno_module(
189192
&self,
190193
id: &NpmPackageNv,
191-
) -> Result<NpmPackageId, AnyError> {
194+
) -> Result<NpmPackageId, PackageNvNotFoundError> {
192195
self
193196
.0
194197
.snapshot
@@ -203,7 +206,7 @@ impl NpmResolution {
203206
pub fn resolve_package_req_as_pending(
204207
&self,
205208
pkg_req: &NpmPackageReq,
206-
) -> Result<NpmPackageNv, AnyError> {
209+
) -> Result<NpmPackageNv, NpmPackageVersionResolutionError> {
207210
// we should always have this because it should have been cached before here
208211
let package_info =
209212
self.0.api.get_cached_package_info(&pkg_req.name).unwrap();
@@ -217,7 +220,7 @@ impl NpmResolution {
217220
&self,
218221
pkg_req: &NpmPackageReq,
219222
package_info: &NpmPackageInfo,
220-
) -> Result<NpmPackageNv, AnyError> {
223+
) -> Result<NpmPackageNv, NpmPackageVersionResolutionError> {
221224
debug_assert_eq!(pkg_req.name, package_info.name);
222225
let inner = &self.0;
223226
let mut snapshot = inner.snapshot.write();
@@ -245,10 +248,14 @@ impl NpmResolution {
245248

246249
async fn add_package_reqs_to_snapshot(
247250
api: &NpmRegistry,
251+
// todo(18079): it should be possible to pass &[NpmPackageReq] in here
252+
// and avoid all these clones, but the LSP complains because of its
253+
// `Send` requirement
248254
package_reqs: Vec<NpmPackageReq>,
249-
snapshot: NpmResolutionSnapshot,
250255
maybe_lockfile: Option<Arc<Mutex<Lockfile>>>,
256+
get_new_snapshot: impl Fn() -> NpmResolutionSnapshot,
251257
) -> Result<NpmResolutionSnapshot, AnyError> {
258+
let snapshot = get_new_snapshot();
252259
if !snapshot.has_pending()
253260
&& package_reqs
254261
.iter()
@@ -257,9 +264,23 @@ async fn add_package_reqs_to_snapshot(
257264
return Ok(snapshot); // already up to date
258265
}
259266

260-
let result = snapshot.resolve_pending(package_reqs, api).await;
267+
let result = snapshot.resolve_pending(package_reqs.clone(), api).await;
261268
api.clear_memory_cache();
262-
let snapshot = result?; // propagate the error after clearing the memory cache
269+
let snapshot = match result {
270+
Ok(snapshot) => snapshot,
271+
Err(NpmResolutionError::Resolution(err)) if api.mark_force_reload() => {
272+
log::debug!("{err:#}");
273+
log::debug!("npm resolution failed. Trying again...");
274+
275+
// try again
276+
let snapshot = get_new_snapshot();
277+
let result = snapshot.resolve_pending(package_reqs, api).await;
278+
api.clear_memory_cache();
279+
// now surface the result after clearing the cache
280+
result?
281+
}
282+
Err(err) => return Err(err.into()),
283+
};
263284

264285
if let Some(lockfile_mutex) = maybe_lockfile {
265286
let mut lockfile = lockfile_mutex.lock();

cli/npm/resolvers/global.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use async_trait::async_trait;
99
use deno_ast::ModuleSpecifier;
1010
use deno_core::error::AnyError;
1111
use deno_core::url::Url;
12+
use deno_npm::resolution::PackageNotFoundFromReferrerError;
1213
use deno_npm::NpmPackageCacheFolderId;
1314
use deno_npm::NpmPackageId;
1415
use deno_npm::NpmResolutionPackage;
@@ -58,7 +59,7 @@ impl GlobalNpmPackageResolver {
5859
&self,
5960
package_name: &str,
6061
referrer_pkg_id: &NpmPackageCacheFolderId,
61-
) -> Result<NpmResolutionPackage, AnyError> {
62+
) -> Result<NpmResolutionPackage, Box<PackageNotFoundFromReferrerError>> {
6263
let types_name = types_package_name(package_name);
6364
self
6465
.resolution

cli/npm/resolvers/mod.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use deno_core::parking_lot::Mutex;
1515
use deno_core::serde_json;
1616
use deno_core::url::Url;
1717
use deno_npm::resolution::NpmResolutionSnapshot;
18+
use deno_npm::resolution::PackageReqNotFoundError;
1819
use deno_npm::NpmPackageId;
1920
use deno_runtime::deno_node::NodePermissions;
2021
use deno_runtime::deno_node::NodeResolutionMode;
@@ -82,14 +83,14 @@ impl NpmPackageResolver {
8283
pub fn resolve_pkg_id_from_pkg_req(
8384
&self,
8485
req: &NpmPackageReq,
85-
) -> Result<NpmPackageId, AnyError> {
86+
) -> Result<NpmPackageId, PackageReqNotFoundError> {
8687
self.resolution.resolve_pkg_id_from_pkg_req(req)
8788
}
8889

8990
pub fn pkg_req_ref_to_nv_ref(
9091
&self,
9192
req_ref: NpmPackageReqReference,
92-
) -> Result<NpmPackageNvReference, AnyError> {
93+
) -> Result<NpmPackageNvReference, PackageReqNotFoundError> {
9394
self.resolution.pkg_req_ref_to_nv_ref(req_ref)
9495
}
9596

@@ -225,10 +226,8 @@ impl NpmPackageResolver {
225226
&self,
226227
) -> Result<(), AnyError> {
227228
// add and ensure this isn't added to the lockfile
228-
self
229-
.resolution
230-
.add_package_reqs(vec![NpmPackageReq::from_str("@types/node").unwrap()])
231-
.await?;
229+
let package_reqs = vec![NpmPackageReq::from_str("@types/node").unwrap()];
230+
self.resolution.add_package_reqs(package_reqs).await?;
232231
self.fs_resolver.cache_packages().await?;
233232

234233
Ok(())

cli/resolver.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,9 +237,15 @@ impl NpmResolver for CliGraphResolver {
237237
if self.no_npm {
238238
bail!("npm specifiers were requested; but --no-npm is specified")
239239
}
240-
self
240+
match self
241241
.npm_resolution
242242
.resolve_package_req_as_pending(package_req)
243+
{
244+
Ok(nv) => Ok(nv),
245+
Err(err) => {
246+
bail!("{:#} Try retrieving the latest npm package information by running with --reload", err)
247+
}
248+
}
243249
}
244250
}
245251

0 commit comments

Comments
 (0)