diff options
| author | Nathan Whitaker <17734409+nathanwhit@users.noreply.github.com> | 2024-09-24 12:23:57 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-09-24 19:23:57 +0000 |
| commit | 36ebc03f177cc7db5deb93f4d403cafbed756eb5 (patch) | |
| tree | c36af6c9b7093d3191de3cd6e60c4ce9dca4151a /cli/npm/managed/resolvers/local | |
| parent | ba5b8d0213cde2585236098b00beb8a512889626 (diff) | |
fix(cli): Warn on not-run lifecycle scripts with global cache (#25786)
Refactors the lifecycle scripts code to extract out the common
functionality and then uses that to provide a warning in the global
resolver.
While ideally we would still support them with the global cache, for now
a warning is at least better than the status quo (where people are
unaware why their packages aren't working).
Diffstat (limited to 'cli/npm/managed/resolvers/local')
| -rw-r--r-- | cli/npm/managed/resolvers/local/bin_entries.rs | 333 |
1 files changed, 0 insertions, 333 deletions
diff --git a/cli/npm/managed/resolvers/local/bin_entries.rs b/cli/npm/managed/resolvers/local/bin_entries.rs deleted file mode 100644 index 980a2653b..000000000 --- a/cli/npm/managed/resolvers/local/bin_entries.rs +++ /dev/null @@ -1,333 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -use crate::npm::managed::NpmResolutionPackage; -use deno_core::anyhow::Context; -use deno_core::error::AnyError; -use deno_npm::resolution::NpmResolutionSnapshot; -use deno_npm::NpmPackageId; -use std::collections::HashMap; -use std::collections::HashSet; -use std::collections::VecDeque; -use std::path::Path; -use std::path::PathBuf; - -#[derive(Default)] -pub(super) struct BinEntries { - /// Packages that have colliding bin names - collisions: HashSet<NpmPackageId>, - seen_names: HashMap<String, NpmPackageId>, - /// The bin entries - entries: Vec<(NpmResolutionPackage, PathBuf)>, -} - -/// Returns the name of the default binary for the given package. -/// This is the package name without the organization (`@org/`), if any. -fn default_bin_name(package: &NpmResolutionPackage) -> &str { - package - .id - .nv - .name - .rsplit_once('/') - .map_or(package.id.nv.name.as_str(), |(_, name)| name) -} - -impl BinEntries { - pub(super) fn new() -> Self { - Self::default() - } - - /// Add a new bin entry (package with a bin field) - pub(super) fn add( - &mut self, - package: NpmResolutionPackage, - package_path: PathBuf, - ) { - // check for a new collision, if we haven't already - // found one - match package.bin.as_ref().unwrap() { - deno_npm::registry::NpmPackageVersionBinEntry::String(_) => { - let bin_name = default_bin_name(&package); - - if let Some(other) = self - .seen_names - .insert(bin_name.to_string(), package.id.clone()) - { - self.collisions.insert(package.id.clone()); - self.collisions.insert(other); - } - } - deno_npm::registry::NpmPackageVersionBinEntry::Map(entries) => { - for name in entries.keys() { - if let Some(other) = - self.seen_names.insert(name.to_string(), package.id.clone()) - { - self.collisions.insert(package.id.clone()); - self.collisions.insert(other); - } - } - } - } - - self.entries.push((package, package_path)); - } - - fn for_each_entry( - &mut self, - snapshot: &NpmResolutionSnapshot, - mut f: impl FnMut( - &NpmResolutionPackage, - &Path, - &str, // bin name - &str, // bin script - ) -> Result<(), AnyError>, - ) -> Result<(), AnyError> { - if !self.collisions.is_empty() { - // 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); - } - - let mut seen = HashSet::new(); - - for (package, package_path) in &self.entries { - if let Some(bin_entries) = &package.bin { - match bin_entries { - deno_npm::registry::NpmPackageVersionBinEntry::String(script) => { - let name = default_bin_name(package); - if !seen.insert(name) { - // we already set up a bin entry with this name - continue; - } - f(package, package_path, name, script)?; - } - deno_npm::registry::NpmPackageVersionBinEntry::Map(entries) => { - for (name, script) in entries { - if !seen.insert(name) { - // we already set up a bin entry with this name - continue; - } - f(package, package_path, name, script)?; - } - } - } - } - } - - Ok(()) - } - - /// Collect the bin entries into a vec of (name, script path) - pub(super) fn into_bin_files( - mut self, - snapshot: &NpmResolutionSnapshot, - ) -> Vec<(String, PathBuf)> { - let mut bins = Vec::new(); - self - .for_each_entry(snapshot, |_, package_path, name, script| { - bins.push((name.to_string(), package_path.join(script))); - Ok(()) - }) - .unwrap(); - bins - } - - /// Finish setting up the bin entries, writing the necessary files - /// to disk. - pub(super) fn finish( - mut self, - snapshot: &NpmResolutionSnapshot, - bin_node_modules_dir_path: &Path, - ) -> 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( - || format!("Creating '{}'", bin_node_modules_dir_path.display()), - )?; - } - - self.for_each_entry(snapshot, |package, package_path, name, script| { - set_up_bin_entry( - package, - name, - script, - package_path, - bin_node_modules_dir_path, - ) - })?; - - Ok(()) - } -} - -// walk the dependency tree to find out the depth of each package -// that has a bin entry, then sort them by depth -fn sort_by_depth( - snapshot: &NpmResolutionSnapshot, - bin_entries: &mut [(NpmResolutionPackage, PathBuf)], - collisions: &mut HashSet<NpmPackageId>, -) { - enum Entry<'a> { - Pkg(&'a NpmPackageId), - IncreaseDepth, - } - - let mut seen = HashSet::new(); - let mut depths: HashMap<&NpmPackageId, u64> = - HashMap::with_capacity(collisions.len()); - - let mut queue = VecDeque::new(); - queue.extend(snapshot.top_level_packages().map(Entry::Pkg)); - seen.extend(snapshot.top_level_packages()); - queue.push_back(Entry::IncreaseDepth); - - let mut current_depth = 0u64; - - while let Some(entry) = queue.pop_front() { - if collisions.is_empty() { - break; - } - let id = match entry { - Entry::Pkg(id) => id, - Entry::IncreaseDepth => { - current_depth += 1; - if queue.is_empty() { - break; - } - queue.push_back(Entry::IncreaseDepth); - continue; - } - }; - if let Some(package) = snapshot.package_from_id(id) { - if collisions.remove(&package.id) { - depths.insert(&package.id, current_depth); - } - for dep in package.dependencies.values() { - if seen.insert(dep) { - queue.push_back(Entry::Pkg(dep)); - } - } - } - } - - bin_entries.sort_by(|(a, _), (b, _)| { - depths - .get(&a.id) - .unwrap_or(&u64::MAX) - .cmp(depths.get(&b.id).unwrap_or(&u64::MAX)) - .then_with(|| a.id.nv.cmp(&b.id.nv).reverse()) - }); -} - -pub(super) fn set_up_bin_entry( - package: &NpmResolutionPackage, - bin_name: &str, - #[allow(unused_variables)] bin_script: &str, - #[allow(unused_variables)] package_path: &Path, - bin_node_modules_dir_path: &Path, -) -> Result<(), AnyError> { - #[cfg(windows)] - { - set_up_bin_shim(package, bin_name, bin_node_modules_dir_path)?; - } - #[cfg(unix)] - { - symlink_bin_entry( - package, - bin_name, - bin_script, - package_path, - bin_node_modules_dir_path, - )?; - } - Ok(()) -} - -#[cfg(windows)] -fn set_up_bin_shim( - package: &NpmResolutionPackage, - bin_name: &str, - bin_node_modules_dir_path: &Path, -) -> Result<(), AnyError> { - use std::fs; - let mut cmd_shim = bin_node_modules_dir_path.join(bin_name); - - cmd_shim.set_extension("cmd"); - let shim = format!("@deno run -A npm:{}/{bin_name} %*", package.id.nv); - fs::write(&cmd_shim, shim).with_context(|| { - format!("Can't set up '{}' bin at {}", bin_name, cmd_shim.display()) - })?; - - Ok(()) -} - -#[cfg(unix)] -fn symlink_bin_entry( - _package: &NpmResolutionPackage, - bin_name: &str, - bin_script: &str, - package_path: &Path, - bin_node_modules_dir_path: &Path, -) -> Result<(), AnyError> { - use std::io; - use std::os::unix::fs::symlink; - let link = bin_node_modules_dir_path.join(bin_name); - let original = package_path.join(bin_script); - - use std::os::unix::fs::PermissionsExt; - let mut perms = match std::fs::metadata(&original) { - Ok(metadata) => metadata.permissions(), - Err(err) => { - if err.kind() == io::ErrorKind::NotFound { - 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(), - original.display() - ); - return Ok(()); - } - return Err(err).with_context(|| { - format!("Can't set up '{}' bin at {}", bin_name, original.display()) - }); - } - }; - if perms.mode() & 0o111 == 0 { - // if the original file is not executable, make it executable - perms.set_mode(perms.mode() | 0o111); - std::fs::set_permissions(&original, perms).with_context(|| { - format!("Setting permissions on '{}'", original.display()) - })?; - } - let original_relative = - crate::util::path::relative_path(bin_node_modules_dir_path, &original) - .unwrap_or(original); - - if let Err(err) = symlink(&original_relative, &link) { - if err.kind() == io::ErrorKind::AlreadyExists { - // remove and retry - std::fs::remove_file(&link).with_context(|| { - format!( - "Failed to remove existing bin symlink at {}", - link.display() - ) - })?; - symlink(&original_relative, &link).with_context(|| { - format!( - "Can't set up '{}' bin at {}", - bin_name, - original_relative.display() - ) - })?; - return Ok(()); - } - return Err(err).with_context(|| { - format!( - "Can't set up '{}' bin at {}", - bin_name, - original_relative.display() - ) - }); - } - - Ok(()) -} |
