summaryrefslogtreecommitdiff
path: root/cli/npm/installer.rs
blob: adb4344ee48a2c6e1385d47b8632aaa02c50fbe9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
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::npm::NpmPackageReq;

use crate::args::PackageJsonDepsProvider;
use crate::util::sync::AtomicFlag;

use 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 NpmPackageReq],
  ) -> FuturesOrdered<
    impl Future<
      Output = Result<
        (&'a NpmPackageReq, 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_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
          reqs_with_info_futures = inner.reqs_with_info_futures(&package_reqs);
        } else {
          return Err(err.into());
        }
      }
    }

    Ok(())
  }
}