mirror of
https://github.com/denoland/deno.git
synced 2025-01-21 13:00:36 -05:00
refactor(npm): split some resolution from installation (#27595)
This splits away some npm resolution code from installation. It will allow for more easily extracting out resolution code in the future.
This commit is contained in:
parent
ea30e188a8
commit
ce0968ef3a
15 changed files with 1571 additions and 1469 deletions
226
cli/npm/managed/installer.rs
Normal file
226
cli/npm/managed/installer.rs
Normal file
|
@ -0,0 +1,226 @@
|
||||||
|
// Copyright 2018-2025 the Deno authors. MIT license.
|
||||||
|
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use capacity_builder::StringBuilder;
|
||||||
|
use deno_core::error::AnyError;
|
||||||
|
use deno_error::JsErrorBox;
|
||||||
|
use deno_lockfile::NpmPackageDependencyLockfileInfo;
|
||||||
|
use deno_lockfile::NpmPackageLockfileInfo;
|
||||||
|
use deno_npm::registry::NpmRegistryApi;
|
||||||
|
use deno_npm::resolution::AddPkgReqsOptions;
|
||||||
|
use deno_npm::resolution::NpmResolutionError;
|
||||||
|
use deno_npm::resolution::NpmResolutionSnapshot;
|
||||||
|
use deno_npm::NpmResolutionPackage;
|
||||||
|
use deno_semver::jsr::JsrDepPackageReq;
|
||||||
|
use deno_semver::package::PackageNv;
|
||||||
|
use deno_semver::package::PackageReq;
|
||||||
|
use deno_semver::SmallStackString;
|
||||||
|
use deno_semver::VersionReq;
|
||||||
|
|
||||||
|
use super::resolution::NpmResolution;
|
||||||
|
use crate::args::CliLockfile;
|
||||||
|
use crate::npm::CliNpmRegistryInfoProvider;
|
||||||
|
use crate::util::sync::TaskQueue;
|
||||||
|
|
||||||
|
pub struct AddPkgReqsResult {
|
||||||
|
/// Results from adding the individual packages.
|
||||||
|
///
|
||||||
|
/// The indexes of the results correspond to the indexes of the provided
|
||||||
|
/// package requirements.
|
||||||
|
pub results: Vec<Result<PackageNv, NpmResolutionError>>,
|
||||||
|
/// The final result of resolving and caching all the package requirements.
|
||||||
|
pub dependencies_result: Result<(), JsErrorBox>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the npm resolution with the provided package requirements.
|
||||||
|
pub struct NpmResolutionInstaller {
|
||||||
|
registry_info_provider: Arc<CliNpmRegistryInfoProvider>,
|
||||||
|
resolution: Arc<NpmResolution>,
|
||||||
|
maybe_lockfile: Option<Arc<CliLockfile>>,
|
||||||
|
update_queue: TaskQueue,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NpmResolutionInstaller {
|
||||||
|
pub fn new(
|
||||||
|
registry_info_provider: Arc<CliNpmRegistryInfoProvider>,
|
||||||
|
resolution: Arc<NpmResolution>,
|
||||||
|
maybe_lockfile: Option<Arc<CliLockfile>>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
registry_info_provider,
|
||||||
|
resolution,
|
||||||
|
maybe_lockfile,
|
||||||
|
update_queue: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn add_package_reqs(
|
||||||
|
&self,
|
||||||
|
package_reqs: &[PackageReq],
|
||||||
|
) -> AddPkgReqsResult {
|
||||||
|
// only allow one thread in here at a time
|
||||||
|
let _snapshot_lock = self.update_queue.acquire().await;
|
||||||
|
let result = add_package_reqs_to_snapshot(
|
||||||
|
&self.registry_info_provider,
|
||||||
|
package_reqs,
|
||||||
|
self.maybe_lockfile.clone(),
|
||||||
|
|| self.resolution.snapshot(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
AddPkgReqsResult {
|
||||||
|
results: result.results,
|
||||||
|
dependencies_result: match result.dep_graph_result {
|
||||||
|
Ok(snapshot) => {
|
||||||
|
self.resolution.set_snapshot(snapshot);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Err(err) => Err(JsErrorBox::from_err(err)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_package_reqs(
|
||||||
|
&self,
|
||||||
|
package_reqs: &[PackageReq],
|
||||||
|
) -> Result<(), AnyError> {
|
||||||
|
// only allow one thread in here at a time
|
||||||
|
let _snapshot_lock = self.update_queue.acquire().await;
|
||||||
|
|
||||||
|
let reqs_set = package_reqs.iter().collect::<HashSet<_>>();
|
||||||
|
let snapshot = add_package_reqs_to_snapshot(
|
||||||
|
&self.registry_info_provider,
|
||||||
|
package_reqs,
|
||||||
|
self.maybe_lockfile.clone(),
|
||||||
|
|| {
|
||||||
|
let snapshot = self.resolution.snapshot();
|
||||||
|
let has_removed_package = !snapshot
|
||||||
|
.package_reqs()
|
||||||
|
.keys()
|
||||||
|
.all(|req| reqs_set.contains(req));
|
||||||
|
// if any packages were removed, we need to completely recreate the npm resolution snapshot
|
||||||
|
if has_removed_package {
|
||||||
|
snapshot.into_empty()
|
||||||
|
} else {
|
||||||
|
snapshot
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.into_result()?;
|
||||||
|
|
||||||
|
self.resolution.set_snapshot(snapshot);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn add_package_reqs_to_snapshot(
|
||||||
|
registry_info_provider: &Arc<CliNpmRegistryInfoProvider>,
|
||||||
|
package_reqs: &[PackageReq],
|
||||||
|
maybe_lockfile: Option<Arc<CliLockfile>>,
|
||||||
|
get_new_snapshot: impl Fn() -> NpmResolutionSnapshot,
|
||||||
|
) -> deno_npm::resolution::AddPkgReqsResult {
|
||||||
|
let snapshot = get_new_snapshot();
|
||||||
|
if package_reqs
|
||||||
|
.iter()
|
||||||
|
.all(|req| snapshot.package_reqs().contains_key(req))
|
||||||
|
{
|
||||||
|
log::debug!("Snapshot already up to date. Skipping npm resolution.");
|
||||||
|
return deno_npm::resolution::AddPkgReqsResult {
|
||||||
|
results: package_reqs
|
||||||
|
.iter()
|
||||||
|
.map(|req| Ok(snapshot.package_reqs().get(req).unwrap().clone()))
|
||||||
|
.collect(),
|
||||||
|
dep_graph_result: Ok(snapshot),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
log::debug!(
|
||||||
|
/* this string is used in tests */
|
||||||
|
"Running npm resolution."
|
||||||
|
);
|
||||||
|
let npm_registry_api = registry_info_provider.as_npm_registry_api();
|
||||||
|
let result = snapshot
|
||||||
|
.add_pkg_reqs(&npm_registry_api, get_add_pkg_reqs_options(package_reqs))
|
||||||
|
.await;
|
||||||
|
let result = match &result.dep_graph_result {
|
||||||
|
Err(NpmResolutionError::Resolution(err))
|
||||||
|
if npm_registry_api.mark_force_reload() =>
|
||||||
|
{
|
||||||
|
log::debug!("{err:#}");
|
||||||
|
log::debug!("npm resolution failed. Trying again...");
|
||||||
|
|
||||||
|
// try again with forced reloading
|
||||||
|
let snapshot = get_new_snapshot();
|
||||||
|
snapshot
|
||||||
|
.add_pkg_reqs(&npm_registry_api, get_add_pkg_reqs_options(package_reqs))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
_ => result,
|
||||||
|
};
|
||||||
|
|
||||||
|
registry_info_provider.clear_memory_cache();
|
||||||
|
|
||||||
|
if let Ok(snapshot) = &result.dep_graph_result {
|
||||||
|
if let Some(lockfile) = maybe_lockfile {
|
||||||
|
populate_lockfile_from_snapshot(&lockfile, snapshot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_add_pkg_reqs_options(package_reqs: &[PackageReq]) -> AddPkgReqsOptions {
|
||||||
|
AddPkgReqsOptions {
|
||||||
|
package_reqs,
|
||||||
|
// WARNING: When bumping this version, check if anything needs to be
|
||||||
|
// updated in the `setNodeOnlyGlobalNames` call in 99_main_compiler.js
|
||||||
|
types_node_version_req: Some(
|
||||||
|
VersionReq::parse_from_npm("22.0.0 - 22.5.4").unwrap(),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn populate_lockfile_from_snapshot(
|
||||||
|
lockfile: &CliLockfile,
|
||||||
|
snapshot: &NpmResolutionSnapshot,
|
||||||
|
) {
|
||||||
|
fn npm_package_to_lockfile_info(
|
||||||
|
pkg: &NpmResolutionPackage,
|
||||||
|
) -> NpmPackageLockfileInfo {
|
||||||
|
let dependencies = pkg
|
||||||
|
.dependencies
|
||||||
|
.iter()
|
||||||
|
.map(|(name, id)| NpmPackageDependencyLockfileInfo {
|
||||||
|
name: name.clone(),
|
||||||
|
id: id.as_serialized(),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
NpmPackageLockfileInfo {
|
||||||
|
serialized_id: pkg.id.as_serialized(),
|
||||||
|
integrity: pkg.dist.integrity().for_lockfile(),
|
||||||
|
dependencies,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut lockfile = lockfile.lock();
|
||||||
|
for (package_req, nv) in snapshot.package_reqs() {
|
||||||
|
let id = &snapshot.resolve_package_from_deno_module(nv).unwrap().id;
|
||||||
|
lockfile.insert_package_specifier(
|
||||||
|
JsrDepPackageReq::npm(package_req.clone()),
|
||||||
|
{
|
||||||
|
StringBuilder::<SmallStackString>::build(|builder| {
|
||||||
|
builder.append(&id.nv.version);
|
||||||
|
builder.append(&id.peer_dependencies);
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for package in snapshot.all_packages_for_every_system() {
|
||||||
|
lockfile.insert_npm_package(npm_package_to_lockfile_info(package));
|
||||||
|
}
|
||||||
|
}
|
18
cli/npm/managed/installers/common/mod.rs
Normal file
18
cli/npm/managed/installers/common/mod.rs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright 2018-2025 the Deno authors. MIT license.
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use deno_error::JsErrorBox;
|
||||||
|
|
||||||
|
use crate::npm::PackageCaching;
|
||||||
|
|
||||||
|
pub mod bin_entries;
|
||||||
|
pub mod lifecycle_scripts;
|
||||||
|
|
||||||
|
/// Part of the resolution that interacts with the file system.
|
||||||
|
#[async_trait(?Send)]
|
||||||
|
pub trait NpmPackageFsInstaller: Send + Sync {
|
||||||
|
async fn cache_packages<'a>(
|
||||||
|
&self,
|
||||||
|
caching: PackageCaching<'a>,
|
||||||
|
) -> Result<(), JsErrorBox>;
|
||||||
|
}
|
190
cli/npm/managed/installers/global.rs
Normal file
190
cli/npm/managed/installers/global.rs
Normal file
|
@ -0,0 +1,190 @@
|
||||||
|
// Copyright 2018-2025 the Deno authors. MIT license.
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use deno_core::futures::stream::FuturesUnordered;
|
||||||
|
use deno_core::futures::StreamExt;
|
||||||
|
use deno_error::JsErrorBox;
|
||||||
|
use deno_npm::NpmResolutionPackage;
|
||||||
|
use deno_npm::NpmSystemInfo;
|
||||||
|
|
||||||
|
use super::super::resolution::NpmResolution;
|
||||||
|
use super::common::lifecycle_scripts::LifecycleScriptsStrategy;
|
||||||
|
use super::common::NpmPackageFsInstaller;
|
||||||
|
use crate::args::LifecycleScriptsConfig;
|
||||||
|
use crate::cache::FastInsecureHasher;
|
||||||
|
use crate::colors;
|
||||||
|
use crate::npm::managed::PackageCaching;
|
||||||
|
use crate::npm::CliNpmCache;
|
||||||
|
use crate::npm::CliNpmTarballCache;
|
||||||
|
|
||||||
|
/// Resolves packages from the global npm cache.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct GlobalNpmPackageInstaller {
|
||||||
|
cache: Arc<CliNpmCache>,
|
||||||
|
tarball_cache: Arc<CliNpmTarballCache>,
|
||||||
|
resolution: Arc<NpmResolution>,
|
||||||
|
system_info: NpmSystemInfo,
|
||||||
|
lifecycle_scripts: LifecycleScriptsConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GlobalNpmPackageInstaller {
|
||||||
|
pub fn new(
|
||||||
|
cache: Arc<CliNpmCache>,
|
||||||
|
tarball_cache: Arc<CliNpmTarballCache>,
|
||||||
|
resolution: Arc<NpmResolution>,
|
||||||
|
system_info: NpmSystemInfo,
|
||||||
|
lifecycle_scripts: LifecycleScriptsConfig,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
cache,
|
||||||
|
tarball_cache,
|
||||||
|
resolution,
|
||||||
|
system_info,
|
||||||
|
lifecycle_scripts,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait(?Send)]
|
||||||
|
impl NpmPackageFsInstaller for GlobalNpmPackageInstaller {
|
||||||
|
async fn cache_packages<'a>(
|
||||||
|
&self,
|
||||||
|
caching: PackageCaching<'a>,
|
||||||
|
) -> Result<(), JsErrorBox> {
|
||||||
|
let package_partitions = match caching {
|
||||||
|
PackageCaching::All => self
|
||||||
|
.resolution
|
||||||
|
.all_system_packages_partitioned(&self.system_info),
|
||||||
|
PackageCaching::Only(reqs) => self
|
||||||
|
.resolution
|
||||||
|
.subset(&reqs)
|
||||||
|
.all_system_packages_partitioned(&self.system_info),
|
||||||
|
};
|
||||||
|
cache_packages(&package_partitions.packages, &self.tarball_cache)
|
||||||
|
.await
|
||||||
|
.map_err(JsErrorBox::from_err)?;
|
||||||
|
|
||||||
|
// create the copy package folders
|
||||||
|
for copy in package_partitions.copy_packages {
|
||||||
|
self
|
||||||
|
.cache
|
||||||
|
.ensure_copy_package(©.get_package_cache_folder_id())
|
||||||
|
.map_err(JsErrorBox::from_err)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut lifecycle_scripts =
|
||||||
|
super::common::lifecycle_scripts::LifecycleScripts::new(
|
||||||
|
&self.lifecycle_scripts,
|
||||||
|
GlobalLifecycleScripts::new(self, &self.lifecycle_scripts.root_dir),
|
||||||
|
);
|
||||||
|
for package in &package_partitions.packages {
|
||||||
|
let package_folder = self.cache.package_folder_for_nv(&package.id.nv);
|
||||||
|
lifecycle_scripts.add(package, Cow::Borrowed(&package_folder));
|
||||||
|
}
|
||||||
|
|
||||||
|
lifecycle_scripts
|
||||||
|
.warn_not_run_scripts()
|
||||||
|
.map_err(JsErrorBox::from_err)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn cache_packages(
|
||||||
|
packages: &[NpmResolutionPackage],
|
||||||
|
tarball_cache: &Arc<CliNpmTarballCache>,
|
||||||
|
) -> Result<(), deno_npm_cache::EnsurePackageError> {
|
||||||
|
let mut futures_unordered = FuturesUnordered::new();
|
||||||
|
for package in packages {
|
||||||
|
futures_unordered.push(async move {
|
||||||
|
tarball_cache
|
||||||
|
.ensure_package(&package.id.nv, &package.dist)
|
||||||
|
.await
|
||||||
|
});
|
||||||
|
}
|
||||||
|
while let Some(result) = futures_unordered.next().await {
|
||||||
|
// surface the first error
|
||||||
|
result?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GlobalLifecycleScripts<'a> {
|
||||||
|
installer: &'a GlobalNpmPackageInstaller,
|
||||||
|
path_hash: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> GlobalLifecycleScripts<'a> {
|
||||||
|
fn new(installer: &'a GlobalNpmPackageInstaller, root_dir: &Path) -> Self {
|
||||||
|
let mut hasher = FastInsecureHasher::new_without_deno_version();
|
||||||
|
hasher.write(root_dir.to_string_lossy().as_bytes());
|
||||||
|
let path_hash = hasher.finish();
|
||||||
|
Self {
|
||||||
|
installer,
|
||||||
|
path_hash,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn warned_scripts_file(&self, package: &NpmResolutionPackage) -> PathBuf {
|
||||||
|
self
|
||||||
|
.package_path(package)
|
||||||
|
.join(format!(".scripts-warned-{}", self.path_hash))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> super::common::lifecycle_scripts::LifecycleScriptsStrategy
|
||||||
|
for GlobalLifecycleScripts<'a>
|
||||||
|
{
|
||||||
|
fn can_run_scripts(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
fn package_path(&self, package: &NpmResolutionPackage) -> PathBuf {
|
||||||
|
self.installer.cache.package_folder_for_nv(&package.id.nv)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn warn_on_scripts_not_run(
|
||||||
|
&self,
|
||||||
|
packages: &[(&NpmResolutionPackage, PathBuf)],
|
||||||
|
) -> std::result::Result<(), std::io::Error> {
|
||||||
|
log::warn!("{} The following packages contained npm lifecycle scripts ({}) that were not executed:", colors::yellow("Warning"), colors::gray("preinstall/install/postinstall"));
|
||||||
|
for (package, _) in packages {
|
||||||
|
log::warn!("┠─ {}", colors::gray(format!("npm:{}", package.id.nv)));
|
||||||
|
}
|
||||||
|
log::warn!("┃");
|
||||||
|
log::warn!(
|
||||||
|
"┠─ {}",
|
||||||
|
colors::italic("This may cause the packages to not work correctly.")
|
||||||
|
);
|
||||||
|
log::warn!("┠─ {}", colors::italic("Lifecycle scripts are only supported when using a `node_modules` directory."));
|
||||||
|
log::warn!(
|
||||||
|
"┠─ {}",
|
||||||
|
colors::italic("Enable it in your deno config file:")
|
||||||
|
);
|
||||||
|
log::warn!("┖─ {}", colors::bold("\"nodeModulesDir\": \"auto\""));
|
||||||
|
|
||||||
|
for (package, _) in packages {
|
||||||
|
std::fs::write(self.warned_scripts_file(package), "")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn did_run_scripts(
|
||||||
|
&self,
|
||||||
|
_package: &NpmResolutionPackage,
|
||||||
|
) -> Result<(), std::io::Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_warned(&self, package: &NpmResolutionPackage) -> bool {
|
||||||
|
self.warned_scripts_file(package).exists()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_run(&self, _package: &NpmResolutionPackage) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
1032
cli/npm/managed/installers/local.rs
Normal file
1032
cli/npm/managed/installers/local.rs
Normal file
File diff suppressed because it is too large
Load diff
55
cli/npm/managed/installers/mod.rs
Normal file
55
cli/npm/managed/installers/mod.rs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
// Copyright 2018-2025 the Deno authors. MIT license.
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use deno_npm::NpmSystemInfo;
|
||||||
|
|
||||||
|
pub use self::common::NpmPackageFsInstaller;
|
||||||
|
use self::global::GlobalNpmPackageInstaller;
|
||||||
|
use self::local::LocalNpmPackageInstaller;
|
||||||
|
use super::resolution::NpmResolution;
|
||||||
|
use crate::args::LifecycleScriptsConfig;
|
||||||
|
use crate::args::NpmInstallDepsProvider;
|
||||||
|
use crate::npm::CliNpmCache;
|
||||||
|
use crate::npm::CliNpmTarballCache;
|
||||||
|
use crate::sys::CliSys;
|
||||||
|
use crate::util::progress_bar::ProgressBar;
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
mod global;
|
||||||
|
mod local;
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn create_npm_fs_installer(
|
||||||
|
npm_cache: Arc<CliNpmCache>,
|
||||||
|
npm_install_deps_provider: &Arc<NpmInstallDepsProvider>,
|
||||||
|
progress_bar: &ProgressBar,
|
||||||
|
resolution: Arc<NpmResolution>,
|
||||||
|
sys: CliSys,
|
||||||
|
tarball_cache: Arc<CliNpmTarballCache>,
|
||||||
|
maybe_node_modules_path: Option<PathBuf>,
|
||||||
|
system_info: NpmSystemInfo,
|
||||||
|
lifecycle_scripts: LifecycleScriptsConfig,
|
||||||
|
) -> Arc<dyn NpmPackageFsInstaller> {
|
||||||
|
match maybe_node_modules_path {
|
||||||
|
Some(node_modules_folder) => Arc::new(LocalNpmPackageInstaller::new(
|
||||||
|
npm_cache,
|
||||||
|
npm_install_deps_provider.clone(),
|
||||||
|
progress_bar.clone(),
|
||||||
|
resolution,
|
||||||
|
sys,
|
||||||
|
tarball_cache,
|
||||||
|
node_modules_folder,
|
||||||
|
system_info,
|
||||||
|
lifecycle_scripts,
|
||||||
|
)),
|
||||||
|
None => Arc::new(GlobalNpmPackageInstaller::new(
|
||||||
|
npm_cache,
|
||||||
|
tarball_cache,
|
||||||
|
resolution,
|
||||||
|
system_info,
|
||||||
|
lifecycle_scripts,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,11 +28,14 @@ use deno_runtime::colors;
|
||||||
use deno_runtime::ops::process::NpmProcessStateProvider;
|
use deno_runtime::ops::process::NpmProcessStateProvider;
|
||||||
use deno_semver::package::PackageNv;
|
use deno_semver::package::PackageNv;
|
||||||
use deno_semver::package::PackageReq;
|
use deno_semver::package::PackageReq;
|
||||||
|
use installer::AddPkgReqsResult;
|
||||||
|
use installer::NpmResolutionInstaller;
|
||||||
|
use installers::create_npm_fs_installer;
|
||||||
|
use installers::NpmPackageFsInstaller;
|
||||||
use node_resolver::errors::PackageFolderResolveError;
|
use node_resolver::errors::PackageFolderResolveError;
|
||||||
use node_resolver::errors::PackageFolderResolveIoError;
|
use node_resolver::errors::PackageFolderResolveIoError;
|
||||||
use node_resolver::InNpmPackageChecker;
|
use node_resolver::InNpmPackageChecker;
|
||||||
use node_resolver::NpmPackageFolderResolver;
|
use node_resolver::NpmPackageFolderResolver;
|
||||||
use resolution::AddPkgReqsResult;
|
|
||||||
|
|
||||||
use self::resolution::NpmResolution;
|
use self::resolution::NpmResolution;
|
||||||
use self::resolvers::create_npm_fs_resolver;
|
use self::resolvers::create_npm_fs_resolver;
|
||||||
|
@ -55,6 +58,8 @@ use crate::sys::CliSys;
|
||||||
use crate::util::progress_bar::ProgressBar;
|
use crate::util::progress_bar::ProgressBar;
|
||||||
use crate::util::sync::AtomicFlag;
|
use crate::util::sync::AtomicFlag;
|
||||||
|
|
||||||
|
mod installer;
|
||||||
|
mod installers;
|
||||||
mod resolution;
|
mod resolution;
|
||||||
mod resolvers;
|
mod resolvers;
|
||||||
|
|
||||||
|
@ -156,11 +161,7 @@ fn create_inner(
|
||||||
snapshot: Option<ValidSerializedNpmResolutionSnapshot>,
|
snapshot: Option<ValidSerializedNpmResolutionSnapshot>,
|
||||||
lifecycle_scripts: LifecycleScriptsConfig,
|
lifecycle_scripts: LifecycleScriptsConfig,
|
||||||
) -> Arc<dyn CliNpmResolver> {
|
) -> Arc<dyn CliNpmResolver> {
|
||||||
let resolution = Arc::new(NpmResolution::from_serialized(
|
let resolution = Arc::new(NpmResolution::from_serialized(snapshot));
|
||||||
registry_info_provider.clone(),
|
|
||||||
snapshot,
|
|
||||||
maybe_lockfile.clone(),
|
|
||||||
));
|
|
||||||
let tarball_cache = Arc::new(CliNpmTarballCache::new(
|
let tarball_cache = Arc::new(CliNpmTarballCache::new(
|
||||||
npm_cache.clone(),
|
npm_cache.clone(),
|
||||||
http_client,
|
http_client,
|
||||||
|
@ -168,18 +169,25 @@ fn create_inner(
|
||||||
npm_rc.clone(),
|
npm_rc.clone(),
|
||||||
));
|
));
|
||||||
|
|
||||||
let fs_resolver = create_npm_fs_resolver(
|
let fs_installer = create_npm_fs_installer(
|
||||||
npm_cache.clone(),
|
npm_cache.clone(),
|
||||||
&npm_install_deps_provider,
|
&npm_install_deps_provider,
|
||||||
&text_only_progress_bar,
|
&text_only_progress_bar,
|
||||||
resolution.clone(),
|
resolution.clone(),
|
||||||
sys.clone(),
|
sys.clone(),
|
||||||
tarball_cache.clone(),
|
tarball_cache.clone(),
|
||||||
node_modules_dir_path,
|
node_modules_dir_path.clone(),
|
||||||
npm_system_info.clone(),
|
npm_system_info.clone(),
|
||||||
lifecycle_scripts.clone(),
|
lifecycle_scripts.clone(),
|
||||||
);
|
);
|
||||||
|
let fs_resolver = create_npm_fs_resolver(
|
||||||
|
npm_cache.clone(),
|
||||||
|
resolution.clone(),
|
||||||
|
sys.clone(),
|
||||||
|
node_modules_dir_path,
|
||||||
|
);
|
||||||
Arc::new(ManagedCliNpmResolver::new(
|
Arc::new(ManagedCliNpmResolver::new(
|
||||||
|
fs_installer,
|
||||||
fs_resolver,
|
fs_resolver,
|
||||||
maybe_lockfile,
|
maybe_lockfile,
|
||||||
registry_info_provider,
|
registry_info_provider,
|
||||||
|
@ -301,6 +309,7 @@ pub enum PackageCaching<'a> {
|
||||||
/// An npm resolver where the resolution is managed by Deno rather than
|
/// An npm resolver where the resolution is managed by Deno rather than
|
||||||
/// the user bringing their own node_modules (BYONM) on the file system.
|
/// the user bringing their own node_modules (BYONM) on the file system.
|
||||||
pub struct ManagedCliNpmResolver {
|
pub struct ManagedCliNpmResolver {
|
||||||
|
fs_installer: Arc<dyn NpmPackageFsInstaller>,
|
||||||
fs_resolver: Arc<dyn NpmPackageFsResolver>,
|
fs_resolver: Arc<dyn NpmPackageFsResolver>,
|
||||||
maybe_lockfile: Option<Arc<CliLockfile>>,
|
maybe_lockfile: Option<Arc<CliLockfile>>,
|
||||||
registry_info_provider: Arc<CliNpmRegistryInfoProvider>,
|
registry_info_provider: Arc<CliNpmRegistryInfoProvider>,
|
||||||
|
@ -308,6 +317,7 @@ pub struct ManagedCliNpmResolver {
|
||||||
npm_install_deps_provider: Arc<NpmInstallDepsProvider>,
|
npm_install_deps_provider: Arc<NpmInstallDepsProvider>,
|
||||||
sys: CliSys,
|
sys: CliSys,
|
||||||
resolution: Arc<NpmResolution>,
|
resolution: Arc<NpmResolution>,
|
||||||
|
resolution_installer: NpmResolutionInstaller,
|
||||||
tarball_cache: Arc<CliNpmTarballCache>,
|
tarball_cache: Arc<CliNpmTarballCache>,
|
||||||
text_only_progress_bar: ProgressBar,
|
text_only_progress_bar: ProgressBar,
|
||||||
npm_system_info: NpmSystemInfo,
|
npm_system_info: NpmSystemInfo,
|
||||||
|
@ -348,6 +358,7 @@ pub enum ResolvePkgFolderFromDenoModuleError {
|
||||||
impl ManagedCliNpmResolver {
|
impl ManagedCliNpmResolver {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
|
fs_installer: Arc<dyn NpmPackageFsInstaller>,
|
||||||
fs_resolver: Arc<dyn NpmPackageFsResolver>,
|
fs_resolver: Arc<dyn NpmPackageFsResolver>,
|
||||||
maybe_lockfile: Option<Arc<CliLockfile>>,
|
maybe_lockfile: Option<Arc<CliLockfile>>,
|
||||||
registry_info_provider: Arc<CliNpmRegistryInfoProvider>,
|
registry_info_provider: Arc<CliNpmRegistryInfoProvider>,
|
||||||
|
@ -360,7 +371,13 @@ impl ManagedCliNpmResolver {
|
||||||
npm_system_info: NpmSystemInfo,
|
npm_system_info: NpmSystemInfo,
|
||||||
lifecycle_scripts: LifecycleScriptsConfig,
|
lifecycle_scripts: LifecycleScriptsConfig,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let resolution_installer = NpmResolutionInstaller::new(
|
||||||
|
registry_info_provider.clone(),
|
||||||
|
resolution.clone(),
|
||||||
|
maybe_lockfile.clone(),
|
||||||
|
);
|
||||||
Self {
|
Self {
|
||||||
|
fs_installer,
|
||||||
fs_resolver,
|
fs_resolver,
|
||||||
maybe_lockfile,
|
maybe_lockfile,
|
||||||
registry_info_provider,
|
registry_info_provider,
|
||||||
|
@ -368,6 +385,7 @@ impl ManagedCliNpmResolver {
|
||||||
npm_install_deps_provider,
|
npm_install_deps_provider,
|
||||||
text_only_progress_bar,
|
text_only_progress_bar,
|
||||||
resolution,
|
resolution,
|
||||||
|
resolution_installer,
|
||||||
sys,
|
sys,
|
||||||
tarball_cache,
|
tarball_cache,
|
||||||
npm_system_info,
|
npm_system_info,
|
||||||
|
@ -489,7 +507,7 @@ impl ManagedCliNpmResolver {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut result = self.resolution.add_package_reqs(packages).await;
|
let mut result = self.resolution_installer.add_package_reqs(packages).await;
|
||||||
|
|
||||||
if result.dependencies_result.is_ok() {
|
if result.dependencies_result.is_ok() {
|
||||||
if let Some(lockfile) = self.maybe_lockfile.as_ref() {
|
if let Some(lockfile) = self.maybe_lockfile.as_ref() {
|
||||||
|
@ -512,7 +530,7 @@ impl ManagedCliNpmResolver {
|
||||||
&self,
|
&self,
|
||||||
packages: &[PackageReq],
|
packages: &[PackageReq],
|
||||||
) -> Result<(), AnyError> {
|
) -> Result<(), AnyError> {
|
||||||
self.resolution.set_package_reqs(packages).await
|
self.resolution_installer.set_package_reqs(packages).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn snapshot(&self) -> NpmResolutionSnapshot {
|
pub fn snapshot(&self) -> NpmResolutionSnapshot {
|
||||||
|
@ -554,7 +572,7 @@ impl ManagedCliNpmResolver {
|
||||||
&self,
|
&self,
|
||||||
caching: PackageCaching<'_>,
|
caching: PackageCaching<'_>,
|
||||||
) -> Result<(), JsErrorBox> {
|
) -> Result<(), JsErrorBox> {
|
||||||
self.fs_resolver.cache_packages(caching).await
|
self.fs_installer.cache_packages(caching).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resolve_pkg_folder_from_deno_module(
|
pub fn resolve_pkg_folder_from_deno_module(
|
||||||
|
@ -738,14 +756,11 @@ impl CliNpmResolver for ManagedCliNpmResolver {
|
||||||
|
|
||||||
fn clone_snapshotted(&self) -> Arc<dyn CliNpmResolver> {
|
fn clone_snapshotted(&self) -> Arc<dyn CliNpmResolver> {
|
||||||
// create a new snapshotted npm resolution and resolver
|
// create a new snapshotted npm resolution and resolver
|
||||||
let npm_resolution = Arc::new(NpmResolution::new(
|
let npm_resolution =
|
||||||
self.registry_info_provider.clone(),
|
Arc::new(NpmResolution::new(self.resolution.snapshot()));
|
||||||
self.resolution.snapshot(),
|
|
||||||
self.maybe_lockfile.clone(),
|
|
||||||
));
|
|
||||||
|
|
||||||
Arc::new(ManagedCliNpmResolver::new(
|
Arc::new(ManagedCliNpmResolver::new(
|
||||||
create_npm_fs_resolver(
|
create_npm_fs_installer(
|
||||||
self.npm_cache.clone(),
|
self.npm_cache.clone(),
|
||||||
&self.npm_install_deps_provider,
|
&self.npm_install_deps_provider,
|
||||||
&self.text_only_progress_bar,
|
&self.text_only_progress_bar,
|
||||||
|
@ -756,6 +771,12 @@ impl CliNpmResolver for ManagedCliNpmResolver {
|
||||||
self.npm_system_info.clone(),
|
self.npm_system_info.clone(),
|
||||||
self.lifecycle_scripts.clone(),
|
self.lifecycle_scripts.clone(),
|
||||||
),
|
),
|
||||||
|
create_npm_fs_resolver(
|
||||||
|
self.npm_cache.clone(),
|
||||||
|
npm_resolution.clone(),
|
||||||
|
self.sys.clone(),
|
||||||
|
self.root_node_modules_path().map(ToOwned::to_owned),
|
||||||
|
),
|
||||||
self.maybe_lockfile.clone(),
|
self.maybe_lockfile.clone(),
|
||||||
self.registry_info_provider.clone(),
|
self.registry_info_provider.clone(),
|
||||||
self.npm_cache.clone(),
|
self.npm_cache.clone(),
|
||||||
|
|
|
@ -1,18 +1,9 @@
|
||||||
// Copyright 2018-2025 the Deno authors. MIT license.
|
// Copyright 2018-2025 the Deno authors. MIT license.
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use capacity_builder::StringBuilder;
|
use deno_core::parking_lot::RwLock;
|
||||||
use deno_core::error::AnyError;
|
|
||||||
use deno_error::JsErrorBox;
|
|
||||||
use deno_lockfile::NpmPackageDependencyLockfileInfo;
|
|
||||||
use deno_lockfile::NpmPackageLockfileInfo;
|
|
||||||
use deno_npm::registry::NpmRegistryApi;
|
|
||||||
use deno_npm::resolution::AddPkgReqsOptions;
|
|
||||||
use deno_npm::resolution::NpmPackagesPartitioned;
|
use deno_npm::resolution::NpmPackagesPartitioned;
|
||||||
use deno_npm::resolution::NpmResolutionError;
|
|
||||||
use deno_npm::resolution::NpmResolutionSnapshot;
|
use deno_npm::resolution::NpmResolutionSnapshot;
|
||||||
use deno_npm::resolution::PackageCacheFolderIdNotFoundError;
|
use deno_npm::resolution::PackageCacheFolderIdNotFoundError;
|
||||||
use deno_npm::resolution::PackageNotFoundFromReferrerError;
|
use deno_npm::resolution::PackageNotFoundFromReferrerError;
|
||||||
|
@ -23,25 +14,8 @@ use deno_npm::NpmPackageCacheFolderId;
|
||||||
use deno_npm::NpmPackageId;
|
use deno_npm::NpmPackageId;
|
||||||
use deno_npm::NpmResolutionPackage;
|
use deno_npm::NpmResolutionPackage;
|
||||||
use deno_npm::NpmSystemInfo;
|
use deno_npm::NpmSystemInfo;
|
||||||
use deno_semver::jsr::JsrDepPackageReq;
|
|
||||||
use deno_semver::package::PackageNv;
|
use deno_semver::package::PackageNv;
|
||||||
use deno_semver::package::PackageReq;
|
use deno_semver::package::PackageReq;
|
||||||
use deno_semver::SmallStackString;
|
|
||||||
use deno_semver::VersionReq;
|
|
||||||
|
|
||||||
use crate::args::CliLockfile;
|
|
||||||
use crate::npm::CliNpmRegistryInfoProvider;
|
|
||||||
use crate::util::sync::SyncReadAsyncWriteLock;
|
|
||||||
|
|
||||||
pub struct AddPkgReqsResult {
|
|
||||||
/// Results from adding the individual packages.
|
|
||||||
///
|
|
||||||
/// The indexes of the results correspond to the indexes of the provided
|
|
||||||
/// package requirements.
|
|
||||||
pub results: Vec<Result<PackageNv, NpmResolutionError>>,
|
|
||||||
/// The final result of resolving and caching all the package requirements.
|
|
||||||
pub dependencies_result: Result<(), JsErrorBox>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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
|
||||||
|
@ -49,9 +23,7 @@ pub struct AddPkgReqsResult {
|
||||||
///
|
///
|
||||||
/// This does not interact with the file system.
|
/// This does not interact with the file system.
|
||||||
pub struct NpmResolution {
|
pub struct NpmResolution {
|
||||||
registry_info_provider: Arc<CliNpmRegistryInfoProvider>,
|
snapshot: RwLock<NpmResolutionSnapshot>,
|
||||||
snapshot: SyncReadAsyncWriteLock<NpmResolutionSnapshot>,
|
|
||||||
maybe_lockfile: Option<Arc<CliLockfile>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for NpmResolution {
|
impl std::fmt::Debug for NpmResolution {
|
||||||
|
@ -65,87 +37,19 @@ impl std::fmt::Debug for NpmResolution {
|
||||||
|
|
||||||
impl NpmResolution {
|
impl NpmResolution {
|
||||||
pub fn from_serialized(
|
pub fn from_serialized(
|
||||||
registry_info_provider: Arc<CliNpmRegistryInfoProvider>,
|
|
||||||
initial_snapshot: Option<ValidSerializedNpmResolutionSnapshot>,
|
initial_snapshot: Option<ValidSerializedNpmResolutionSnapshot>,
|
||||||
maybe_lockfile: Option<Arc<CliLockfile>>,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let snapshot =
|
let snapshot =
|
||||||
NpmResolutionSnapshot::new(initial_snapshot.unwrap_or_default());
|
NpmResolutionSnapshot::new(initial_snapshot.unwrap_or_default());
|
||||||
Self::new(registry_info_provider, snapshot, maybe_lockfile)
|
Self::new(snapshot)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(
|
pub fn new(initial_snapshot: NpmResolutionSnapshot) -> Self {
|
||||||
registry_info_provider: Arc<CliNpmRegistryInfoProvider>,
|
|
||||||
initial_snapshot: NpmResolutionSnapshot,
|
|
||||||
maybe_lockfile: Option<Arc<CliLockfile>>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
registry_info_provider,
|
snapshot: RwLock::new(initial_snapshot),
|
||||||
snapshot: SyncReadAsyncWriteLock::new(initial_snapshot),
|
|
||||||
maybe_lockfile,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn add_package_reqs(
|
|
||||||
&self,
|
|
||||||
package_reqs: &[PackageReq],
|
|
||||||
) -> AddPkgReqsResult {
|
|
||||||
// only allow one thread in here at a time
|
|
||||||
let snapshot_lock = self.snapshot.acquire().await;
|
|
||||||
let result = add_package_reqs_to_snapshot(
|
|
||||||
&self.registry_info_provider,
|
|
||||||
package_reqs,
|
|
||||||
self.maybe_lockfile.clone(),
|
|
||||||
|| snapshot_lock.read().clone(),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
AddPkgReqsResult {
|
|
||||||
results: result.results,
|
|
||||||
dependencies_result: match result.dep_graph_result {
|
|
||||||
Ok(snapshot) => {
|
|
||||||
*snapshot_lock.write() = snapshot;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Err(err) => Err(JsErrorBox::from_err(err)),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn set_package_reqs(
|
|
||||||
&self,
|
|
||||||
package_reqs: &[PackageReq],
|
|
||||||
) -> Result<(), AnyError> {
|
|
||||||
// only allow one thread in here at a time
|
|
||||||
let snapshot_lock = self.snapshot.acquire().await;
|
|
||||||
|
|
||||||
let reqs_set = package_reqs.iter().collect::<HashSet<_>>();
|
|
||||||
let snapshot = add_package_reqs_to_snapshot(
|
|
||||||
&self.registry_info_provider,
|
|
||||||
package_reqs,
|
|
||||||
self.maybe_lockfile.clone(),
|
|
||||||
|| {
|
|
||||||
let snapshot = snapshot_lock.read().clone();
|
|
||||||
let has_removed_package = !snapshot
|
|
||||||
.package_reqs()
|
|
||||||
.keys()
|
|
||||||
.all(|req| reqs_set.contains(req));
|
|
||||||
// if any packages were removed, we need to completely recreate the npm resolution snapshot
|
|
||||||
if has_removed_package {
|
|
||||||
snapshot.into_empty()
|
|
||||||
} else {
|
|
||||||
snapshot
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.into_result()?;
|
|
||||||
|
|
||||||
*snapshot_lock.write() = snapshot;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resolve_pkg_cache_folder_id_from_pkg_id(
|
pub fn resolve_pkg_cache_folder_id_from_pkg_id(
|
||||||
&self,
|
&self,
|
||||||
id: &NpmPackageId,
|
id: &NpmPackageId,
|
||||||
|
@ -262,112 +166,8 @@ impl NpmResolution {
|
||||||
pub fn subset(&self, package_reqs: &[PackageReq]) -> NpmResolutionSnapshot {
|
pub fn subset(&self, package_reqs: &[PackageReq]) -> NpmResolutionSnapshot {
|
||||||
self.snapshot.read().subset(package_reqs)
|
self.snapshot.read().subset(package_reqs)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
async fn add_package_reqs_to_snapshot(
|
pub fn set_snapshot(&self, snapshot: NpmResolutionSnapshot) {
|
||||||
registry_info_provider: &Arc<CliNpmRegistryInfoProvider>,
|
*self.snapshot.write() = snapshot;
|
||||||
package_reqs: &[PackageReq],
|
|
||||||
maybe_lockfile: Option<Arc<CliLockfile>>,
|
|
||||||
get_new_snapshot: impl Fn() -> NpmResolutionSnapshot,
|
|
||||||
) -> deno_npm::resolution::AddPkgReqsResult {
|
|
||||||
let snapshot = get_new_snapshot();
|
|
||||||
if package_reqs
|
|
||||||
.iter()
|
|
||||||
.all(|req| snapshot.package_reqs().contains_key(req))
|
|
||||||
{
|
|
||||||
log::debug!("Snapshot already up to date. Skipping npm resolution.");
|
|
||||||
return deno_npm::resolution::AddPkgReqsResult {
|
|
||||||
results: package_reqs
|
|
||||||
.iter()
|
|
||||||
.map(|req| Ok(snapshot.package_reqs().get(req).unwrap().clone()))
|
|
||||||
.collect(),
|
|
||||||
dep_graph_result: Ok(snapshot),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
log::debug!(
|
|
||||||
/* this string is used in tests */
|
|
||||||
"Running npm resolution."
|
|
||||||
);
|
|
||||||
let npm_registry_api = registry_info_provider.as_npm_registry_api();
|
|
||||||
let result = snapshot
|
|
||||||
.add_pkg_reqs(&npm_registry_api, get_add_pkg_reqs_options(package_reqs))
|
|
||||||
.await;
|
|
||||||
let result = match &result.dep_graph_result {
|
|
||||||
Err(NpmResolutionError::Resolution(err))
|
|
||||||
if npm_registry_api.mark_force_reload() =>
|
|
||||||
{
|
|
||||||
log::debug!("{err:#}");
|
|
||||||
log::debug!("npm resolution failed. Trying again...");
|
|
||||||
|
|
||||||
// try again with forced reloading
|
|
||||||
let snapshot = get_new_snapshot();
|
|
||||||
snapshot
|
|
||||||
.add_pkg_reqs(&npm_registry_api, get_add_pkg_reqs_options(package_reqs))
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
_ => result,
|
|
||||||
};
|
|
||||||
|
|
||||||
registry_info_provider.clear_memory_cache();
|
|
||||||
|
|
||||||
if let Ok(snapshot) = &result.dep_graph_result {
|
|
||||||
if let Some(lockfile) = maybe_lockfile {
|
|
||||||
populate_lockfile_from_snapshot(&lockfile, snapshot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_add_pkg_reqs_options(package_reqs: &[PackageReq]) -> AddPkgReqsOptions {
|
|
||||||
AddPkgReqsOptions {
|
|
||||||
package_reqs,
|
|
||||||
// WARNING: When bumping this version, check if anything needs to be
|
|
||||||
// updated in the `setNodeOnlyGlobalNames` call in 99_main_compiler.js
|
|
||||||
types_node_version_req: Some(
|
|
||||||
VersionReq::parse_from_npm("22.0.0 - 22.5.4").unwrap(),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn populate_lockfile_from_snapshot(
|
|
||||||
lockfile: &CliLockfile,
|
|
||||||
snapshot: &NpmResolutionSnapshot,
|
|
||||||
) {
|
|
||||||
let mut lockfile = lockfile.lock();
|
|
||||||
for (package_req, nv) in snapshot.package_reqs() {
|
|
||||||
let id = &snapshot.resolve_package_from_deno_module(nv).unwrap().id;
|
|
||||||
lockfile.insert_package_specifier(
|
|
||||||
JsrDepPackageReq::npm(package_req.clone()),
|
|
||||||
{
|
|
||||||
StringBuilder::<SmallStackString>::build(|builder| {
|
|
||||||
builder.append(&id.nv.version);
|
|
||||||
builder.append(&id.peer_dependencies);
|
|
||||||
})
|
|
||||||
.unwrap()
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
for package in snapshot.all_packages_for_every_system() {
|
|
||||||
lockfile.insert_npm_package(npm_package_to_lockfile_info(package));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn npm_package_to_lockfile_info(
|
|
||||||
pkg: &NpmResolutionPackage,
|
|
||||||
) -> NpmPackageLockfileInfo {
|
|
||||||
let dependencies = pkg
|
|
||||||
.dependencies
|
|
||||||
.iter()
|
|
||||||
.map(|(name, id)| NpmPackageDependencyLockfileInfo {
|
|
||||||
name: name.clone(),
|
|
||||||
id: id.as_serialized(),
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
NpmPackageLockfileInfo {
|
|
||||||
serialized_id: pkg.id.as_serialized(),
|
|
||||||
integrity: pkg.dist.integrity().for_lockfile(),
|
|
||||||
dependencies,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,14 @@
|
||||||
// Copyright 2018-2025 the Deno authors. MIT license.
|
// Copyright 2018-2025 the Deno authors. MIT license.
|
||||||
|
|
||||||
pub mod bin_entries;
|
|
||||||
pub mod lifecycle_scripts;
|
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use deno_ast::ModuleSpecifier;
|
use deno_ast::ModuleSpecifier;
|
||||||
use deno_error::JsErrorBox;
|
|
||||||
use deno_npm::NpmPackageCacheFolderId;
|
use deno_npm::NpmPackageCacheFolderId;
|
||||||
use deno_npm::NpmPackageId;
|
use deno_npm::NpmPackageId;
|
||||||
use node_resolver::errors::PackageFolderResolveError;
|
use node_resolver::errors::PackageFolderResolveError;
|
||||||
|
|
||||||
use super::super::PackageCaching;
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error, deno_error::JsError)]
|
#[derive(Debug, thiserror::Error, deno_error::JsError)]
|
||||||
#[class(generic)]
|
#[class(generic)]
|
||||||
#[error("Package folder not found for '{0}'")]
|
#[error("Package folder not found for '{0}'")]
|
||||||
|
@ -47,9 +41,4 @@ pub trait NpmPackageFsResolver: Send + Sync {
|
||||||
&self,
|
&self,
|
||||||
specifier: &ModuleSpecifier,
|
specifier: &ModuleSpecifier,
|
||||||
) -> Result<Option<NpmPackageCacheFolderId>, std::io::Error>;
|
) -> Result<Option<NpmPackageCacheFolderId>, std::io::Error>;
|
||||||
|
|
||||||
async fn cache_packages<'a>(
|
|
||||||
&self,
|
|
||||||
caching: PackageCaching<'a>,
|
|
||||||
) -> Result<(), JsErrorBox>;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,59 +2,32 @@
|
||||||
|
|
||||||
//! Code for global npm cache resolution.
|
//! Code for global npm cache resolution.
|
||||||
|
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use deno_ast::ModuleSpecifier;
|
use deno_ast::ModuleSpecifier;
|
||||||
use deno_core::futures::stream::FuturesUnordered;
|
|
||||||
use deno_core::futures::StreamExt;
|
|
||||||
use deno_error::JsErrorBox;
|
|
||||||
use deno_npm::NpmPackageCacheFolderId;
|
use deno_npm::NpmPackageCacheFolderId;
|
||||||
use deno_npm::NpmPackageId;
|
use deno_npm::NpmPackageId;
|
||||||
use deno_npm::NpmResolutionPackage;
|
|
||||||
use deno_npm::NpmSystemInfo;
|
|
||||||
use node_resolver::errors::PackageFolderResolveError;
|
use node_resolver::errors::PackageFolderResolveError;
|
||||||
use node_resolver::errors::PackageNotFoundError;
|
use node_resolver::errors::PackageNotFoundError;
|
||||||
use node_resolver::errors::ReferrerNotFoundError;
|
use node_resolver::errors::ReferrerNotFoundError;
|
||||||
|
|
||||||
use super::super::resolution::NpmResolution;
|
use super::super::resolution::NpmResolution;
|
||||||
use super::common::lifecycle_scripts::LifecycleScriptsStrategy;
|
|
||||||
use super::common::NpmPackageFsResolver;
|
use super::common::NpmPackageFsResolver;
|
||||||
use crate::args::LifecycleScriptsConfig;
|
|
||||||
use crate::cache::FastInsecureHasher;
|
|
||||||
use crate::colors;
|
|
||||||
use crate::npm::managed::PackageCaching;
|
|
||||||
use crate::npm::CliNpmCache;
|
use crate::npm::CliNpmCache;
|
||||||
use crate::npm::CliNpmTarballCache;
|
|
||||||
|
|
||||||
/// Resolves packages from the global npm cache.
|
/// Resolves packages from the global npm cache.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct GlobalNpmPackageResolver {
|
pub struct GlobalNpmPackageResolver {
|
||||||
cache: Arc<CliNpmCache>,
|
cache: Arc<CliNpmCache>,
|
||||||
tarball_cache: Arc<CliNpmTarballCache>,
|
|
||||||
resolution: Arc<NpmResolution>,
|
resolution: Arc<NpmResolution>,
|
||||||
system_info: NpmSystemInfo,
|
|
||||||
lifecycle_scripts: LifecycleScriptsConfig,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GlobalNpmPackageResolver {
|
impl GlobalNpmPackageResolver {
|
||||||
pub fn new(
|
pub fn new(cache: Arc<CliNpmCache>, resolution: Arc<NpmResolution>) -> Self {
|
||||||
cache: Arc<CliNpmCache>,
|
Self { cache, resolution }
|
||||||
tarball_cache: Arc<CliNpmTarballCache>,
|
|
||||||
resolution: Arc<NpmResolution>,
|
|
||||||
system_info: NpmSystemInfo,
|
|
||||||
lifecycle_scripts: LifecycleScriptsConfig,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
cache,
|
|
||||||
tarball_cache,
|
|
||||||
resolution,
|
|
||||||
system_info,
|
|
||||||
lifecycle_scripts,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,140 +114,4 @@ impl NpmPackageFsResolver for GlobalNpmPackageResolver {
|
||||||
.resolve_package_folder_id_from_specifier(specifier),
|
.resolve_package_folder_id_from_specifier(specifier),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn cache_packages<'a>(
|
|
||||||
&self,
|
|
||||||
caching: PackageCaching<'a>,
|
|
||||||
) -> Result<(), JsErrorBox> {
|
|
||||||
let package_partitions = match caching {
|
|
||||||
PackageCaching::All => self
|
|
||||||
.resolution
|
|
||||||
.all_system_packages_partitioned(&self.system_info),
|
|
||||||
PackageCaching::Only(reqs) => self
|
|
||||||
.resolution
|
|
||||||
.subset(&reqs)
|
|
||||||
.all_system_packages_partitioned(&self.system_info),
|
|
||||||
};
|
|
||||||
cache_packages(&package_partitions.packages, &self.tarball_cache)
|
|
||||||
.await
|
|
||||||
.map_err(JsErrorBox::from_err)?;
|
|
||||||
|
|
||||||
// create the copy package folders
|
|
||||||
for copy in package_partitions.copy_packages {
|
|
||||||
self
|
|
||||||
.cache
|
|
||||||
.ensure_copy_package(©.get_package_cache_folder_id())
|
|
||||||
.map_err(JsErrorBox::from_err)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut lifecycle_scripts =
|
|
||||||
super::common::lifecycle_scripts::LifecycleScripts::new(
|
|
||||||
&self.lifecycle_scripts,
|
|
||||||
GlobalLifecycleScripts::new(self, &self.lifecycle_scripts.root_dir),
|
|
||||||
);
|
|
||||||
for package in &package_partitions.packages {
|
|
||||||
let package_folder = self.cache.package_folder_for_nv(&package.id.nv);
|
|
||||||
lifecycle_scripts.add(package, Cow::Borrowed(&package_folder));
|
|
||||||
}
|
|
||||||
|
|
||||||
lifecycle_scripts
|
|
||||||
.warn_not_run_scripts()
|
|
||||||
.map_err(JsErrorBox::from_err)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn cache_packages(
|
|
||||||
packages: &[NpmResolutionPackage],
|
|
||||||
tarball_cache: &Arc<CliNpmTarballCache>,
|
|
||||||
) -> Result<(), deno_npm_cache::EnsurePackageError> {
|
|
||||||
let mut futures_unordered = FuturesUnordered::new();
|
|
||||||
for package in packages {
|
|
||||||
futures_unordered.push(async move {
|
|
||||||
tarball_cache
|
|
||||||
.ensure_package(&package.id.nv, &package.dist)
|
|
||||||
.await
|
|
||||||
});
|
|
||||||
}
|
|
||||||
while let Some(result) = futures_unordered.next().await {
|
|
||||||
// surface the first error
|
|
||||||
result?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
struct GlobalLifecycleScripts<'a> {
|
|
||||||
resolver: &'a GlobalNpmPackageResolver,
|
|
||||||
path_hash: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> GlobalLifecycleScripts<'a> {
|
|
||||||
fn new(resolver: &'a GlobalNpmPackageResolver, root_dir: &Path) -> Self {
|
|
||||||
let mut hasher = FastInsecureHasher::new_without_deno_version();
|
|
||||||
hasher.write(root_dir.to_string_lossy().as_bytes());
|
|
||||||
let path_hash = hasher.finish();
|
|
||||||
Self {
|
|
||||||
resolver,
|
|
||||||
path_hash,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn warned_scripts_file(&self, package: &NpmResolutionPackage) -> PathBuf {
|
|
||||||
self
|
|
||||||
.package_path(package)
|
|
||||||
.join(format!(".scripts-warned-{}", self.path_hash))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> super::common::lifecycle_scripts::LifecycleScriptsStrategy
|
|
||||||
for GlobalLifecycleScripts<'a>
|
|
||||||
{
|
|
||||||
fn can_run_scripts(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
fn package_path(&self, package: &NpmResolutionPackage) -> PathBuf {
|
|
||||||
self.resolver.cache.package_folder_for_nv(&package.id.nv)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn warn_on_scripts_not_run(
|
|
||||||
&self,
|
|
||||||
packages: &[(&NpmResolutionPackage, PathBuf)],
|
|
||||||
) -> std::result::Result<(), std::io::Error> {
|
|
||||||
log::warn!("{} The following packages contained npm lifecycle scripts ({}) that were not executed:", colors::yellow("Warning"), colors::gray("preinstall/install/postinstall"));
|
|
||||||
for (package, _) in packages {
|
|
||||||
log::warn!("┠─ {}", colors::gray(format!("npm:{}", package.id.nv)));
|
|
||||||
}
|
|
||||||
log::warn!("┃");
|
|
||||||
log::warn!(
|
|
||||||
"┠─ {}",
|
|
||||||
colors::italic("This may cause the packages to not work correctly.")
|
|
||||||
);
|
|
||||||
log::warn!("┠─ {}", colors::italic("Lifecycle scripts are only supported when using a `node_modules` directory."));
|
|
||||||
log::warn!(
|
|
||||||
"┠─ {}",
|
|
||||||
colors::italic("Enable it in your deno config file:")
|
|
||||||
);
|
|
||||||
log::warn!("┖─ {}", colors::bold("\"nodeModulesDir\": \"auto\""));
|
|
||||||
|
|
||||||
for (package, _) in packages {
|
|
||||||
std::fs::write(self.warned_scripts_file(package), "")?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn did_run_scripts(
|
|
||||||
&self,
|
|
||||||
_package: &NpmResolutionPackage,
|
|
||||||
) -> Result<(), std::io::Error> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn has_warned(&self, package: &NpmResolutionPackage) -> bool {
|
|
||||||
self.warned_scripts_file(package).exists()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn has_run(&self, _package: &NpmResolutionPackage) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -7,50 +7,27 @@ mod local;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use deno_npm::NpmSystemInfo;
|
|
||||||
|
|
||||||
pub use self::common::NpmPackageFsResolver;
|
pub use self::common::NpmPackageFsResolver;
|
||||||
pub use self::common::NpmPackageFsResolverPackageFolderError;
|
pub use self::common::NpmPackageFsResolverPackageFolderError;
|
||||||
use self::global::GlobalNpmPackageResolver;
|
use self::global::GlobalNpmPackageResolver;
|
||||||
|
pub use self::local::get_package_folder_id_folder_name;
|
||||||
use self::local::LocalNpmPackageResolver;
|
use self::local::LocalNpmPackageResolver;
|
||||||
use super::resolution::NpmResolution;
|
use super::resolution::NpmResolution;
|
||||||
use crate::args::LifecycleScriptsConfig;
|
|
||||||
use crate::args::NpmInstallDepsProvider;
|
|
||||||
use crate::npm::CliNpmCache;
|
use crate::npm::CliNpmCache;
|
||||||
use crate::npm::CliNpmTarballCache;
|
|
||||||
use crate::sys::CliSys;
|
use crate::sys::CliSys;
|
||||||
use crate::util::progress_bar::ProgressBar;
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
pub fn create_npm_fs_resolver(
|
pub fn create_npm_fs_resolver(
|
||||||
npm_cache: Arc<CliNpmCache>,
|
npm_cache: Arc<CliNpmCache>,
|
||||||
npm_install_deps_provider: &Arc<NpmInstallDepsProvider>,
|
|
||||||
progress_bar: &ProgressBar,
|
|
||||||
resolution: Arc<NpmResolution>,
|
resolution: Arc<NpmResolution>,
|
||||||
sys: CliSys,
|
sys: CliSys,
|
||||||
tarball_cache: Arc<CliNpmTarballCache>,
|
|
||||||
maybe_node_modules_path: Option<PathBuf>,
|
maybe_node_modules_path: Option<PathBuf>,
|
||||||
system_info: NpmSystemInfo,
|
|
||||||
lifecycle_scripts: LifecycleScriptsConfig,
|
|
||||||
) -> Arc<dyn NpmPackageFsResolver> {
|
) -> Arc<dyn NpmPackageFsResolver> {
|
||||||
match maybe_node_modules_path {
|
match maybe_node_modules_path {
|
||||||
Some(node_modules_folder) => Arc::new(LocalNpmPackageResolver::new(
|
Some(node_modules_folder) => Arc::new(LocalNpmPackageResolver::new(
|
||||||
npm_cache,
|
|
||||||
npm_install_deps_provider.clone(),
|
|
||||||
progress_bar.clone(),
|
|
||||||
resolution,
|
resolution,
|
||||||
sys,
|
sys,
|
||||||
tarball_cache,
|
|
||||||
node_modules_folder,
|
node_modules_folder,
|
||||||
system_info,
|
|
||||||
lifecycle_scripts,
|
|
||||||
)),
|
|
||||||
None => Arc::new(GlobalNpmPackageResolver::new(
|
|
||||||
npm_cache,
|
|
||||||
tarball_cache,
|
|
||||||
resolution,
|
|
||||||
system_info,
|
|
||||||
lifecycle_scripts,
|
|
||||||
)),
|
)),
|
||||||
|
None => Arc::new(GlobalNpmPackageResolver::new(npm_cache, resolution)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
// Copyright 2018-2025 the Deno authors. MIT license.
|
// Copyright 2018-2025 the Deno authors. MIT license.
|
||||||
|
|
||||||
mod async_flag;
|
mod async_flag;
|
||||||
mod sync_read_async_write_lock;
|
|
||||||
mod task_queue;
|
mod task_queue;
|
||||||
|
|
||||||
pub use async_flag::AsyncFlag;
|
pub use async_flag::AsyncFlag;
|
||||||
pub use deno_core::unsync::sync::AtomicFlag;
|
pub use deno_core::unsync::sync::AtomicFlag;
|
||||||
pub use sync_read_async_write_lock::SyncReadAsyncWriteLock;
|
|
||||||
pub use task_queue::TaskQueue;
|
pub use task_queue::TaskQueue;
|
||||||
pub use task_queue::TaskQueuePermit;
|
pub use task_queue::TaskQueuePermit;
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
// Copyright 2018-2025 the Deno authors. MIT license.
|
|
||||||
|
|
||||||
use deno_core::parking_lot::RwLock;
|
|
||||||
use deno_core::parking_lot::RwLockReadGuard;
|
|
||||||
use deno_core::parking_lot::RwLockWriteGuard;
|
|
||||||
|
|
||||||
use super::TaskQueue;
|
|
||||||
use super::TaskQueuePermit;
|
|
||||||
|
|
||||||
/// A lock that can be read synchronously at any time (including when
|
|
||||||
/// being written to), but must write asynchronously.
|
|
||||||
pub struct SyncReadAsyncWriteLockWriteGuard<'a, T: Send + Sync> {
|
|
||||||
_update_permit: TaskQueuePermit<'a>,
|
|
||||||
data: &'a RwLock<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T: Send + Sync> SyncReadAsyncWriteLockWriteGuard<'a, T> {
|
|
||||||
pub fn read(&self) -> RwLockReadGuard<'_, T> {
|
|
||||||
self.data.read()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Warning: Only `write()` with data you created within this
|
|
||||||
/// write this `SyncReadAsyncWriteLockWriteGuard`.
|
|
||||||
///
|
|
||||||
/// ```rs
|
|
||||||
/// let mut data = lock.write().await;
|
|
||||||
///
|
|
||||||
/// let mut data = data.read().clone();
|
|
||||||
/// data.value = 2;
|
|
||||||
/// *data.write() = data;
|
|
||||||
/// ```
|
|
||||||
pub fn write(&self) -> RwLockWriteGuard<'_, T> {
|
|
||||||
self.data.write()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A lock that can only be
|
|
||||||
pub struct SyncReadAsyncWriteLock<T: Send + Sync> {
|
|
||||||
data: RwLock<T>,
|
|
||||||
update_queue: TaskQueue,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Send + Sync> SyncReadAsyncWriteLock<T> {
|
|
||||||
pub fn new(data: T) -> Self {
|
|
||||||
Self {
|
|
||||||
data: RwLock::new(data),
|
|
||||||
update_queue: TaskQueue::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read(&self) -> RwLockReadGuard<'_, T> {
|
|
||||||
self.data.read()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn acquire(&self) -> SyncReadAsyncWriteLockWriteGuard<'_, T> {
|
|
||||||
let update_permit = self.update_queue.acquire().await;
|
|
||||||
SyncReadAsyncWriteLockWriteGuard {
|
|
||||||
_update_permit: update_permit,
|
|
||||||
data: &self.data,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue