summaryrefslogtreecommitdiff
path: root/cli/npm/managed/installer.rs
diff options
context:
space:
mode:
Diffstat (limited to 'cli/npm/managed/installer.rs')
-rw-r--r--cli/npm/managed/installer.rs122
1 files changed, 122 insertions, 0 deletions
diff --git a/cli/npm/managed/installer.rs b/cli/npm/managed/installer.rs
new file mode 100644
index 000000000..21285c3d7
--- /dev/null
+++ b/cli/npm/managed/installer.rs
@@ -0,0 +1,122 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+
+use std::future::Future;
+use std::sync::Arc;
+
+use deno_core::error::AnyError;
+use deno_core::futures::stream::FuturesOrdered;
+use deno_core::futures::StreamExt;
+use deno_npm::registry::NpmRegistryApi;
+use deno_npm::registry::NpmRegistryPackageInfoLoadError;
+use deno_semver::package::PackageReq;
+
+use crate::args::PackageJsonDepsProvider;
+use crate::util::sync::AtomicFlag;
+
+use super::super::CliNpmRegistryApi;
+use super::NpmResolution;
+
+#[derive(Debug)]
+struct PackageJsonDepsInstallerInner {
+ deps_provider: Arc<PackageJsonDepsProvider>,
+ has_installed_flag: AtomicFlag,
+ npm_registry_api: Arc<CliNpmRegistryApi>,
+ npm_resolution: Arc<NpmResolution>,
+}
+
+impl PackageJsonDepsInstallerInner {
+ pub fn reqs_with_info_futures<'a>(
+ &self,
+ reqs: &'a [&'a PackageReq],
+ ) -> FuturesOrdered<
+ impl Future<
+ Output = Result<
+ (&'a PackageReq, Arc<deno_npm::registry::NpmPackageInfo>),
+ NpmRegistryPackageInfoLoadError,
+ >,
+ >,
+ > {
+ FuturesOrdered::from_iter(reqs.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.
+#[derive(Debug, Default)]
+pub struct PackageJsonDepsInstaller(Option<PackageJsonDepsInstallerInner>);
+
+impl PackageJsonDepsInstaller {
+ pub fn new(
+ deps_provider: Arc<PackageJsonDepsProvider>,
+ npm_registry_api: Arc<CliNpmRegistryApi>,
+ npm_resolution: Arc<NpmResolution>,
+ ) -> Self {
+ Self(Some(PackageJsonDepsInstallerInner {
+ deps_provider,
+ has_installed_flag: Default::default(),
+ npm_registry_api,
+ npm_resolution,
+ }))
+ }
+
+ /// Creates an installer that never installs local packages during
+ /// resolution. A top level install will be a no-op.
+ pub fn no_op() -> Self {
+ Self(None)
+ }
+
+ /// Installs the top level dependencies in the package.json file
+ /// without going through and resolving the descendant dependencies yet.
+ pub async fn ensure_top_level_install(&self) -> Result<(), AnyError> {
+ let inner = match &self.0 {
+ Some(inner) => inner,
+ None => return Ok(()),
+ };
+
+ if !inner.has_installed_flag.raise() {
+ return Ok(()); // already installed by something else
+ }
+
+ let package_reqs = inner.deps_provider.reqs();
+
+ // check if something needs resolving before bothering to load all
+ // the package information (which is slow)
+ if package_reqs.iter().all(|req| {
+ inner
+ .npm_resolution
+ .resolve_pkg_id_from_pkg_req(req)
+ .is_ok()
+ }) {
+ log::debug!(
+ "All package.json deps resolvable. Skipping top level install."
+ );
+ return Ok(()); // everything is already resolvable
+ }
+
+ let mut reqs_with_info_futures =
+ inner.reqs_with_info_futures(&package_reqs);
+
+ while let Some(result) = reqs_with_info_futures.next().await {
+ let (req, info) = result?;
+ let result = inner
+ .npm_resolution
+ .resolve_pkg_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
+ reqs_with_info_futures = inner.reqs_with_info_futures(&package_reqs);
+ } else {
+ return Err(err.into());
+ }
+ }
+ }
+
+ Ok(())
+ }
+}