Skip to content

Commit 0c9598d

Browse files
authored
fix(check): ensure @types/node is cached when already in resolution (#31235)
1 parent 19c7f1f commit 0c9598d

File tree

7 files changed

+94
-34
lines changed

7 files changed

+94
-34
lines changed

cli/graph_util.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -792,7 +792,9 @@ impl ModuleGraphBuilder {
792792
&& graph.has_node_specifier
793793
&& graph.graph_kind().include_types()
794794
{
795-
npm_installer.inject_synthetic_types_node_package().await?;
795+
npm_installer
796+
.inject_synthetic_types_node_package(options.npm_caching)
797+
.await?;
796798
}
797799

798800
Ok(())

cli/tools/repl/session.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ use deno_graph::Position;
4040
use deno_graph::PositionRange;
4141
use deno_graph::analysis::SpecifierWithRange;
4242
use deno_lib::util::result::any_and_jserrorbox_downcast_ref;
43+
use deno_npm_installer::graph::NpmCachingStrategy;
4344
use deno_resolver::deno_json::CompilerOptionsResolver;
4445
use deno_runtime::worker::MainWorker;
4546
use deno_semver::npm::NpmPackageReqReference;
@@ -896,7 +897,9 @@ impl ReplSession {
896897

897898
// prevent messages in the repl about @types/node not being cached
898899
if has_node_specifier {
899-
npm_installer.inject_synthetic_types_node_package().await?;
900+
npm_installer
901+
.inject_synthetic_types_node_package(NpmCachingStrategy::Eager)
902+
.await?;
900903
}
901904
}
902905
Ok(())

libs/npm_installer/graph.rs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Copyright 2018-2025 the Deno authors. MIT license.
22

3+
use std::borrow::Cow;
34
use std::sync::Arc;
45

56
use deno_error::JsErrorBox;
@@ -21,6 +22,21 @@ pub enum NpmCachingStrategy {
2122
Manual,
2223
}
2324

25+
impl NpmCachingStrategy {
26+
pub fn as_package_caching<'a>(
27+
&self,
28+
package_reqs: &'a [PackageReq],
29+
) -> Option<PackageCaching<'a>> {
30+
match self {
31+
NpmCachingStrategy::Eager => Some(PackageCaching::All),
32+
NpmCachingStrategy::Lazy => {
33+
Some(PackageCaching::Only(Cow::Borrowed(package_reqs)))
34+
}
35+
NpmCachingStrategy::Manual => None,
36+
}
37+
}
38+
}
39+
2440
#[derive(Debug)]
2541
pub struct NpmDenoGraphResolver<
2642
TNpmCacheHttpClient: NpmCacheHttpClient,
@@ -88,13 +104,7 @@ impl<TNpmCacheHttpClient: NpmCacheHttpClient, TSys: NpmInstallerSys>
88104
let result = npm_installer
89105
.add_package_reqs_raw(
90106
package_reqs,
91-
match self.npm_caching {
92-
NpmCachingStrategy::Eager => Some(PackageCaching::All),
93-
NpmCachingStrategy::Lazy => {
94-
Some(PackageCaching::Only(package_reqs.into()))
95-
}
96-
NpmCachingStrategy::Manual => None,
97-
},
107+
self.npm_caching.as_package_caching(package_reqs),
98108
)
99109
.await;
100110

libs/npm_installer/lib.rs

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -298,50 +298,65 @@ impl<TNpmCacheHttpClient: NpmCacheHttpClient, TSys: NpmInstallerSys>
298298
if result.dependencies_result.is_ok()
299299
&& let Some(caching) = caching
300300
{
301-
// the async mutex is unfortunate, but needed to handle the edge case where two workers
302-
// try to cache the same package at the same time. we need to hold the lock while we cache
303-
// and since that crosses an await point, we need the async mutex.
304-
//
305-
// should have a negligible perf impact because acquiring the lock is still in the order of nanoseconds
306-
// while caching typically takes micro or milli seconds.
307-
let _permit = self.install_queue.acquire().await;
308-
let uncached = {
309-
let cached_reqs = self.cached_reqs.lock();
310-
packages
311-
.iter()
312-
.filter(|req| !cached_reqs.contains(req))
313-
.collect::<Vec<_>>()
314-
};
301+
result.dependencies_result =
302+
self.maybe_cache_packages(packages, caching).await;
303+
}
315304

316-
if !uncached.is_empty() {
317-
result.dependencies_result =
318-
self.fs_installer.cache_packages(caching).await;
319-
if result.dependencies_result.is_ok() {
320-
let mut cached_reqs = self.cached_reqs.lock();
321-
for req in uncached {
322-
cached_reqs.insert(req.clone());
323-
}
324-
}
305+
result
306+
}
307+
308+
async fn maybe_cache_packages(
309+
&self,
310+
packages: &[PackageReq],
311+
caching: PackageCaching<'_>,
312+
) -> Result<(), JsErrorBox> {
313+
// the async mutex is unfortunate, but needed to handle the edge case where two workers
314+
// try to cache the same package at the same time. we need to hold the lock while we cache
315+
// and since that crosses an await point, we need the async mutex.
316+
//
317+
// should have a negligible perf impact because acquiring the lock is still in the order of nanoseconds
318+
// while caching typically takes micro or milli seconds.
319+
let _permit = self.install_queue.acquire().await;
320+
let uncached = {
321+
let cached_reqs = self.cached_reqs.lock();
322+
packages
323+
.iter()
324+
.filter(|req| !cached_reqs.contains(req))
325+
.collect::<Vec<_>>()
326+
};
327+
328+
if uncached.is_empty() {
329+
return Ok(());
330+
}
331+
let result = self.fs_installer.cache_packages(caching).await;
332+
if result.is_ok() {
333+
let mut cached_reqs = self.cached_reqs.lock();
334+
for req in uncached {
335+
cached_reqs.insert(req.clone());
325336
}
326337
}
327-
328338
result
329339
}
330340

331341
pub async fn inject_synthetic_types_node_package(
332342
&self,
343+
cache_strategy: graph::NpmCachingStrategy,
333344
) -> Result<(), JsErrorBox> {
334345
self.npm_resolution_initializer.ensure_initialized().await?;
335346

336347
// don't inject this if it's already been added
348+
let reqs = &[PackageReq::from_str("@types/node").unwrap()];
337349
if self
338350
.npm_resolution
339351
.any_top_level_package(|id| id.nv.name == "@types/node")
340352
{
353+
// ensure it's cached though
354+
if let Some(caching) = cache_strategy.as_package_caching(reqs) {
355+
self.maybe_cache_packages(reqs, caching).await?;
356+
}
341357
return Ok(());
342358
}
343359

344-
let reqs = &[PackageReq::from_str("@types/node").unwrap()];
345360
self
346361
.add_package_reqs(reqs, PackageCaching::Only(reqs.into()))
347362
.await?;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"tempDir": true,
3+
"steps": [{
4+
// will create a deno.lock file
5+
"args": "check",
6+
"output": "[WILDCARD]"
7+
}, {
8+
"args": "clean",
9+
"output": "[WILDCARD]"
10+
}, {
11+
// this should ensure the package gets cached
12+
"args": "check",
13+
"output": "[WILDCARD]"
14+
}]
15+
}

tests/specs/check/types_node_in_lockfile_not_cached/deno.json

Whitespace-only changes.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import * as cp from "node:child_process";
2+
3+
const child = cp.spawn(
4+
"cmd.exe",
5+
["/d", "/s", "/c"],
6+
{
7+
cwd: "test",
8+
},
9+
);
10+
child.on("exit", (code) => {
11+
console.log(code);
12+
});
13+
child.on("error", (err) => {
14+
console.log(err);
15+
});

0 commit comments

Comments
 (0)