summaryrefslogtreecommitdiff
path: root/cli/npm/managed
diff options
context:
space:
mode:
authorhaturau <135221985+haturatu@users.noreply.github.com>2024-11-20 01:20:47 +0900
committerGitHub <noreply@github.com>2024-11-20 01:20:47 +0900
commit85719a67e59c7aa45bead26e4942d7df8b1b42d4 (patch)
treeface0aecaac53e93ce2f23b53c48859bcf1a36ec /cli/npm/managed
parent67697bc2e4a62a9670699fd18ad0dd8efc5bd955 (diff)
parent186b52731c6bb326c4d32905c5e732d082e83465 (diff)
Merge branch 'denoland:main' into main
Diffstat (limited to 'cli/npm/managed')
-rw-r--r--cli/npm/managed/cache/mod.rs14
-rw-r--r--cli/npm/managed/cache/registry_info.rs66
-rw-r--r--cli/npm/managed/cache/tarball.rs2
-rw-r--r--cli/npm/managed/mod.rs142
-rw-r--r--cli/npm/managed/resolvers/common.rs6
-rw-r--r--cli/npm/managed/resolvers/common/bin_entries.rs139
-rw-r--r--cli/npm/managed/resolvers/common/lifecycle_scripts.rs57
-rw-r--r--cli/npm/managed/resolvers/global.rs7
-rw-r--r--cli/npm/managed/resolvers/local.rs49
9 files changed, 328 insertions, 154 deletions
diff --git a/cli/npm/managed/cache/mod.rs b/cli/npm/managed/cache/mod.rs
index fa0e8c8a5..8ae99f41e 100644
--- a/cli/npm/managed/cache/mod.rs
+++ b/cli/npm/managed/cache/mod.rs
@@ -26,7 +26,7 @@ use crate::cache::CACHE_PERM;
use crate::util::fs::atomic_write_file_with_retries;
use crate::util::fs::hard_link_dir_recursive;
-mod registry_info;
+pub mod registry_info;
mod tarball;
mod tarball_extract;
@@ -36,7 +36,7 @@ pub use tarball::TarballCache;
/// Stores a single copy of npm packages in a cache.
#[derive(Debug)]
pub struct NpmCache {
- cache_dir: NpmCacheDir,
+ cache_dir: Arc<NpmCacheDir>,
cache_setting: CacheSetting,
npmrc: Arc<ResolvedNpmRc>,
/// ensures a package is only downloaded once per run
@@ -45,7 +45,7 @@ pub struct NpmCache {
impl NpmCache {
pub fn new(
- cache_dir: NpmCacheDir,
+ cache_dir: Arc<NpmCacheDir>,
cache_setting: CacheSetting,
npmrc: Arc<ResolvedNpmRc>,
) -> Self {
@@ -61,6 +61,10 @@ impl NpmCache {
&self.cache_setting
}
+ pub fn root_dir_path(&self) -> &Path {
+ self.cache_dir.root_dir()
+ }
+
pub fn root_dir_url(&self) -> &Url {
self.cache_dir.root_dir_url()
}
@@ -152,10 +156,6 @@ impl NpmCache {
self.cache_dir.package_name_folder(name, registry_url)
}
- pub fn root_folder(&self) -> PathBuf {
- self.cache_dir.root_dir().to_owned()
- }
-
pub fn resolve_package_folder_id_from_specifier(
&self,
specifier: &ModuleSpecifier,
diff --git a/cli/npm/managed/cache/registry_info.rs b/cli/npm/managed/cache/registry_info.rs
index 28b19373e..6d39d3c13 100644
--- a/cli/npm/managed/cache/registry_info.rs
+++ b/cli/npm/managed/cache/registry_info.rs
@@ -84,7 +84,7 @@ impl RegistryInfoDownloader {
self.load_package_info_inner(name).await.with_context(|| {
format!(
"Error getting response at {} for package \"{}\"",
- self.get_package_url(name),
+ get_package_url(&self.npmrc, name),
name
)
})
@@ -190,7 +190,7 @@ impl RegistryInfoDownloader {
fn create_load_future(self: &Arc<Self>, name: &str) -> LoadFuture {
let downloader = self.clone();
- let package_url = self.get_package_url(name);
+ let package_url = get_package_url(&self.npmrc, name);
let registry_config = self.npmrc.get_registry_config(name);
let maybe_auth_header =
match maybe_auth_header_for_npm_registry(registry_config) {
@@ -202,10 +202,13 @@ impl RegistryInfoDownloader {
let guard = self.progress_bar.update(package_url.as_str());
let name = name.to_string();
async move {
- let maybe_bytes = downloader
- .http_client_provider
- .get_or_create()?
- .download_with_progress(package_url, maybe_auth_header, &guard)
+ let client = downloader.http_client_provider.get_or_create()?;
+ let maybe_bytes = client
+ .download_with_progress_and_retries(
+ package_url,
+ maybe_auth_header,
+ &guard,
+ )
.await?;
match maybe_bytes {
Some(bytes) => {
@@ -236,25 +239,36 @@ impl RegistryInfoDownloader {
.map(|r| r.map_err(Arc::new))
.boxed_local()
}
+}
- fn get_package_url(&self, name: &str) -> Url {
- let registry_url = self.npmrc.get_registry_url(name);
- // list of all characters used in npm packages:
- // !, ', (, ), *, -, ., /, [0-9], @, [A-Za-z], _, ~
- const ASCII_SET: percent_encoding::AsciiSet =
- percent_encoding::NON_ALPHANUMERIC
- .remove(b'!')
- .remove(b'\'')
- .remove(b'(')
- .remove(b')')
- .remove(b'*')
- .remove(b'-')
- .remove(b'.')
- .remove(b'/')
- .remove(b'@')
- .remove(b'_')
- .remove(b'~');
- let name = percent_encoding::utf8_percent_encode(name, &ASCII_SET);
- registry_url.join(&name.to_string()).unwrap()
- }
+pub fn get_package_url(npmrc: &ResolvedNpmRc, name: &str) -> Url {
+ let registry_url = npmrc.get_registry_url(name);
+ // The '/' character in scoped package names "@scope/name" must be
+ // encoded for older third party registries. Newer registries and
+ // npm itself support both ways
+ // - encoded: https://registry.npmjs.org/@rollup%2fplugin-json
+ // - non-ecoded: https://registry.npmjs.org/@rollup/plugin-json
+ // To support as many third party registries as possible we'll
+ // always encode the '/' character.
+
+ // list of all characters used in npm packages:
+ // !, ', (, ), *, -, ., /, [0-9], @, [A-Za-z], _, ~
+ const ASCII_SET: percent_encoding::AsciiSet =
+ percent_encoding::NON_ALPHANUMERIC
+ .remove(b'!')
+ .remove(b'\'')
+ .remove(b'(')
+ .remove(b')')
+ .remove(b'*')
+ .remove(b'-')
+ .remove(b'.')
+ .remove(b'@')
+ .remove(b'_')
+ .remove(b'~');
+ let name = percent_encoding::utf8_percent_encode(name, &ASCII_SET);
+ registry_url
+ // Ensure that scoped package name percent encoding is lower cased
+ // to match npm.
+ .join(&name.to_string().replace("%2F", "%2f"))
+ .unwrap()
}
diff --git a/cli/npm/managed/cache/tarball.rs b/cli/npm/managed/cache/tarball.rs
index 4bcee38ea..7cf88d6d6 100644
--- a/cli/npm/managed/cache/tarball.rs
+++ b/cli/npm/managed/cache/tarball.rs
@@ -172,7 +172,7 @@ impl TarballCache {
let guard = tarball_cache.progress_bar.update(&dist.tarball);
let result = tarball_cache.http_client_provider
.get_or_create()?
- .download_with_progress(tarball_uri, maybe_auth_header, &guard)
+ .download_with_progress_and_retries(tarball_uri, maybe_auth_header, &guard)
.await;
let maybe_bytes = match result {
Ok(maybe_bytes) => maybe_bytes,
diff --git a/cli/npm/managed/mod.rs b/cli/npm/managed/mod.rs
index ec50a9c65..2e64f5f18 100644
--- a/cli/npm/managed/mod.rs
+++ b/cli/npm/managed/mod.rs
@@ -12,6 +12,7 @@ use deno_cache_dir::npm::NpmCacheDir;
use deno_core::anyhow::Context;
use deno_core::error::AnyError;
use deno_core::serde_json;
+use deno_core::url::Url;
use deno_npm::npm_rc::ResolvedNpmRc;
use deno_npm::registry::NpmPackageInfo;
use deno_npm::registry::NpmRegistryApi;
@@ -21,16 +22,17 @@ use deno_npm::resolution::ValidSerializedNpmResolutionSnapshot;
use deno_npm::NpmPackageId;
use deno_npm::NpmResolutionPackage;
use deno_npm::NpmSystemInfo;
+use deno_resolver::npm::CliNpmReqResolver;
use deno_runtime::colors;
use deno_runtime::deno_fs::FileSystem;
use deno_runtime::deno_node::NodePermissions;
-use deno_runtime::deno_node::NodeRequireResolver;
use deno_runtime::ops::process::NpmProcessStateProvider;
use deno_semver::package::PackageNv;
use deno_semver::package::PackageReq;
use node_resolver::errors::PackageFolderResolveError;
use node_resolver::errors::PackageFolderResolveIoError;
-use node_resolver::NpmResolver;
+use node_resolver::InNpmPackageChecker;
+use node_resolver::NpmPackageFolderResolver;
use resolution::AddPkgReqsResult;
use crate::args::CliLockfile;
@@ -38,7 +40,7 @@ use crate::args::LifecycleScriptsConfig;
use crate::args::NpmInstallDepsProvider;
use crate::args::NpmProcessState;
use crate::args::NpmProcessStateKind;
-use crate::cache::DenoCacheEnvFsAdapter;
+use crate::args::PackageJsonDepValueParseWithLocationError;
use crate::cache::FastInsecureHasher;
use crate::http_util::HttpClientProvider;
use crate::util::fs::canonicalize_path_maybe_not_exists_with_fs;
@@ -55,7 +57,7 @@ use super::CliNpmResolver;
use super::InnerCliNpmResolverRef;
use super::ResolvePkgFolderFromDenoReqError;
-mod cache;
+pub mod cache;
mod registry;
mod resolution;
mod resolvers;
@@ -65,12 +67,12 @@ pub enum CliNpmResolverManagedSnapshotOption {
Specified(Option<ValidSerializedNpmResolutionSnapshot>),
}
-pub struct CliNpmResolverManagedCreateOptions {
+pub struct CliManagedNpmResolverCreateOptions {
pub snapshot: CliNpmResolverManagedSnapshotOption,
pub maybe_lockfile: Option<Arc<CliLockfile>>,
pub fs: Arc<dyn deno_runtime::deno_fs::FileSystem>,
pub http_client_provider: Arc<crate::http_util::HttpClientProvider>,
- pub npm_global_cache_dir: PathBuf,
+ pub npm_cache_dir: Arc<NpmCacheDir>,
pub cache_setting: crate::args::CacheSetting,
pub text_only_progress_bar: crate::util::progress_bar::ProgressBar,
pub maybe_node_modules_path: Option<PathBuf>,
@@ -81,7 +83,7 @@ pub struct CliNpmResolverManagedCreateOptions {
}
pub async fn create_managed_npm_resolver_for_lsp(
- options: CliNpmResolverManagedCreateOptions,
+ options: CliManagedNpmResolverCreateOptions,
) -> Arc<dyn CliNpmResolver> {
let npm_cache = create_cache(&options);
let npm_api = create_api(&options, npm_cache.clone());
@@ -114,7 +116,7 @@ pub async fn create_managed_npm_resolver_for_lsp(
}
pub async fn create_managed_npm_resolver(
- options: CliNpmResolverManagedCreateOptions,
+ options: CliManagedNpmResolverCreateOptions,
) -> Result<Arc<dyn CliNpmResolver>, AnyError> {
let npm_cache = create_cache(&options);
let npm_api = create_api(&options, npm_cache.clone());
@@ -188,20 +190,16 @@ fn create_inner(
))
}
-fn create_cache(options: &CliNpmResolverManagedCreateOptions) -> Arc<NpmCache> {
+fn create_cache(options: &CliManagedNpmResolverCreateOptions) -> Arc<NpmCache> {
Arc::new(NpmCache::new(
- NpmCacheDir::new(
- &DenoCacheEnvFsAdapter(options.fs.as_ref()),
- options.npm_global_cache_dir.clone(),
- options.npmrc.get_all_known_registries_urls(),
- ),
+ options.npm_cache_dir.clone(),
options.cache_setting.clone(),
options.npmrc.clone(),
))
}
fn create_api(
- options: &CliNpmResolverManagedCreateOptions,
+ options: &CliManagedNpmResolverCreateOptions,
npm_cache: Arc<NpmCache>,
) -> Arc<CliNpmRegistryApi> {
Arc::new(CliNpmRegistryApi::new(
@@ -258,6 +256,35 @@ async fn snapshot_from_lockfile(
Ok(snapshot)
}
+#[derive(Debug)]
+struct ManagedInNpmPackageChecker {
+ root_dir: Url,
+}
+
+impl InNpmPackageChecker for ManagedInNpmPackageChecker {
+ fn in_npm_package(&self, specifier: &Url) -> bool {
+ specifier.as_ref().starts_with(self.root_dir.as_str())
+ }
+}
+
+pub struct CliManagedInNpmPkgCheckerCreateOptions<'a> {
+ pub root_cache_dir_url: &'a Url,
+ pub maybe_node_modules_path: Option<&'a Path>,
+}
+
+pub fn create_managed_in_npm_pkg_checker(
+ options: CliManagedInNpmPkgCheckerCreateOptions,
+) -> Arc<dyn InNpmPackageChecker> {
+ let root_dir = match options.maybe_node_modules_path {
+ Some(node_modules_folder) => {
+ deno_path_util::url_from_directory_path(node_modules_folder).unwrap()
+ }
+ None => options.root_cache_dir_url.clone(),
+ };
+ debug_assert!(root_dir.as_str().ends_with('/'));
+ Arc::new(ManagedInNpmPackageChecker { root_dir })
+}
+
/// An npm resolver where the resolution is managed by Deno rather than
/// the user bringing their own node_modules (BYONM) on the file system.
pub struct ManagedCliNpmResolver {
@@ -480,19 +507,24 @@ impl ManagedCliNpmResolver {
self.resolution.resolve_pkg_id_from_pkg_req(req)
}
- pub fn ensure_no_pkg_json_dep_errors(&self) -> Result<(), AnyError> {
+ pub fn ensure_no_pkg_json_dep_errors(
+ &self,
+ ) -> Result<(), Box<PackageJsonDepValueParseWithLocationError>> {
for err in self.npm_install_deps_provider.pkg_json_dep_errors() {
- match err {
+ match &err.source {
deno_package_json::PackageJsonDepValueParseError::VersionReq(_) => {
- return Err(
- AnyError::from(err.clone())
- .context("Failed to install from package.json"),
- );
+ return Err(Box::new(err.clone()));
}
deno_package_json::PackageJsonDepValueParseError::Unsupported {
..
} => {
- log::warn!("{} {} in package.json", colors::yellow("Warning"), err)
+ // only warn for this one
+ log::warn!(
+ "{} {}\n at {}",
+ colors::yellow("Warning"),
+ err.source,
+ err.location,
+ )
}
}
}
@@ -549,8 +581,16 @@ impl ManagedCliNpmResolver {
.map_err(|err| err.into())
}
- pub fn global_cache_root_folder(&self) -> PathBuf {
- self.npm_cache.root_folder()
+ pub fn maybe_node_modules_path(&self) -> Option<&Path> {
+ self.fs_resolver.node_modules_path()
+ }
+
+ pub fn global_cache_root_path(&self) -> &Path {
+ self.npm_cache.root_dir_path()
+ }
+
+ pub fn global_cache_root_url(&self) -> &Url {
+ self.npm_cache.root_dir_url()
}
}
@@ -566,7 +606,7 @@ fn npm_process_state(
.unwrap()
}
-impl NpmResolver for ManagedCliNpmResolver {
+impl NpmPackageFolderResolver for ManagedCliNpmResolver {
fn resolve_package_folder_from_package(
&self,
name: &str,
@@ -585,22 +625,6 @@ impl NpmResolver for ManagedCliNpmResolver {
log::debug!("Resolved {} from {} to {}", name, referrer, path.display());
Ok(path)
}
-
- fn in_npm_package(&self, specifier: &ModuleSpecifier) -> bool {
- let root_dir_url = self.fs_resolver.root_dir_url();
- debug_assert!(root_dir_url.as_str().ends_with('/'));
- specifier.as_ref().starts_with(root_dir_url.as_str())
- }
-}
-
-impl NodeRequireResolver for ManagedCliNpmResolver {
- fn ensure_read_permission<'a>(
- &self,
- permissions: &mut dyn NodePermissions,
- path: &'a Path,
- ) -> Result<Cow<'a, Path>, AnyError> {
- self.fs_resolver.ensure_read_permission(permissions, path)
- }
}
impl NpmProcessStateProvider for ManagedCliNpmResolver {
@@ -612,12 +636,29 @@ impl NpmProcessStateProvider for ManagedCliNpmResolver {
}
}
+impl CliNpmReqResolver for ManagedCliNpmResolver {
+ fn resolve_pkg_folder_from_deno_module_req(
+ &self,
+ req: &PackageReq,
+ _referrer: &ModuleSpecifier,
+ ) -> Result<PathBuf, ResolvePkgFolderFromDenoReqError> {
+ let pkg_id = self
+ .resolve_pkg_id_from_pkg_req(req)
+ .map_err(|err| ResolvePkgFolderFromDenoReqError::Managed(err.into()))?;
+ self
+ .resolve_pkg_folder_from_pkg_id(&pkg_id)
+ .map_err(ResolvePkgFolderFromDenoReqError::Managed)
+ }
+}
+
impl CliNpmResolver for ManagedCliNpmResolver {
- fn into_npm_resolver(self: Arc<Self>) -> Arc<dyn NpmResolver> {
+ fn into_npm_pkg_folder_resolver(
+ self: Arc<Self>,
+ ) -> Arc<dyn NpmPackageFolderResolver> {
self
}
- fn into_require_resolver(self: Arc<Self>) -> Arc<dyn NodeRequireResolver> {
+ fn into_npm_req_resolver(self: Arc<Self>) -> Arc<dyn CliNpmReqResolver> {
self
}
@@ -668,17 +709,12 @@ impl CliNpmResolver for ManagedCliNpmResolver {
self.fs_resolver.node_modules_path()
}
- fn resolve_pkg_folder_from_deno_module_req(
+ fn ensure_read_permission<'a>(
&self,
- req: &PackageReq,
- _referrer: &ModuleSpecifier,
- ) -> Result<PathBuf, ResolvePkgFolderFromDenoReqError> {
- let pkg_id = self
- .resolve_pkg_id_from_pkg_req(req)
- .map_err(|err| ResolvePkgFolderFromDenoReqError::Managed(err.into()))?;
- self
- .resolve_pkg_folder_from_pkg_id(&pkg_id)
- .map_err(ResolvePkgFolderFromDenoReqError::Managed)
+ permissions: &mut dyn NodePermissions,
+ path: &'a Path,
+ ) -> Result<Cow<'a, Path>, AnyError> {
+ self.fs_resolver.ensure_read_permission(permissions, path)
}
fn check_state_hash(&self) -> Option<u64> {
diff --git a/cli/npm/managed/resolvers/common.rs b/cli/npm/managed/resolvers/common.rs
index 867bb4168..eee11c760 100644
--- a/cli/npm/managed/resolvers/common.rs
+++ b/cli/npm/managed/resolvers/common.rs
@@ -17,7 +17,6 @@ use deno_core::anyhow::Context;
use deno_core::error::AnyError;
use deno_core::futures;
use deno_core::futures::StreamExt;
-use deno_core::url::Url;
use deno_npm::NpmPackageCacheFolderId;
use deno_npm::NpmPackageId;
use deno_npm::NpmResolutionPackage;
@@ -30,9 +29,6 @@ use crate::npm::managed::cache::TarballCache;
/// Part of the resolution that interacts with the file system.
#[async_trait(?Send)]
pub trait NpmPackageFsResolver: Send + Sync {
- /// Specifier for the root directory.
- fn root_dir_url(&self) -> &Url;
-
/// The local node_modules folder if it is applicable to the implementation.
fn node_modules_path(&self) -> Option<&Path>;
@@ -137,7 +133,7 @@ impl RegistryReadPermissionChecker {
}
}
- permissions.check_read_path(path)
+ permissions.check_read_path(path).map_err(Into::into)
}
}
diff --git a/cli/npm/managed/resolvers/common/bin_entries.rs b/cli/npm/managed/resolvers/common/bin_entries.rs
index 4524ce832..e4a184568 100644
--- a/cli/npm/managed/resolvers/common/bin_entries.rs
+++ b/cli/npm/managed/resolvers/common/bin_entries.rs
@@ -18,6 +18,7 @@ pub struct BinEntries<'a> {
seen_names: HashMap<&'a str, &'a NpmPackageId>,
/// The bin entries
entries: Vec<(&'a NpmResolutionPackage, PathBuf)>,
+ sorted: bool,
}
/// Returns the name of the default binary for the given package.
@@ -31,6 +32,20 @@ fn default_bin_name(package: &NpmResolutionPackage) -> &str {
.map_or(package.id.nv.name.as_str(), |(_, name)| name)
}
+pub fn warn_missing_entrypoint(
+ bin_name: &str,
+ package_path: &Path,
+ entrypoint: &Path,
+) {
+ log::warn!(
+ "{} Trying to set up '{}' bin for \"{}\", but the entry point \"{}\" doesn't exist.",
+ deno_terminal::colors::yellow("Warning"),
+ bin_name,
+ package_path.display(),
+ entrypoint.display()
+ );
+}
+
impl<'a> BinEntries<'a> {
pub fn new() -> Self {
Self::default()
@@ -42,6 +57,7 @@ impl<'a> BinEntries<'a> {
package: &'a NpmResolutionPackage,
package_path: PathBuf,
) {
+ self.sorted = false;
// check for a new collision, if we haven't already
// found one
match package.bin.as_ref().unwrap() {
@@ -79,16 +95,21 @@ impl<'a> BinEntries<'a> {
&str, // bin name
&str, // bin script
) -> Result<(), AnyError>,
+ mut filter: impl FnMut(&NpmResolutionPackage) -> bool,
) -> Result<(), AnyError> {
- if !self.collisions.is_empty() {
+ if !self.collisions.is_empty() && !self.sorted {
// walking the dependency tree to find out the depth of each package
// is sort of expensive, so we only do it if there's a collision
sort_by_depth(snapshot, &mut self.entries, &mut self.collisions);
+ self.sorted = true;
}
let mut seen = HashSet::new();
for (package, package_path) in &self.entries {
+ if !filter(package) {
+ continue;
+ }
if let Some(bin_entries) = &package.bin {
match bin_entries {
deno_npm::registry::NpmPackageVersionBinEntry::String(script) => {
@@ -118,8 +139,8 @@ impl<'a> BinEntries<'a> {
}
/// Collect the bin entries into a vec of (name, script path)
- pub fn into_bin_files(
- mut self,
+ pub fn collect_bin_files(
+ &mut self,
snapshot: &NpmResolutionSnapshot,
) -> Vec<(String, PathBuf)> {
let mut bins = Vec::new();
@@ -131,17 +152,18 @@ impl<'a> BinEntries<'a> {
bins.push((name.to_string(), package_path.join(script)));
Ok(())
},
+ |_| true,
)
.unwrap();
bins
}
- /// Finish setting up the bin entries, writing the necessary files
- /// to disk.
- pub fn finish(
+ fn set_up_entries_filtered(
mut self,
snapshot: &NpmResolutionSnapshot,
bin_node_modules_dir_path: &Path,
+ filter: impl FnMut(&NpmResolutionPackage) -> bool,
+ mut handler: impl FnMut(&EntrySetupOutcome<'_>),
) -> Result<(), AnyError> {
if !self.entries.is_empty() && !bin_node_modules_dir_path.exists() {
std::fs::create_dir_all(bin_node_modules_dir_path).with_context(
@@ -160,18 +182,54 @@ impl<'a> BinEntries<'a> {
Ok(())
},
|package, package_path, name, script| {
- set_up_bin_entry(
+ let outcome = set_up_bin_entry(
package,
name,
script,
package_path,
bin_node_modules_dir_path,
- )
+ )?;
+ handler(&outcome);
+ Ok(())
},
+ filter,
)?;
Ok(())
}
+
+ /// Finish setting up the bin entries, writing the necessary files
+ /// to disk.
+ pub fn finish(
+ self,
+ snapshot: &NpmResolutionSnapshot,
+ bin_node_modules_dir_path: &Path,
+ handler: impl FnMut(&EntrySetupOutcome<'_>),
+ ) -> Result<(), AnyError> {
+ self.set_up_entries_filtered(
+ snapshot,
+ bin_node_modules_dir_path,
+ |_| true,
+ handler,
+ )
+ }
+
+ /// Finish setting up the bin entries, writing the necessary files
+ /// to disk.
+ pub fn finish_only(
+ self,
+ snapshot: &NpmResolutionSnapshot,
+ bin_node_modules_dir_path: &Path,
+ handler: impl FnMut(&EntrySetupOutcome<'_>),
+ only: &HashSet<&NpmPackageId>,
+ ) -> Result<(), AnyError> {
+ self.set_up_entries_filtered(
+ snapshot,
+ bin_node_modules_dir_path,
+ |package| only.contains(&package.id),
+ handler,
+ )
+ }
}
// walk the dependency tree to find out the depth of each package
@@ -233,16 +291,17 @@ fn sort_by_depth(
});
}
-pub fn set_up_bin_entry(
- package: &NpmResolutionPackage,
- bin_name: &str,
+pub fn set_up_bin_entry<'a>(
+ package: &'a NpmResolutionPackage,
+ bin_name: &'a str,
#[allow(unused_variables)] bin_script: &str,
- #[allow(unused_variables)] package_path: &Path,
+ #[allow(unused_variables)] package_path: &'a Path,
bin_node_modules_dir_path: &Path,
-) -> Result<(), AnyError> {
+) -> Result<EntrySetupOutcome<'a>, AnyError> {
#[cfg(windows)]
{
set_up_bin_shim(package, bin_name, bin_node_modules_dir_path)?;
+ Ok(EntrySetupOutcome::Success)
}
#[cfg(unix)]
{
@@ -252,9 +311,8 @@ pub fn set_up_bin_entry(
bin_script,
package_path,
bin_node_modules_dir_path,
- )?;
+ )
}
- Ok(())
}
#[cfg(windows)]
@@ -301,14 +359,39 @@ fn make_executable_if_exists(path: &Path) -> Result<bool, AnyError> {
Ok(true)
}
+pub enum EntrySetupOutcome<'a> {
+ #[cfg_attr(windows, allow(dead_code))]
+ MissingEntrypoint {
+ bin_name: &'a str,
+ package_path: &'a Path,
+ entrypoint: PathBuf,
+ package: &'a NpmResolutionPackage,
+ },
+ Success,
+}
+
+impl<'a> EntrySetupOutcome<'a> {
+ pub fn warn_if_failed(&self) {
+ match self {
+ EntrySetupOutcome::MissingEntrypoint {
+ bin_name,
+ package_path,
+ entrypoint,
+ ..
+ } => warn_missing_entrypoint(bin_name, package_path, entrypoint),
+ EntrySetupOutcome::Success => {}
+ }
+ }
+}
+
#[cfg(unix)]
-fn symlink_bin_entry(
- _package: &NpmResolutionPackage,
- bin_name: &str,
+fn symlink_bin_entry<'a>(
+ package: &'a NpmResolutionPackage,
+ bin_name: &'a str,
bin_script: &str,
- package_path: &Path,
+ package_path: &'a Path,
bin_node_modules_dir_path: &Path,
-) -> Result<(), AnyError> {
+) -> Result<EntrySetupOutcome<'a>, AnyError> {
use std::io;
use std::os::unix::fs::symlink;
let link = bin_node_modules_dir_path.join(bin_name);
@@ -318,14 +401,12 @@ fn symlink_bin_entry(
format!("Can't set up '{}' bin at {}", bin_name, original.display())
})?;
if !found {
- log::warn!(
- "{} Trying to set up '{}' bin for \"{}\", but the entry point \"{}\" doesn't exist.",
- deno_terminal::colors::yellow("Warning"),
+ return Ok(EntrySetupOutcome::MissingEntrypoint {
bin_name,
- package_path.display(),
- original.display()
- );
- return Ok(());
+ package_path,
+ entrypoint: original,
+ package,
+ });
}
let original_relative =
@@ -348,7 +429,7 @@ fn symlink_bin_entry(
original_relative.display()
)
})?;
- return Ok(());
+ return Ok(EntrySetupOutcome::Success);
}
return Err(err).with_context(|| {
format!(
@@ -359,5 +440,5 @@ fn symlink_bin_entry(
});
}
- Ok(())
+ Ok(EntrySetupOutcome::Success)
}
diff --git a/cli/npm/managed/resolvers/common/lifecycle_scripts.rs b/cli/npm/managed/resolvers/common/lifecycle_scripts.rs
index 5735f5248..5c5755c81 100644
--- a/cli/npm/managed/resolvers/common/lifecycle_scripts.rs
+++ b/cli/npm/managed/resolvers/common/lifecycle_scripts.rs
@@ -10,6 +10,7 @@ use deno_runtime::deno_io::FromRawIoHandle;
use deno_semver::package::PackageNv;
use deno_semver::Version;
use std::borrow::Cow;
+use std::collections::HashSet;
use std::rc::Rc;
use std::path::Path;
@@ -61,7 +62,7 @@ impl<'a> LifecycleScripts<'a> {
}
}
-fn has_lifecycle_scripts(
+pub fn has_lifecycle_scripts(
package: &NpmResolutionPackage,
package_path: &Path,
) -> bool {
@@ -83,7 +84,7 @@ fn is_broken_default_install_script(script: &str, package_path: &Path) -> bool {
}
impl<'a> LifecycleScripts<'a> {
- fn can_run_scripts(&self, package_nv: &PackageNv) -> bool {
+ pub fn can_run_scripts(&self, package_nv: &PackageNv) -> bool {
if !self.strategy.can_run_scripts() {
return false;
}
@@ -98,6 +99,9 @@ impl<'a> LifecycleScripts<'a> {
PackagesAllowedScripts::None => false,
}
}
+ pub fn has_run_scripts(&self, package: &NpmResolutionPackage) -> bool {
+ self.strategy.has_run(package)
+ }
/// Register a package for running lifecycle scripts, if applicable.
///
/// `package_path` is the path containing the package's code (its root dir).
@@ -110,12 +114,12 @@ impl<'a> LifecycleScripts<'a> {
) {
if has_lifecycle_scripts(package, &package_path) {
if self.can_run_scripts(&package.id.nv) {
- if !self.strategy.has_run(package) {
+ if !self.has_run_scripts(package) {
self
.packages_with_scripts
.push((package, package_path.into_owned()));
}
- } else if !self.strategy.has_run(package)
+ } else if !self.has_run_scripts(package)
&& (self.config.explicit_install || !self.strategy.has_warned(package))
{
// Skip adding `esbuild` as it is known that it can work properly without lifecycle script
@@ -149,22 +153,32 @@ impl<'a> LifecycleScripts<'a> {
self,
snapshot: &NpmResolutionSnapshot,
packages: &[NpmResolutionPackage],
- root_node_modules_dir_path: Option<&Path>,
+ root_node_modules_dir_path: &Path,
progress_bar: &ProgressBar,
) -> Result<(), AnyError> {
self.warn_not_run_scripts()?;
let get_package_path =
|p: &NpmResolutionPackage| self.strategy.package_path(p);
let mut failed_packages = Vec::new();
+ let mut bin_entries = BinEntries::new();
if !self.packages_with_scripts.is_empty() {
+ let package_ids = self
+ .packages_with_scripts
+ .iter()
+ .map(|(p, _)| &p.id)
+ .collect::<HashSet<_>>();
// get custom commands for each bin available in the node_modules dir (essentially
// the scripts that are in `node_modules/.bin`)
- let base =
- resolve_baseline_custom_commands(snapshot, packages, get_package_path)?;
+ let base = resolve_baseline_custom_commands(
+ &mut bin_entries,
+ snapshot,
+ packages,
+ get_package_path,
+ )?;
let init_cwd = &self.config.initial_cwd;
let process_state = crate::npm::managed::npm_process_state(
snapshot.as_valid_serialized(),
- root_node_modules_dir_path,
+ Some(root_node_modules_dir_path),
);
let mut env_vars = crate::task_runner::real_env_vars();
@@ -221,7 +235,7 @@ impl<'a> LifecycleScripts<'a> {
custom_commands: custom_commands.clone(),
init_cwd,
argv: &[],
- root_node_modules_dir: root_node_modules_dir_path,
+ root_node_modules_dir: Some(root_node_modules_dir_path),
stdio: Some(crate::task_runner::TaskIo {
stderr: TaskStdio::piped(),
stdout: TaskStdio::piped(),
@@ -262,6 +276,17 @@ impl<'a> LifecycleScripts<'a> {
}
self.strategy.did_run_scripts(package)?;
}
+
+ // re-set up bin entries for the packages which we've run scripts for.
+ // lifecycle scripts can create files that are linked to by bin entries,
+ // and the only reliable way to handle this is to re-link bin entries
+ // (this is what PNPM does as well)
+ bin_entries.finish_only(
+ snapshot,
+ &root_node_modules_dir_path.join(".bin"),
+ |outcome| outcome.warn_if_failed(),
+ &package_ids,
+ )?;
}
if failed_packages.is_empty() {
Ok(())
@@ -281,9 +306,10 @@ impl<'a> LifecycleScripts<'a> {
// take in all (non copy) packages from snapshot,
// and resolve the set of available binaries to create
// custom commands available to the task runner
-fn resolve_baseline_custom_commands(
- snapshot: &NpmResolutionSnapshot,
- packages: &[NpmResolutionPackage],
+fn resolve_baseline_custom_commands<'a>(
+ bin_entries: &mut BinEntries<'a>,
+ snapshot: &'a NpmResolutionSnapshot,
+ packages: &'a [NpmResolutionPackage],
get_package_path: impl Fn(&NpmResolutionPackage) -> PathBuf,
) -> Result<crate::task_runner::TaskCustomCommands, AnyError> {
let mut custom_commands = crate::task_runner::TaskCustomCommands::new();
@@ -306,6 +332,7 @@ fn resolve_baseline_custom_commands(
// doing it for packages that are set up already.
// realistically, scripts won't be run very often so it probably isn't too big of an issue.
resolve_custom_commands_from_packages(
+ bin_entries,
custom_commands,
snapshot,
packages,
@@ -320,12 +347,12 @@ fn resolve_custom_commands_from_packages<
'a,
P: IntoIterator<Item = &'a NpmResolutionPackage>,
>(
+ bin_entries: &mut BinEntries<'a>,
mut commands: crate::task_runner::TaskCustomCommands,
snapshot: &'a NpmResolutionSnapshot,
packages: P,
get_package_path: impl Fn(&'a NpmResolutionPackage) -> PathBuf,
) -> Result<crate::task_runner::TaskCustomCommands, AnyError> {
- let mut bin_entries = BinEntries::new();
for package in packages {
let package_path = get_package_path(package);
@@ -333,7 +360,7 @@ fn resolve_custom_commands_from_packages<
bin_entries.add(package, package_path);
}
}
- let bins = bin_entries.into_bin_files(snapshot);
+ let bins: Vec<(String, PathBuf)> = bin_entries.collect_bin_files(snapshot);
for (bin_name, script_path) in bins {
commands.insert(
bin_name.clone(),
@@ -356,7 +383,9 @@ fn resolve_custom_commands_from_deps(
snapshot: &NpmResolutionSnapshot,
get_package_path: impl Fn(&NpmResolutionPackage) -> PathBuf,
) -> Result<crate::task_runner::TaskCustomCommands, AnyError> {
+ let mut bin_entries = BinEntries::new();
resolve_custom_commands_from_packages(
+ &mut bin_entries,
baseline,
snapshot,
package
diff --git a/cli/npm/managed/resolvers/global.rs b/cli/npm/managed/resolvers/global.rs
index 5be315e99..f0193e78e 100644
--- a/cli/npm/managed/resolvers/global.rs
+++ b/cli/npm/managed/resolvers/global.rs
@@ -11,7 +11,6 @@ use crate::colors;
use async_trait::async_trait;
use deno_ast::ModuleSpecifier;
use deno_core::error::AnyError;
-use deno_core::url::Url;
use deno_npm::NpmPackageCacheFolderId;
use deno_npm::NpmPackageId;
use deno_npm::NpmResolutionPackage;
@@ -56,7 +55,7 @@ impl GlobalNpmPackageResolver {
Self {
registry_read_permission_checker: RegistryReadPermissionChecker::new(
fs,
- cache.root_folder(),
+ cache.root_dir_path().to_path_buf(),
),
cache,
tarball_cache,
@@ -69,10 +68,6 @@ impl GlobalNpmPackageResolver {
#[async_trait(?Send)]
impl NpmPackageFsResolver for GlobalNpmPackageResolver {
- fn root_dir_url(&self) -> &Url {
- self.cache.root_dir_url()
- }
-
fn node_modules_path(&self) -> Option<&Path> {
None
}
diff --git a/cli/npm/managed/resolvers/local.rs b/cli/npm/managed/resolvers/local.rs
index 54f7576ad..50c5bd268 100644
--- a/cli/npm/managed/resolvers/local.rs
+++ b/cli/npm/managed/resolvers/local.rs
@@ -55,6 +55,7 @@ use crate::util::progress_bar::ProgressMessagePrompt;
use super::super::cache::NpmCache;
use super::super::cache::TarballCache;
use super::super::resolution::NpmResolution;
+use super::common::bin_entries;
use super::common::NpmPackageFsResolver;
use super::common::RegistryReadPermissionChecker;
@@ -155,10 +156,6 @@ impl LocalNpmPackageResolver {
#[async_trait(?Send)]
impl NpmPackageFsResolver for LocalNpmPackageResolver {
- fn root_dir_url(&self) -> &Url {
- &self.root_node_modules_url
- }
-
fn node_modules_path(&self) -> Option<&Path> {
Some(self.root_node_modules_path.as_ref())
}
@@ -333,8 +330,7 @@ async fn sync_resolution_with_fs(
let mut cache_futures = FuturesUnordered::new();
let mut newest_packages_by_name: HashMap<&String, &NpmResolutionPackage> =
HashMap::with_capacity(package_partitions.packages.len());
- let bin_entries =
- Rc::new(RefCell::new(super::common::bin_entries::BinEntries::new()));
+ let bin_entries = Rc::new(RefCell::new(bin_entries::BinEntries::new()));
let mut lifecycle_scripts =
super::common::lifecycle_scripts::LifecycleScripts::new(
lifecycle_scripts,
@@ -662,7 +658,28 @@ async fn sync_resolution_with_fs(
// 7. Set up `node_modules/.bin` entries for packages that need it.
{
let bin_entries = std::mem::take(&mut *bin_entries.borrow_mut());
- bin_entries.finish(snapshot, &bin_node_modules_dir_path)?;
+ bin_entries.finish(
+ snapshot,
+ &bin_node_modules_dir_path,
+ |setup_outcome| {
+ match setup_outcome {
+ bin_entries::EntrySetupOutcome::MissingEntrypoint {
+ package,
+ package_path,
+ ..
+ } if super::common::lifecycle_scripts::has_lifecycle_scripts(
+ package,
+ package_path,
+ ) && lifecycle_scripts.can_run_scripts(&package.id.nv)
+ && !lifecycle_scripts.has_run_scripts(package) =>
+ {
+ // ignore, it might get fixed when the lifecycle scripts run.
+ // if not, we'll warn then
+ }
+ outcome => outcome.warn_if_failed(),
+ }
+ },
+ )?;
}
// 8. Create symlinks for the workspace packages
@@ -712,7 +729,7 @@ async fn sync_resolution_with_fs(
.finish(
snapshot,
&package_partitions.packages,
- Some(root_node_modules_dir_path),
+ root_node_modules_dir_path,
progress_bar,
)
.await?;
@@ -1039,12 +1056,18 @@ fn junction_or_symlink_dir(
if symlink_err.kind() == std::io::ErrorKind::PermissionDenied =>
{
USE_JUNCTIONS.store(true, std::sync::atomic::Ordering::Relaxed);
- junction::create(old_path, new_path).map_err(Into::into)
+ junction::create(old_path, new_path)
+ .context("Failed creating junction in node_modules folder")
+ }
+ Err(symlink_err) => {
+ log::warn!(
+ "{} Unexpected error symlinking node_modules: {symlink_err}",
+ colors::yellow("Warning")
+ );
+ USE_JUNCTIONS.store(true, std::sync::atomic::Ordering::Relaxed);
+ junction::create(old_path, new_path)
+ .context("Failed creating junction in node_modules folder")
}
- Err(symlink_err) => Err(
- AnyError::from(symlink_err)
- .context("Failed creating symlink in node_modules folder"),
- ),
}
}