2024-01-01 14:58:21 -05:00
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2022-09-22 11:17:02 -04:00
//! Code for local node_modules resolution.
2022-11-16 13:44:31 -05:00
use std ::borrow ::Cow ;
2024-06-06 18:37:41 -04:00
use std ::cell ::RefCell ;
2023-05-24 15:04:21 -04:00
use std ::cmp ::Ordering ;
2024-07-16 16:30:28 -04:00
use std ::collections ::hash_map ::Entry ;
2024-07-09 14:04:21 -04:00
use std ::collections ::BTreeMap ;
2023-05-24 15:04:21 -04:00
use std ::collections ::HashMap ;
2024-09-06 15:08:56 -04:00
use std ::collections ::HashSet ;
2022-09-22 11:17:02 -04:00
use std ::fs ;
use std ::path ::Path ;
use std ::path ::PathBuf ;
2024-06-06 18:37:41 -04:00
use std ::rc ::Rc ;
2023-04-14 16:22:33 -04:00
use std ::sync ::Arc ;
2022-09-22 11:17:02 -04:00
2024-07-09 23:06:08 -04:00
use crate ::args ::LifecycleScriptsConfig ;
2024-09-18 15:04:25 -04:00
use crate ::colors ;
2023-02-22 14:15:25 -05:00
use async_trait ::async_trait ;
2022-09-22 11:17:02 -04:00
use deno_ast ::ModuleSpecifier ;
2024-09-28 08:50:16 -04:00
use deno_cache_dir ::npm ::mixed_case_package_name_decode ;
2022-09-22 11:17:02 -04:00
use deno_core ::anyhow ::Context ;
use deno_core ::error ::AnyError ;
2024-06-02 21:39:13 -04:00
use deno_core ::futures ::stream ::FuturesUnordered ;
use deno_core ::futures ::StreamExt ;
2024-09-18 15:04:25 -04:00
use deno_core ::parking_lot ::Mutex ;
2022-09-22 11:17:02 -04:00
use deno_core ::url ::Url ;
2023-04-06 18:46:44 -04:00
use deno_npm ::resolution ::NpmResolutionSnapshot ;
use deno_npm ::NpmPackageCacheFolderId ;
use deno_npm ::NpmPackageId ;
2023-05-24 15:04:21 -04:00
use deno_npm ::NpmResolutionPackage ;
2023-05-17 17:38:50 -04:00
use deno_npm ::NpmSystemInfo ;
2024-09-30 09:33:32 -04:00
use deno_resolver ::npm ::normalize_pkg_name_for_node_modules_deno_folder ;
2023-05-05 12:44:24 -04:00
use deno_runtime ::deno_fs ;
2023-01-10 08:35:44 -05:00
use deno_runtime ::deno_node ::NodePermissions ;
2023-08-21 05:53:52 -04:00
use deno_semver ::package ::PackageNv ;
2024-07-25 19:08:14 -04:00
use node_resolver ::errors ::PackageFolderResolveError ;
use node_resolver ::errors ::PackageFolderResolveIoError ;
use node_resolver ::errors ::PackageNotFoundError ;
use node_resolver ::errors ::ReferrerNotFoundError ;
2023-07-10 13:42:47 -04:00
use serde ::Deserialize ;
use serde ::Serialize ;
2022-09-22 11:17:02 -04:00
2024-09-04 10:00:44 -04:00
use crate ::args ::NpmInstallDepsProvider ;
2024-07-09 12:15:03 -04:00
use crate ::cache ::CACHE_PERM ;
use crate ::util ::fs ::atomic_write_file_with_retries ;
use crate ::util ::fs ::canonicalize_path_maybe_not_exists_with_fs ;
use crate ::util ::fs ::clone_dir_recursive ;
use crate ::util ::fs ::symlink_dir ;
use crate ::util ::fs ::LaxSingleProcessFsFlag ;
use crate ::util ::progress_bar ::ProgressBar ;
use crate ::util ::progress_bar ::ProgressMessagePrompt ;
2022-09-22 11:17:02 -04:00
2023-10-02 17:53:55 -04:00
use super ::super ::cache ::NpmCache ;
2024-06-02 21:39:13 -04:00
use super ::super ::cache ::TarballCache ;
2023-09-30 12:06:38 -04:00
use super ::super ::resolution ::NpmResolution ;
2024-11-12 12:23:39 -05:00
use super ::common ::bin_entries ;
2023-02-22 14:15:25 -05:00
use super ::common ::NpmPackageFsResolver ;
2023-06-09 14:41:18 -04:00
use super ::common ::RegistryReadPermissionChecker ;
2022-09-22 11:17:02 -04:00
/// Resolver that creates a local node_modules directory
/// and resolves packages from it.
2023-04-14 16:22:33 -04:00
#[ derive(Debug) ]
2022-09-22 11:17:02 -04:00
pub struct LocalNpmPackageResolver {
2023-04-14 16:22:33 -04:00
cache : Arc < NpmCache > ,
2024-06-02 21:39:13 -04:00
fs : Arc < dyn deno_fs ::FileSystem > ,
2024-09-04 10:00:44 -04:00
npm_install_deps_provider : Arc < NpmInstallDepsProvider > ,
2023-03-13 14:18:29 -04:00
progress_bar : ProgressBar ,
2023-04-14 16:22:33 -04:00
resolution : Arc < NpmResolution > ,
2024-06-02 21:39:13 -04:00
tarball_cache : Arc < TarballCache > ,
2022-09-22 11:17:02 -04:00
root_node_modules_path : PathBuf ,
2023-02-23 10:58:10 -05:00
root_node_modules_url : Url ,
2023-05-17 17:38:50 -04:00
system_info : NpmSystemInfo ,
2023-06-09 14:41:18 -04:00
registry_read_permission_checker : RegistryReadPermissionChecker ,
2024-07-09 23:06:08 -04:00
lifecycle_scripts : LifecycleScriptsConfig ,
2022-09-22 11:17:02 -04:00
}
impl LocalNpmPackageResolver {
2024-07-03 20:54:33 -04:00
#[ allow(clippy::too_many_arguments) ]
2022-09-22 11:17:02 -04:00
pub fn new (
2023-04-14 16:22:33 -04:00
cache : Arc < NpmCache > ,
2024-06-02 21:39:13 -04:00
fs : Arc < dyn deno_fs ::FileSystem > ,
2024-09-04 10:00:44 -04:00
npm_install_deps_provider : Arc < NpmInstallDepsProvider > ,
2023-03-13 14:18:29 -04:00
progress_bar : ProgressBar ,
2023-04-14 16:22:33 -04:00
resolution : Arc < NpmResolution > ,
2024-06-02 21:39:13 -04:00
tarball_cache : Arc < TarballCache > ,
node_modules_folder : PathBuf ,
2023-05-17 17:38:50 -04:00
system_info : NpmSystemInfo ,
2024-07-09 23:06:08 -04:00
lifecycle_scripts : LifecycleScriptsConfig ,
2022-09-22 11:17:02 -04:00
) -> Self {
Self {
cache ,
2024-06-02 21:39:13 -04:00
fs : fs . clone ( ) ,
2024-09-04 10:00:44 -04:00
npm_install_deps_provider ,
2023-03-13 14:18:29 -04:00
progress_bar ,
2022-09-22 11:17:02 -04:00
resolution ,
2024-06-02 21:39:13 -04:00
tarball_cache ,
2023-06-09 14:41:18 -04:00
registry_read_permission_checker : RegistryReadPermissionChecker ::new (
fs ,
2024-06-02 21:39:13 -04:00
node_modules_folder . clone ( ) ,
2023-06-09 14:41:18 -04:00
) ,
2024-06-02 21:39:13 -04:00
root_node_modules_url : Url ::from_directory_path ( & node_modules_folder )
. unwrap ( ) ,
root_node_modules_path : node_modules_folder ,
system_info ,
2024-07-09 23:06:08 -04:00
lifecycle_scripts ,
2022-09-22 11:17:02 -04:00
}
}
fn resolve_package_root ( & self , path : & Path ) -> PathBuf {
let mut last_found = path ;
loop {
let parent = last_found . parent ( ) . unwrap ( ) ;
if parent . file_name ( ) . unwrap ( ) = = " node_modules " {
return last_found . to_path_buf ( ) ;
} else {
last_found = parent ;
}
}
}
fn resolve_folder_for_specifier (
& self ,
specifier : & ModuleSpecifier ,
2024-07-09 12:15:03 -04:00
) -> Result < Option < PathBuf > , std ::io ::Error > {
2023-08-27 00:04:12 -04:00
let Some ( relative_url ) =
self . root_node_modules_url . make_relative ( specifier )
else {
2023-07-17 14:00:44 -04:00
return Ok ( None ) ;
} ;
2022-09-22 11:17:02 -04:00
if relative_url . starts_with ( " ../ " ) {
2023-07-17 14:00:44 -04:00
return Ok ( None ) ;
2022-09-22 11:17:02 -04:00
}
// it's within the directory, so use it
2023-07-17 14:00:44 -04:00
let Some ( path ) = specifier . to_file_path ( ) . ok ( ) else {
return Ok ( None ) ;
} ;
// Canonicalize the path so it's not pointing to the symlinked directory
// in `node_modules` directory of the referrer.
2023-11-29 09:32:23 -05:00
canonicalize_path_maybe_not_exists_with_fs ( & path , self . fs . as_ref ( ) )
. map ( Some )
2022-09-22 11:17:02 -04:00
}
2024-05-23 17:26:23 -04:00
fn resolve_package_folder_from_specifier (
& self ,
specifier : & ModuleSpecifier ,
) -> Result < Option < PathBuf > , AnyError > {
let Some ( local_path ) = self . resolve_folder_for_specifier ( specifier ) ? else {
return Ok ( None ) ;
} ;
let package_root_path = self . resolve_package_root ( & local_path ) ;
Ok ( Some ( package_root_path ) )
}
2023-04-14 16:22:33 -04:00
}
2022-11-08 14:17:24 -05:00
2024-06-06 18:37:41 -04:00
#[ async_trait(?Send) ]
2023-04-14 16:22:33 -04:00
impl NpmPackageFsResolver for LocalNpmPackageResolver {
2024-09-30 09:33:32 -04:00
fn node_modules_path ( & self ) -> Option < & Path > {
Some ( self . root_node_modules_path . as_ref ( ) )
2023-04-14 16:22:33 -04:00
}
2024-07-09 12:15:03 -04:00
fn maybe_package_folder ( & self , id : & NpmPackageId ) -> Option < PathBuf > {
let cache_folder_id = self
. resolution
. resolve_pkg_cache_folder_id_from_pkg_id ( id ) ? ;
// package is stored at:
// node_modules/.deno/<package_cache_folder_id_folder_name>/node_modules/<package_name>
Some (
self
. root_node_modules_path
. join ( " .deno " )
. join ( get_package_folder_id_folder_name ( & cache_folder_id ) )
. join ( " node_modules " )
. join ( & cache_folder_id . nv . name ) ,
)
2022-11-08 14:17:24 -05:00
}
2022-09-22 11:17:02 -04:00
fn resolve_package_folder_from_package (
& self ,
name : & str ,
referrer : & ModuleSpecifier ,
2024-07-09 12:15:03 -04:00
) -> Result < PathBuf , PackageFolderResolveError > {
let maybe_local_path = self
. resolve_folder_for_specifier ( referrer )
2024-07-23 20:22:24 -04:00
. map_err ( | err | PackageFolderResolveIoError {
2024-07-09 12:15:03 -04:00
package_name : name . to_string ( ) ,
referrer : referrer . clone ( ) ,
source : err ,
} ) ? ;
let Some ( local_path ) = maybe_local_path else {
return Err (
2024-07-23 20:22:24 -04:00
ReferrerNotFoundError {
2024-07-09 12:15:03 -04:00
referrer : referrer . clone ( ) ,
referrer_extra : None ,
}
. into ( ) ,
) ;
2023-07-17 14:00:44 -04:00
} ;
2022-09-22 11:17:02 -04:00
let package_root_path = self . resolve_package_root ( & local_path ) ;
let mut current_folder = package_root_path . as_path ( ) ;
2024-04-25 10:13:55 -04:00
while let Some ( parent_folder ) = current_folder . parent ( ) {
current_folder = parent_folder ;
2023-05-24 15:04:21 -04:00
let node_modules_folder = if current_folder . ends_with ( " node_modules " ) {
Cow ::Borrowed ( current_folder )
} else {
Cow ::Owned ( current_folder . join ( " node_modules " ) )
} ;
2022-10-21 11:20:18 -04:00
2023-10-25 14:39:00 -04:00
let sub_dir = join_package_name ( & node_modules_folder , name ) ;
if self . fs . is_dir_sync ( & sub_dir ) {
return Ok ( sub_dir ) ;
}
2022-09-22 11:17:02 -04:00
if current_folder = = self . root_node_modules_path {
2024-04-25 10:13:55 -04:00
break ;
2022-09-22 11:17:02 -04:00
}
}
2024-04-25 10:13:55 -04:00
2024-07-09 12:15:03 -04:00
Err (
2024-07-23 20:22:24 -04:00
PackageNotFoundError {
2024-07-09 12:15:03 -04:00
package_name : name . to_string ( ) ,
referrer : referrer . clone ( ) ,
referrer_extra : None ,
}
. into ( ) ,
)
2022-09-22 11:17:02 -04:00
}
2023-07-01 21:07:57 -04:00
fn resolve_package_cache_folder_id_from_specifier (
& self ,
specifier : & ModuleSpecifier ,
2023-07-17 14:00:44 -04:00
) -> Result < Option < NpmPackageCacheFolderId > , AnyError > {
2023-08-27 00:04:12 -04:00
let Some ( folder_path ) =
self . resolve_package_folder_from_specifier ( specifier ) ?
else {
2023-07-17 14:00:44 -04:00
return Ok ( None ) ;
} ;
2023-07-01 21:07:57 -04:00
let folder_name = folder_path . parent ( ) . unwrap ( ) . to_string_lossy ( ) ;
2023-07-17 14:00:44 -04:00
Ok ( get_package_folder_id_from_folder_name ( & folder_name ) )
2023-07-01 21:07:57 -04:00
}
2024-06-03 17:17:08 -04:00
async fn cache_packages ( & self ) -> Result < ( ) , AnyError > {
2023-03-13 14:18:29 -04:00
sync_resolution_with_fs (
& self . resolution . snapshot ( ) ,
& self . cache ,
2024-09-04 10:00:44 -04:00
& self . npm_install_deps_provider ,
2023-03-13 14:18:29 -04:00
& self . progress_bar ,
2024-06-02 21:39:13 -04:00
& self . tarball_cache ,
2023-03-13 14:18:29 -04:00
& self . root_node_modules_path ,
2023-05-17 17:38:50 -04:00
& self . system_info ,
2024-07-09 23:06:08 -04:00
& self . lifecycle_scripts ,
2023-03-13 14:18:29 -04:00
)
. await
2022-09-22 11:17:02 -04:00
}
2024-10-04 15:55:41 -04:00
fn ensure_read_permission < ' a > (
2023-01-10 08:35:44 -05:00
& self ,
2024-06-06 23:37:53 -04:00
permissions : & mut dyn NodePermissions ,
2024-10-04 15:55:41 -04:00
path : & ' a Path ,
) -> Result < Cow < ' a , Path > , AnyError > {
2023-06-09 14:41:18 -04:00
self
. registry_read_permission_checker
. ensure_registry_read_permission ( permissions , path )
2022-09-22 11:17:02 -04:00
}
}
2024-09-24 15:23:57 -04:00
/// `node_modules/.deno/<package>/node_modules/<package_name>`
///
/// Where the actual package is stored.
fn local_node_modules_package_contents_path (
2024-07-09 23:06:08 -04:00
local_registry_dir : & Path ,
package : & NpmResolutionPackage ,
) -> PathBuf {
local_registry_dir
. join ( get_package_folder_id_folder_name (
& package . get_package_cache_folder_id ( ) ,
) )
. join ( " node_modules " )
. join ( & package . id . nv . name )
}
2022-09-22 11:17:02 -04:00
/// Creates a pnpm style folder structure.
2024-07-09 23:06:08 -04:00
#[ allow(clippy::too_many_arguments) ]
2022-09-23 17:35:48 -04:00
async fn sync_resolution_with_fs (
2022-09-22 11:17:02 -04:00
snapshot : & NpmResolutionSnapshot ,
2023-04-14 16:22:33 -04:00
cache : & Arc < NpmCache > ,
2024-09-04 10:00:44 -04:00
npm_install_deps_provider : & NpmInstallDepsProvider ,
2023-03-13 14:18:29 -04:00
progress_bar : & ProgressBar ,
2024-06-02 21:39:13 -04:00
tarball_cache : & Arc < TarballCache > ,
2022-09-22 11:17:02 -04:00
root_node_modules_dir_path : & Path ,
2023-05-17 17:38:50 -04:00
system_info : & NpmSystemInfo ,
2024-07-09 23:06:08 -04:00
lifecycle_scripts : & LifecycleScriptsConfig ,
2022-09-22 11:17:02 -04:00
) -> Result < ( ) , AnyError > {
2024-09-04 10:00:44 -04:00
if snapshot . is_empty ( )
& & npm_install_deps_provider . workspace_pkgs ( ) . is_empty ( )
{
2023-03-13 10:03:19 -04:00
return Ok ( ( ) ) ; // don't create the directory
}
2024-11-26 18:29:46 -05:00
// don't set up node_modules (and more importantly try to acquire the file lock)
// if we're running as part of a lifecycle script
if super ::common ::lifecycle_scripts ::is_running_lifecycle_script ( ) {
return Ok ( ( ) ) ;
}
2022-09-22 11:17:02 -04:00
let deno_local_registry_dir = root_node_modules_dir_path . join ( " .deno " ) ;
2023-05-24 15:04:21 -04:00
let deno_node_modules_dir = deno_local_registry_dir . join ( " node_modules " ) ;
fs ::create_dir_all ( & deno_node_modules_dir ) . with_context ( | | {
2022-09-22 11:17:02 -04:00
format! ( " Creating ' {} ' " , deno_local_registry_dir . display ( ) )
} ) ? ;
2024-05-23 19:43:38 -04:00
let bin_node_modules_dir_path = root_node_modules_dir_path . join ( " .bin " ) ;
fs ::create_dir_all ( & bin_node_modules_dir_path ) . with_context ( | | {
format! ( " Creating ' {} ' " , bin_node_modules_dir_path . display ( ) )
} ) ? ;
2022-09-22 11:17:02 -04:00
2023-03-08 10:13:13 -05:00
let single_process_lock = LaxSingleProcessFsFlag ::lock (
deno_local_registry_dir . join ( " .deno.lock " ) ,
// similar message used by cargo build
" waiting for file lock on node_modules directory " ,
)
. await ;
2023-07-10 13:42:47 -04:00
// load this after we get the directory lock
let mut setup_cache =
SetupCache ::load ( deno_local_registry_dir . join ( " .setup-cache.bin " ) ) ;
2023-03-13 14:18:29 -04:00
let pb_clear_guard = progress_bar . clear_guard ( ) ; // prevent flickering
2022-09-22 11:17:02 -04:00
// 1. Write all the packages out the .deno directory.
//
// Copy (hardlink in future) <global_registry_cache>/<package_id>/ to
2022-11-08 14:17:24 -05:00
// node_modules/.deno/<package_folder_id_folder_name>/node_modules/<package_name>
2023-09-14 13:51:28 -04:00
let package_partitions =
2023-05-17 17:38:50 -04:00
snapshot . all_system_packages_partitioned ( system_info ) ;
2024-06-02 21:39:13 -04:00
let mut cache_futures = FuturesUnordered ::new ( ) ;
2023-05-24 15:04:21 -04:00
let mut newest_packages_by_name : HashMap < & String , & NpmResolutionPackage > =
HashMap ::with_capacity ( package_partitions . packages . len ( ) ) ;
2024-11-12 12:23:39 -05:00
let bin_entries = Rc ::new ( RefCell ::new ( bin_entries ::BinEntries ::new ( ) ) ) ;
2024-09-24 15:23:57 -04:00
let mut lifecycle_scripts =
super ::common ::lifecycle_scripts ::LifecycleScripts ::new (
lifecycle_scripts ,
LocalLifecycleScripts {
deno_local_registry_dir : & deno_local_registry_dir ,
} ,
) ;
2024-09-18 15:04:25 -04:00
let packages_with_deprecation_warnings = Arc ::new ( Mutex ::new ( Vec ::new ( ) ) ) ;
fix(install): store tags associated with package in node_modules dir (#26000)
Fixes #25998. Fixes https://github.com/denoland/deno/issues/25928.
Originally I was just going to make this an error message instead of a
panic, but once I got to a minimal repro I felt that this really should
work.
The panic occurs when you have `nodeModulesDir: manual` (or a
package.json present), and you have an npm package with a tag in your
deno.json (see the spec test that illustrates this).
This code path only actually executes when trying to choose an
appropriate package version from `node_modules/.deno`, so we should be
able to fix it by storing some extra data at install time.
The fix proposed here is to repurpose the `.initialized` file that we
store in `node_modules` to store the tags associated with a package.
Basically, if you have a version requirement with a tag (e.g.
`npm:chalk@latest`), when we set up the node_modules folder for that
package, we store the tag (`latest`) in `.initialized`. Then, when doing
BYONM resolution, if we have a version requirement with a tag, we read
that file and check if the tag is present.
The downside is that we do more work when setting up `node_modules`. We
_could_ do this only when BYONM is enabled, but that would have the
downside of needing to re-run `deno install` when you switch from auto
-> manual, though maybe that's not a big deal.
2024-10-02 20:16:46 -04:00
let mut package_tags : HashMap < & PackageNv , Vec < & str > > = HashMap ::new ( ) ;
for ( package_req , package_nv ) in snapshot . package_reqs ( ) {
if let Some ( tag ) = package_req . version_req . tag ( ) {
package_tags . entry ( package_nv ) . or_default ( ) . push ( tag ) ;
}
}
2022-11-08 14:17:24 -05:00
for package in & package_partitions . packages {
2023-05-24 15:04:21 -04:00
if let Some ( current_pkg ) =
2023-05-24 16:23:10 -04:00
newest_packages_by_name . get_mut ( & package . id . nv . name )
2023-05-24 15:04:21 -04:00
{
2023-05-24 16:23:10 -04:00
if current_pkg . id . nv . cmp ( & package . id . nv ) = = Ordering ::Less {
2023-05-24 15:04:21 -04:00
* current_pkg = package ;
}
} else {
2023-05-24 16:23:10 -04:00
newest_packages_by_name . insert ( & package . id . nv . name , package ) ;
2023-05-24 15:04:21 -04:00
} ;
2023-07-10 13:42:47 -04:00
let package_folder_name =
2022-11-08 14:17:24 -05:00
get_package_folder_id_folder_name ( & package . get_package_cache_folder_id ( ) ) ;
2023-07-10 13:42:47 -04:00
let folder_path = deno_local_registry_dir . join ( & package_folder_name ) ;
fix(install): store tags associated with package in node_modules dir (#26000)
Fixes #25998. Fixes https://github.com/denoland/deno/issues/25928.
Originally I was just going to make this an error message instead of a
panic, but once I got to a minimal repro I felt that this really should
work.
The panic occurs when you have `nodeModulesDir: manual` (or a
package.json present), and you have an npm package with a tag in your
deno.json (see the spec test that illustrates this).
This code path only actually executes when trying to choose an
appropriate package version from `node_modules/.deno`, so we should be
able to fix it by storing some extra data at install time.
The fix proposed here is to repurpose the `.initialized` file that we
store in `node_modules` to store the tags associated with a package.
Basically, if you have a version requirement with a tag (e.g.
`npm:chalk@latest`), when we set up the node_modules folder for that
package, we store the tag (`latest`) in `.initialized`. Then, when doing
BYONM resolution, if we have a version requirement with a tag, we read
that file and check if the tag is present.
The downside is that we do more work when setting up `node_modules`. We
_could_ do this only when BYONM is enabled, but that would have the
downside of needing to re-run `deno install` when you switch from auto
-> manual, though maybe that's not a big deal.
2024-10-02 20:16:46 -04:00
let tags = package_tags
. get ( & package . id . nv )
. map ( | tags | tags . join ( " , " ) )
. unwrap_or_default ( ) ;
enum PackageFolderState {
UpToDate ,
Uninitialized ,
TagsOutdated ,
}
2022-11-08 14:17:24 -05:00
let initialized_file = folder_path . join ( " .initialized " ) ;
fix(install): store tags associated with package in node_modules dir (#26000)
Fixes #25998. Fixes https://github.com/denoland/deno/issues/25928.
Originally I was just going to make this an error message instead of a
panic, but once I got to a minimal repro I felt that this really should
work.
The panic occurs when you have `nodeModulesDir: manual` (or a
package.json present), and you have an npm package with a tag in your
deno.json (see the spec test that illustrates this).
This code path only actually executes when trying to choose an
appropriate package version from `node_modules/.deno`, so we should be
able to fix it by storing some extra data at install time.
The fix proposed here is to repurpose the `.initialized` file that we
store in `node_modules` to store the tags associated with a package.
Basically, if you have a version requirement with a tag (e.g.
`npm:chalk@latest`), when we set up the node_modules folder for that
package, we store the tag (`latest`) in `.initialized`. Then, when doing
BYONM resolution, if we have a version requirement with a tag, we read
that file and check if the tag is present.
The downside is that we do more work when setting up `node_modules`. We
_could_ do this only when BYONM is enabled, but that would have the
downside of needing to re-run `deno install` when you switch from auto
-> manual, though maybe that's not a big deal.
2024-10-02 20:16:46 -04:00
let package_state = std ::fs ::read_to_string ( & initialized_file )
. map ( | s | {
if s ! = tags {
PackageFolderState ::TagsOutdated
} else {
PackageFolderState ::UpToDate
}
} )
. unwrap_or ( PackageFolderState ::Uninitialized ) ;
2022-11-27 13:25:08 -05:00
if ! cache
. cache_setting ( )
2023-05-24 16:23:10 -04:00
. should_use_for_npm_package ( & package . id . nv . name )
fix(install): store tags associated with package in node_modules dir (#26000)
Fixes #25998. Fixes https://github.com/denoland/deno/issues/25928.
Originally I was just going to make this an error message instead of a
panic, but once I got to a minimal repro I felt that this really should
work.
The panic occurs when you have `nodeModulesDir: manual` (or a
package.json present), and you have an npm package with a tag in your
deno.json (see the spec test that illustrates this).
This code path only actually executes when trying to choose an
appropriate package version from `node_modules/.deno`, so we should be
able to fix it by storing some extra data at install time.
The fix proposed here is to repurpose the `.initialized` file that we
store in `node_modules` to store the tags associated with a package.
Basically, if you have a version requirement with a tag (e.g.
`npm:chalk@latest`), when we set up the node_modules folder for that
package, we store the tag (`latest`) in `.initialized`. Then, when doing
BYONM resolution, if we have a version requirement with a tag, we read
that file and check if the tag is present.
The downside is that we do more work when setting up `node_modules`. We
_could_ do this only when BYONM is enabled, but that would have the
downside of needing to re-run `deno install` when you switch from auto
-> manual, though maybe that's not a big deal.
2024-10-02 20:16:46 -04:00
| | matches! ( package_state , PackageFolderState ::Uninitialized )
2022-11-08 14:17:24 -05:00
{
2023-07-10 13:42:47 -04:00
// cache bust the dep from the dep setup cache so the symlinks
// are forced to be recreated
setup_cache . remove_dep ( & package_folder_name ) ;
2024-07-09 23:06:08 -04:00
let folder_path = folder_path . clone ( ) ;
2024-05-29 20:45:22 -04:00
let bin_entries_to_setup = bin_entries . clone ( ) ;
2024-09-18 15:04:25 -04:00
let packages_with_deprecation_warnings =
packages_with_deprecation_warnings . clone ( ) ;
fix(install): store tags associated with package in node_modules dir (#26000)
Fixes #25998. Fixes https://github.com/denoland/deno/issues/25928.
Originally I was just going to make this an error message instead of a
panic, but once I got to a minimal repro I felt that this really should
work.
The panic occurs when you have `nodeModulesDir: manual` (or a
package.json present), and you have an npm package with a tag in your
deno.json (see the spec test that illustrates this).
This code path only actually executes when trying to choose an
appropriate package version from `node_modules/.deno`, so we should be
able to fix it by storing some extra data at install time.
The fix proposed here is to repurpose the `.initialized` file that we
store in `node_modules` to store the tags associated with a package.
Basically, if you have a version requirement with a tag (e.g.
`npm:chalk@latest`), when we set up the node_modules folder for that
package, we store the tag (`latest`) in `.initialized`. Then, when doing
BYONM resolution, if we have a version requirement with a tag, we read
that file and check if the tag is present.
The downside is that we do more work when setting up `node_modules`. We
_could_ do this only when BYONM is enabled, but that would have the
downside of needing to re-run `deno install` when you switch from auto
-> manual, though maybe that's not a big deal.
2024-10-02 20:16:46 -04:00
2024-06-02 21:39:13 -04:00
cache_futures . push ( async move {
tarball_cache
2024-06-03 17:17:08 -04:00
. ensure_package ( & package . id . nv , & package . dist )
2024-06-02 21:39:13 -04:00
. await ? ;
let pb_guard = progress_bar . update_with_prompt (
2023-03-13 14:18:29 -04:00
ProgressMessagePrompt ::Initialize ,
2023-05-24 16:23:10 -04:00
& package . id . nv . to_string ( ) ,
2023-03-13 14:18:29 -04:00
) ;
2022-09-23 17:35:48 -04:00
let sub_node_modules = folder_path . join ( " node_modules " ) ;
let package_path =
2023-05-24 16:23:10 -04:00
join_package_name ( & sub_node_modules , & package . id . nv . name ) ;
2024-06-02 21:39:13 -04:00
let cache_folder = cache . package_folder_for_nv ( & package . id . nv ) ;
2024-05-28 17:17:36 -04:00
deno_core ::unsync ::spawn_blocking ( {
let package_path = package_path . clone ( ) ;
move | | {
clone_dir_recursive ( & cache_folder , & package_path ) ? ;
// write out a file that indicates this folder has been initialized
fix(install): store tags associated with package in node_modules dir (#26000)
Fixes #25998. Fixes https://github.com/denoland/deno/issues/25928.
Originally I was just going to make this an error message instead of a
panic, but once I got to a minimal repro I felt that this really should
work.
The panic occurs when you have `nodeModulesDir: manual` (or a
package.json present), and you have an npm package with a tag in your
deno.json (see the spec test that illustrates this).
This code path only actually executes when trying to choose an
appropriate package version from `node_modules/.deno`, so we should be
able to fix it by storing some extra data at install time.
The fix proposed here is to repurpose the `.initialized` file that we
store in `node_modules` to store the tags associated with a package.
Basically, if you have a version requirement with a tag (e.g.
`npm:chalk@latest`), when we set up the node_modules folder for that
package, we store the tag (`latest`) in `.initialized`. Then, when doing
BYONM resolution, if we have a version requirement with a tag, we read
that file and check if the tag is present.
The downside is that we do more work when setting up `node_modules`. We
_could_ do this only when BYONM is enabled, but that would have the
downside of needing to re-run `deno install` when you switch from auto
-> manual, though maybe that's not a big deal.
2024-10-02 20:16:46 -04:00
fs ::write ( initialized_file , tags ) ? ;
2024-05-28 17:17:36 -04:00
Ok ::< _ , AnyError > ( ( ) )
}
} )
. await ? ? ;
2024-05-23 19:43:38 -04:00
if package . bin . is_some ( ) {
2024-09-24 15:23:57 -04:00
bin_entries_to_setup . borrow_mut ( ) . add ( package , package_path ) ;
2024-05-23 19:43:38 -04:00
}
2024-08-20 13:53:53 -04:00
if let Some ( deprecated ) = & package . deprecated {
2024-09-18 15:04:25 -04:00
packages_with_deprecation_warnings
. lock ( )
. push ( ( package . id . clone ( ) , deprecated . clone ( ) ) ) ;
2024-08-20 13:53:53 -04:00
}
2023-03-13 14:18:29 -04:00
// finally stop showing the progress bar
drop ( pb_guard ) ; // explicit for clarity
2024-06-02 21:39:13 -04:00
Ok ::< _ , AnyError > ( ( ) )
2022-09-23 17:35:48 -04:00
} ) ;
fix(install): store tags associated with package in node_modules dir (#26000)
Fixes #25998. Fixes https://github.com/denoland/deno/issues/25928.
Originally I was just going to make this an error message instead of a
panic, but once I got to a minimal repro I felt that this really should
work.
The panic occurs when you have `nodeModulesDir: manual` (or a
package.json present), and you have an npm package with a tag in your
deno.json (see the spec test that illustrates this).
This code path only actually executes when trying to choose an
appropriate package version from `node_modules/.deno`, so we should be
able to fix it by storing some extra data at install time.
The fix proposed here is to repurpose the `.initialized` file that we
store in `node_modules` to store the tags associated with a package.
Basically, if you have a version requirement with a tag (e.g.
`npm:chalk@latest`), when we set up the node_modules folder for that
package, we store the tag (`latest`) in `.initialized`. Then, when doing
BYONM resolution, if we have a version requirement with a tag, we read
that file and check if the tag is present.
The downside is that we do more work when setting up `node_modules`. We
_could_ do this only when BYONM is enabled, but that would have the
downside of needing to re-run `deno install` when you switch from auto
-> manual, though maybe that's not a big deal.
2024-10-02 20:16:46 -04:00
} else if matches! ( package_state , PackageFolderState ::TagsOutdated ) {
fs ::write ( initialized_file , tags ) ? ;
2022-09-22 11:17:02 -04:00
}
2024-07-09 23:06:08 -04:00
2024-07-11 14:39:45 -04:00
let sub_node_modules = folder_path . join ( " node_modules " ) ;
let package_path =
join_package_name ( & sub_node_modules , & package . id . nv . name ) ;
2024-09-24 15:23:57 -04:00
lifecycle_scripts . add ( package , package_path . into ( ) ) ;
2022-09-22 11:17:02 -04:00
}
2024-06-02 21:39:13 -04:00
while let Some ( result ) = cache_futures . next ( ) . await {
result ? ; // surface the first error
2022-09-23 17:35:48 -04:00
}
2022-11-08 14:17:24 -05:00
// 2. Create any "copy" packages, which are used for peer dependencies
for package in & package_partitions . copy_packages {
let package_cache_folder_id = package . get_package_cache_folder_id ( ) ;
let destination_path = deno_local_registry_dir
2022-12-17 17:20:15 -05:00
. join ( get_package_folder_id_folder_name ( & package_cache_folder_id ) ) ;
2022-11-08 14:17:24 -05:00
let initialized_file = destination_path . join ( " .initialized " ) ;
if ! initialized_file . exists ( ) {
let sub_node_modules = destination_path . join ( " node_modules " ) ;
2023-02-21 12:03:48 -05:00
let package_path =
2023-05-24 16:23:10 -04:00
join_package_name ( & sub_node_modules , & package . id . nv . name ) ;
2024-05-28 14:59:17 -04:00
2022-11-08 14:17:24 -05:00
let source_path = join_package_name (
& deno_local_registry_dir
2022-12-17 17:20:15 -05:00
. join ( get_package_folder_id_folder_name (
2022-11-08 14:17:24 -05:00
& package_cache_folder_id . with_no_count ( ) ,
) )
. join ( " node_modules " ) ,
2023-05-24 16:23:10 -04:00
& package . id . nv . name ,
2022-11-08 14:17:24 -05:00
) ;
2024-05-28 17:17:36 -04:00
2024-05-28 14:59:17 -04:00
clone_dir_recursive ( & source_path , & package_path ) ? ;
2022-11-08 14:17:24 -05:00
// write out a file that indicates this folder has been initialized
fs ::write ( initialized_file , " " ) ? ;
}
}
// 3. Symlink all the dependencies into the .deno directory.
2022-09-22 11:17:02 -04:00
//
// Symlink node_modules/.deno/<package_id>/node_modules/<dep_name> to
// node_modules/.deno/<dep_id>/node_modules/<dep_package_name>
2023-05-24 16:23:10 -04:00
for package in package_partitions . iter_all ( ) {
2023-07-10 13:42:47 -04:00
let package_folder_name =
get_package_folder_id_folder_name ( & package . get_package_cache_folder_id ( ) ) ;
2022-09-22 11:17:02 -04:00
let sub_node_modules = deno_local_registry_dir
2023-07-10 13:42:47 -04:00
. join ( & package_folder_name )
2022-09-22 11:17:02 -04:00
. join ( " node_modules " ) ;
2023-07-10 13:42:47 -04:00
let mut dep_setup_cache = setup_cache . with_dep ( & package_folder_name ) ;
2022-09-22 11:17:02 -04:00
for ( name , dep_id ) in & package . dependencies {
2023-12-06 14:24:00 -05:00
let dep = snapshot . package_from_id ( dep_id ) . unwrap ( ) ;
if package . optional_dependencies . contains ( name )
& & ! dep . system . matches_system ( system_info )
{
continue ; // this isn't a dependency for the current system
}
let dep_cache_folder_id = dep . get_package_cache_folder_id ( ) ;
2022-11-08 14:17:24 -05:00
let dep_folder_name =
get_package_folder_id_folder_name ( & dep_cache_folder_id ) ;
2023-07-10 13:42:47 -04:00
if dep_setup_cache . insert ( name , & dep_folder_name ) {
let dep_folder_path = join_package_name (
& deno_local_registry_dir
. join ( dep_folder_name )
. join ( " node_modules " ) ,
& dep_id . nv . name ,
) ;
symlink_package_dir (
& dep_folder_path ,
& join_package_name ( & sub_node_modules , name ) ,
) ? ;
}
2022-09-22 11:17:02 -04:00
}
}
2024-07-16 16:30:28 -04:00
let mut found_names : HashMap < & String , & PackageNv > = HashMap ::new ( ) ;
2024-09-06 15:08:56 -04:00
// set of node_modules in workspace packages that we've already ensured exist
let mut existing_child_node_modules_dirs : HashSet < PathBuf > = HashSet ::new ( ) ;
2024-07-16 16:30:28 -04:00
// 4. Create symlinks for package json dependencies
{
2024-09-04 10:00:44 -04:00
for remote in npm_install_deps_provider . remote_pkgs ( ) {
2024-08-09 09:35:54 -04:00
let remote_pkg = if let Ok ( remote_pkg ) =
snapshot . resolve_pkg_from_pkg_req ( & remote . req )
{
remote_pkg
} else if remote . req . version_req . tag ( ) . is_some ( ) {
// couldn't find a match, and `resolve_best_package_id`
// panics if you give it a tag
continue ;
} else if let Some ( remote_id ) = snapshot
2024-07-16 16:30:28 -04:00
. resolve_best_package_id ( & remote . req . name , & remote . req . version_req )
2024-08-09 09:35:54 -04:00
{
snapshot . package_from_id ( & remote_id ) . unwrap ( )
} else {
2024-07-16 16:30:28 -04:00
continue ; // skip, package not found
} ;
2024-09-09 16:19:29 -04:00
let Some ( remote_alias ) = & remote . alias else {
continue ;
} ;
let alias_clashes = remote . req . name ! = * remote_alias
& & newest_packages_by_name . contains_key ( remote_alias ) ;
2024-07-16 16:30:28 -04:00
let install_in_child = {
// we'll install in the child if the alias is taken by another package, or
// if there's already a package with the same name but different version
// linked into the root
2024-09-09 16:19:29 -04:00
match found_names . entry ( remote_alias ) {
2024-07-16 16:30:28 -04:00
Entry ::Occupied ( nv ) = > {
2024-10-02 18:11:43 -04:00
// alias to a different package (in case of duplicate aliases)
// or the version doesn't match the version in the root node_modules
alias_clashes | | & remote_pkg . id . nv ! = * nv . get ( )
2024-07-16 16:30:28 -04:00
}
Entry ::Vacant ( entry ) = > {
entry . insert ( & remote_pkg . id . nv ) ;
alias_clashes
}
}
} ;
let target_folder_name = get_package_folder_id_folder_name (
& remote_pkg . get_package_cache_folder_id ( ) ,
) ;
let local_registry_package_path = join_package_name (
& deno_local_registry_dir
. join ( & target_folder_name )
. join ( " node_modules " ) ,
& remote_pkg . id . nv . name ,
) ;
if install_in_child {
// symlink the dep into the package's child node_modules folder
2024-09-06 15:08:56 -04:00
let dest_node_modules = remote . base_dir . join ( " node_modules " ) ;
if ! existing_child_node_modules_dirs . contains ( & dest_node_modules ) {
fs ::create_dir_all ( & dest_node_modules ) . with_context ( | | {
format! ( " Creating ' {} ' " , dest_node_modules . display ( ) )
} ) ? ;
existing_child_node_modules_dirs . insert ( dest_node_modules . clone ( ) ) ;
}
let mut dest_path = dest_node_modules ;
2024-09-09 16:19:29 -04:00
dest_path . push ( remote_alias ) ;
2024-07-16 16:30:28 -04:00
symlink_package_dir ( & local_registry_package_path , & dest_path ) ? ;
} else {
// symlink the package into `node_modules/<alias>`
if setup_cache
. insert_root_symlink ( & remote_pkg . id . nv . name , & target_folder_name )
{
symlink_package_dir (
& local_registry_package_path ,
2024-09-09 16:19:29 -04:00
& join_package_name ( root_node_modules_dir_path , remote_alias ) ,
2024-07-16 16:30:28 -04:00
) ? ;
}
}
}
}
// 5. Create symlinks for the remaining top level packages in the node_modules folder.
2024-09-04 10:00:44 -04:00
// (These may be present if they are not in the package.json dependencies)
2024-07-16 16:30:28 -04:00
// Symlink node_modules/.deno/<package_id>/node_modules/<package_name> to
// node_modules/<package_name>
let mut ids = snapshot
. top_level_packages ( )
. filter ( | f | ! found_names . contains_key ( & f . nv . name ) )
. collect ::< Vec < _ > > ( ) ;
2023-04-24 17:08:11 -04:00
ids . sort_by ( | a , b | b . cmp ( a ) ) ; // create determinism and only include the latest version
for id in ids {
2024-07-16 16:30:28 -04:00
match found_names . entry ( & id . nv . name ) {
Entry ::Occupied ( _ ) = > {
continue ; // skip, already handled
}
Entry ::Vacant ( entry ) = > {
entry . insert ( & id . nv ) ;
}
2023-05-24 15:04:21 -04:00
}
2023-04-06 18:46:44 -04:00
let package = snapshot . package_from_id ( id ) . unwrap ( ) ;
2023-07-10 13:42:47 -04:00
let target_folder_name =
get_package_folder_id_folder_name ( & package . get_package_cache_folder_id ( ) ) ;
if setup_cache . insert_root_symlink ( & id . nv . name , & target_folder_name ) {
let local_registry_package_path = join_package_name (
& deno_local_registry_dir
. join ( target_folder_name )
. join ( " node_modules " ) ,
& id . nv . name ,
) ;
2022-09-22 11:17:02 -04:00
2023-07-10 13:42:47 -04:00
symlink_package_dir (
& local_registry_package_path ,
& join_package_name ( root_node_modules_dir_path , & id . nv . name ) ,
) ? ;
}
2023-05-24 15:04:21 -04:00
}
2024-07-16 16:30:28 -04:00
// 6. Create a node_modules/.deno/node_modules/<package-name> directory with
2023-05-24 15:04:21 -04:00
// the remaining packages
for package in newest_packages_by_name . values ( ) {
2024-07-16 16:30:28 -04:00
match found_names . entry ( & package . id . nv . name ) {
Entry ::Occupied ( _ ) = > {
continue ; // skip, already handled
}
Entry ::Vacant ( entry ) = > {
entry . insert ( & package . id . nv ) ;
}
2023-05-24 15:04:21 -04:00
}
2023-07-10 13:42:47 -04:00
let target_folder_name =
get_package_folder_id_folder_name ( & package . get_package_cache_folder_id ( ) ) ;
if setup_cache . insert_deno_symlink ( & package . id . nv . name , & target_folder_name )
{
let local_registry_package_path = join_package_name (
& deno_local_registry_dir
. join ( target_folder_name )
. join ( " node_modules " ) ,
& package . id . nv . name ,
) ;
2023-05-24 15:04:21 -04:00
2023-07-10 13:42:47 -04:00
symlink_package_dir (
& local_registry_package_path ,
& join_package_name ( & deno_node_modules_dir , & package . id . nv . name ) ,
) ? ;
}
2022-09-22 11:17:02 -04:00
}
2024-07-16 16:30:28 -04:00
// 7. Set up `node_modules/.bin` entries for packages that need it.
2024-05-23 19:43:38 -04:00
{
2024-06-06 18:37:41 -04:00
let bin_entries = std ::mem ::take ( & mut * bin_entries . borrow_mut ( ) ) ;
2024-11-12 12:23:39 -05:00
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 ( ) ,
}
} ,
) ? ;
2024-05-23 19:43:38 -04:00
}
2024-07-16 16:30:28 -04:00
// 8. Create symlinks for the workspace packages
2024-07-03 20:54:33 -04:00
{
2024-09-04 10:00:44 -04:00
// todo(dsherret): this is not exactly correct because it should
2024-07-03 20:54:33 -04:00
// install correctly for a workspace (potentially in sub directories),
// but this is good enough for a first pass
2024-09-04 10:00:44 -04:00
for workspace in npm_install_deps_provider . workspace_pkgs ( ) {
2024-09-09 16:19:29 -04:00
let Some ( workspace_alias ) = & workspace . alias else {
continue ;
} ;
2024-07-03 20:54:33 -04:00
symlink_package_dir (
2024-07-15 15:08:51 -04:00
& workspace . target_dir ,
2024-09-09 16:19:29 -04:00
& root_node_modules_dir_path . join ( workspace_alias ) ,
2024-07-03 20:54:33 -04:00
) ? ;
}
}
2024-09-18 15:04:25 -04:00
{
let packages_with_deprecation_warnings =
packages_with_deprecation_warnings . lock ( ) ;
if ! packages_with_deprecation_warnings . is_empty ( ) {
log ::warn! (
2024-09-24 15:23:57 -04:00
" {} The following packages are deprecated: " ,
2024-09-18 15:04:25 -04:00
colors ::yellow ( " Warning " )
) ;
let len = packages_with_deprecation_warnings . len ( ) ;
for ( idx , ( package_id , msg ) ) in
packages_with_deprecation_warnings . iter ( ) . enumerate ( )
{
if idx ! = len - 1 {
log ::warn! (
" ┠─ {} " ,
colors ::gray ( format! ( " npm: {:?} ( {} ) " , package_id , msg ) )
) ;
} else {
log ::warn! (
2024-09-24 15:23:57 -04:00
" ┖─ {} " ,
2024-09-18 15:04:25 -04:00
colors ::gray ( format! ( " npm: {:?} ( {} ) " , package_id , msg ) )
) ;
}
}
}
}
2024-09-24 15:23:57 -04:00
lifecycle_scripts
. finish (
snapshot ,
& package_partitions . packages ,
2024-11-12 12:23:39 -05:00
root_node_modules_dir_path ,
2024-10-12 15:14:32 -04:00
progress_bar ,
2024-09-24 15:23:57 -04:00
)
. await ? ;
2024-09-18 15:04:25 -04:00
2024-09-24 15:23:57 -04:00
setup_cache . save ( ) ;
drop ( single_process_lock ) ;
drop ( pb_clear_guard ) ;
2024-09-18 15:04:25 -04:00
2024-09-24 15:23:57 -04:00
Ok ( ( ) )
}
/// `node_modules/.deno/<package>/`
fn local_node_modules_package_folder (
local_registry_dir : & Path ,
package : & NpmResolutionPackage ,
) -> PathBuf {
local_registry_dir . join ( get_package_folder_id_folder_name (
& package . get_package_cache_folder_id ( ) ,
) )
}
2024-09-18 15:04:25 -04:00
2024-09-24 15:23:57 -04:00
struct LocalLifecycleScripts < ' a > {
deno_local_registry_dir : & ' a Path ,
}
impl < ' a > LocalLifecycleScripts < ' a > {
/// `node_modules/.deno/<package>/.scripts-run`
fn ran_scripts_file ( & self , package : & NpmResolutionPackage ) -> PathBuf {
local_node_modules_package_folder ( self . deno_local_registry_dir , package )
. join ( " .scripts-run " )
}
/// `node_modules/.deno/<package>/.scripts-warned`
fn warned_scripts_file ( & self , package : & NpmResolutionPackage ) -> PathBuf {
local_node_modules_package_folder ( self . deno_local_registry_dir , package )
. join ( " .scripts-warned " )
}
}
impl < ' a > super ::common ::lifecycle_scripts ::LifecycleScriptsStrategy
for LocalLifecycleScripts < ' a >
{
fn package_path ( & self , package : & NpmResolutionPackage ) -> PathBuf {
local_node_modules_package_contents_path (
self . deno_local_registry_dir ,
package ,
)
}
fn did_run_scripts (
& self ,
package : & NpmResolutionPackage ,
) -> std ::result ::Result < ( ) , deno_core ::anyhow ::Error > {
std ::fs ::write ( self . ran_scripts_file ( package ) , " " ) ? ;
Ok ( ( ) )
}
fn warn_on_scripts_not_run (
& self ,
packages : & [ ( & NpmResolutionPackage , std ::path ::PathBuf ) ] ,
) -> Result < ( ) , AnyError > {
if ! packages . is_empty ( ) {
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 ( " To run lifecycle scripts, use the `--allow-scripts` flag with `deno install`: " ) ) ;
let packages_comma_separated = packages
. iter ( )
. map ( | ( p , _ ) | format! ( " npm: {} " , p . id . nv ) )
. collect ::< Vec < _ > > ( )
. join ( " , " ) ;
log ::warn! (
" {} " ,
colors ::bold ( format! (
" deno install --allow-scripts={} " ,
packages_comma_separated
) )
) ;
for ( package , _ ) in packages {
let _ignore_err = fs ::write ( self . warned_scripts_file ( package ) , " " ) ;
}
2024-07-11 12:41:42 -04:00
}
2024-09-24 15:23:57 -04:00
Ok ( ( ) )
2024-07-09 23:06:08 -04:00
}
2024-09-24 15:23:57 -04:00
fn has_warned ( & self , package : & NpmResolutionPackage ) -> bool {
self . warned_scripts_file ( package ) . exists ( )
}
2023-03-08 10:13:13 -05:00
2024-09-24 15:23:57 -04:00
fn has_run ( & self , package : & NpmResolutionPackage ) -> bool {
self . ran_scripts_file ( package ) . exists ( )
}
2022-09-22 11:17:02 -04:00
}
2024-07-09 14:04:21 -04:00
// Uses BTreeMap to preserve the ordering of the elements in memory, to ensure
// the file generated from this datastructure is deterministic.
// See: https://github.com/denoland/deno/issues/24479
2023-07-10 13:42:47 -04:00
/// Represents a dependency at `node_modules/.deno/<package_id>/`
struct SetupCacheDep < ' a > {
2024-07-09 14:04:21 -04:00
previous : Option < & ' a BTreeMap < String , String > > ,
current : & ' a mut BTreeMap < String , String > ,
2023-07-10 13:42:47 -04:00
}
impl < ' a > SetupCacheDep < ' a > {
pub fn insert ( & mut self , name : & str , target_folder_name : & str ) -> bool {
self
. current
. insert ( name . to_string ( ) , target_folder_name . to_string ( ) ) ;
if let Some ( previous_target ) = self . previous . and_then ( | p | p . get ( name ) ) {
previous_target ! = target_folder_name
} else {
true
}
}
}
2024-07-09 14:04:21 -04:00
// Uses BTreeMap to preserve the ordering of the elements in memory, to ensure
// the file generated from this datastructure is deterministic.
// See: https://github.com/denoland/deno/issues/24479
2023-07-10 13:42:47 -04:00
#[ derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq) ]
struct SetupCacheData {
2024-07-09 14:04:21 -04:00
root_symlinks : BTreeMap < String , String > ,
deno_symlinks : BTreeMap < String , String > ,
dep_symlinks : BTreeMap < String , BTreeMap < String , String > > ,
2023-07-10 13:42:47 -04:00
}
/// It is very slow to try to re-setup the symlinks each time, so this will
/// cache what we've setup on the last run and only update what is necessary.
/// Obviously this could lead to issues if the cache gets out of date with the
/// file system, such as if the user manually deletes a symlink.
struct SetupCache {
file_path : PathBuf ,
previous : Option < SetupCacheData > ,
current : SetupCacheData ,
}
impl SetupCache {
pub fn load ( file_path : PathBuf ) -> Self {
let previous = std ::fs ::read ( & file_path )
. ok ( )
. and_then ( | data | bincode ::deserialize ( & data ) . ok ( ) ) ;
Self {
file_path ,
previous ,
current : Default ::default ( ) ,
}
}
pub fn save ( & self ) -> bool {
if let Some ( previous ) = & self . previous {
if previous = = & self . current {
return false ; // nothing to save
}
}
bincode ::serialize ( & self . current ) . ok ( ) . and_then ( | data | {
2024-06-07 13:06:17 -04:00
atomic_write_file_with_retries ( & self . file_path , data , CACHE_PERM ) . ok ( )
2023-07-10 13:42:47 -04:00
} ) ;
true
}
/// Inserts and checks for the existence of a root symlink
/// at `node_modules/<package_name>` pointing to
/// `node_modules/.deno/<package_id>/`
pub fn insert_root_symlink (
& mut self ,
name : & str ,
target_folder_name : & str ,
) -> bool {
self
. current
. root_symlinks
. insert ( name . to_string ( ) , target_folder_name . to_string ( ) ) ;
if let Some ( previous_target ) = self
. previous
. as_ref ( )
. and_then ( | p | p . root_symlinks . get ( name ) )
{
previous_target ! = target_folder_name
} else {
true
}
}
/// Inserts and checks for the existence of a symlink at
/// `node_modules/.deno/node_modules/<package_name>` pointing to
/// `node_modules/.deno/<package_id>/`
pub fn insert_deno_symlink (
& mut self ,
name : & str ,
target_folder_name : & str ,
) -> bool {
self
. current
. deno_symlinks
. insert ( name . to_string ( ) , target_folder_name . to_string ( ) ) ;
if let Some ( previous_target ) = self
. previous
. as_ref ( )
. and_then ( | p | p . deno_symlinks . get ( name ) )
{
previous_target ! = target_folder_name
} else {
true
}
}
pub fn remove_dep ( & mut self , parent_name : & str ) {
if let Some ( previous ) = & mut self . previous {
previous . dep_symlinks . remove ( parent_name ) ;
}
}
pub fn with_dep ( & mut self , parent_name : & str ) -> SetupCacheDep < '_ > {
SetupCacheDep {
previous : self
. previous
. as_ref ( )
. and_then ( | p | p . dep_symlinks . get ( parent_name ) ) ,
current : self
. current
. dep_symlinks
. entry ( parent_name . to_string ( ) )
. or_default ( ) ,
}
}
}
2023-02-21 12:03:48 -05:00
fn get_package_folder_id_folder_name (
folder_id : & NpmPackageCacheFolderId ,
) -> String {
let copy_str = if folder_id . copy_index = = 0 {
2024-09-04 10:00:44 -04:00
Cow ::Borrowed ( " " )
2022-11-08 14:17:24 -05:00
} else {
2024-09-04 10:00:44 -04:00
Cow ::Owned ( format! ( " _ {} " , folder_id . copy_index ) )
2022-11-08 14:17:24 -05:00
} ;
2023-02-21 12:03:48 -05:00
let nv = & folder_id . nv ;
2024-09-04 10:00:44 -04:00
let name = normalize_pkg_name_for_node_modules_deno_folder ( & nv . name ) ;
format! ( " {} @ {} {} " , name , nv . version , copy_str )
2022-11-08 14:17:24 -05:00
}
2023-07-01 21:07:57 -04:00
fn get_package_folder_id_from_folder_name (
folder_name : & str ,
) -> Option < NpmPackageCacheFolderId > {
let folder_name = folder_name . replace ( '+' , " / " ) ;
let ( name , ending ) = folder_name . rsplit_once ( '@' ) ? ;
let name = if let Some ( encoded_name ) = name . strip_prefix ( '_' ) {
mixed_case_package_name_decode ( encoded_name ) ?
} else {
name . to_string ( )
} ;
let ( raw_version , copy_index ) = match ending . split_once ( '_' ) {
Some ( ( raw_version , copy_index ) ) = > {
let copy_index = copy_index . parse ::< u8 > ( ) . ok ( ) ? ;
( raw_version , copy_index )
}
None = > ( ending , 0 ) ,
} ;
let version = deno_semver ::Version ::parse_from_npm ( raw_version ) . ok ( ) ? ;
Some ( NpmPackageCacheFolderId {
2023-08-21 05:53:52 -04:00
nv : PackageNv { name , version } ,
2023-07-01 21:07:57 -04:00
copy_index ,
} )
}
2022-09-22 11:17:02 -04:00
fn symlink_package_dir (
old_path : & Path ,
new_path : & Path ,
) -> Result < ( ) , AnyError > {
let new_parent = new_path . parent ( ) . unwrap ( ) ;
if new_parent . file_name ( ) . unwrap ( ) ! = " node_modules " {
// create the parent folder that will contain the symlink
fs ::create_dir_all ( new_parent )
. with_context ( | | format! ( " Creating ' {} ' " , new_parent . display ( ) ) ) ? ;
}
// need to delete the previous symlink before creating a new one
let _ignore = fs ::remove_dir_all ( new_path ) ;
2022-09-27 18:02:35 -04:00
2024-08-22 17:55:17 -04:00
let old_path_relative =
crate ::util ::path ::relative_path ( new_parent , old_path )
. unwrap_or_else ( | | old_path . to_path_buf ( ) ) ;
2022-09-27 18:02:35 -04:00
#[ cfg(windows) ]
2024-08-22 17:55:17 -04:00
{
junction_or_symlink_dir ( & old_path_relative , old_path , new_path )
}
2022-09-27 18:02:35 -04:00
#[ cfg(not(windows)) ]
2024-08-22 17:55:17 -04:00
{
symlink_dir ( & old_path_relative , new_path ) . map_err ( Into ::into )
}
2022-09-22 11:17:02 -04:00
}
2022-09-27 18:02:35 -04:00
#[ cfg(windows) ]
fn junction_or_symlink_dir (
2024-08-22 17:55:17 -04:00
old_path_relative : & Path ,
2022-09-27 18:02:35 -04:00
old_path : & Path ,
new_path : & Path ,
) -> Result < ( ) , AnyError > {
2024-08-22 17:55:17 -04:00
static USE_JUNCTIONS : std ::sync ::atomic ::AtomicBool =
std ::sync ::atomic ::AtomicBool ::new ( false ) ;
if USE_JUNCTIONS . load ( std ::sync ::atomic ::Ordering ::Relaxed ) {
// Use junctions because they're supported on ntfs file systems without
// needing to elevate privileges on Windows.
// Note: junctions don't support relative paths, so we need to use the
// absolute path here.
return junction ::create ( old_path , new_path )
. context ( " Failed creating junction in node_modules folder " ) ;
}
2022-11-28 17:28:54 -05:00
2024-08-22 17:55:17 -04:00
match symlink_dir ( old_path_relative , new_path ) {
2022-09-27 18:02:35 -04:00
Ok ( ( ) ) = > Ok ( ( ) ) ,
2024-08-22 17:55:17 -04:00
Err ( symlink_err )
if symlink_err . kind ( ) = = std ::io ::ErrorKind ::PermissionDenied = >
{
USE_JUNCTIONS . store ( true , std ::sync ::atomic ::Ordering ::Relaxed ) ;
2024-11-04 23:16:53 -05:00
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 " )
2022-09-27 18:02:35 -04:00
}
}
}
2022-09-22 11:17:02 -04:00
fn join_package_name ( path : & Path , package_name : & str ) -> PathBuf {
let mut path = path . to_path_buf ( ) ;
// ensure backslashes are used on windows
for part in package_name . split ( '/' ) {
path = path . join ( part ) ;
}
path
}
2023-07-01 21:07:57 -04:00
#[ cfg(test) ]
mod test {
use deno_npm ::NpmPackageCacheFolderId ;
2023-08-21 05:53:52 -04:00
use deno_semver ::package ::PackageNv ;
2023-07-10 13:42:47 -04:00
use test_util ::TempDir ;
2023-07-01 21:07:57 -04:00
use super ::* ;
#[ test ]
fn test_get_package_folder_id_folder_name ( ) {
let cases = vec! [
(
NpmPackageCacheFolderId {
2023-08-21 05:53:52 -04:00
nv : PackageNv ::from_str ( " @types/foo@1.2.3 " ) . unwrap ( ) ,
2023-07-01 21:07:57 -04:00
copy_index : 1 ,
} ,
" @types+foo@1.2.3_1 " . to_string ( ) ,
) ,
(
NpmPackageCacheFolderId {
2023-08-21 05:53:52 -04:00
nv : PackageNv ::from_str ( " JSON@3.2.1 " ) . unwrap ( ) ,
2023-07-01 21:07:57 -04:00
copy_index : 0 ,
} ,
" _jjju6tq@3.2.1 " . to_string ( ) ,
) ,
] ;
for ( input , output ) in cases {
assert_eq! ( get_package_folder_id_folder_name ( & input ) , output ) ;
let folder_id = get_package_folder_id_from_folder_name ( & output ) . unwrap ( ) ;
assert_eq! ( folder_id , input ) ;
}
}
2023-07-10 13:42:47 -04:00
#[ test ]
fn test_setup_cache ( ) {
let temp_dir = TempDir ::new ( ) ;
let cache_bin_path = temp_dir . path ( ) . join ( " cache.bin " ) . to_path_buf ( ) ;
let mut cache = SetupCache ::load ( cache_bin_path . clone ( ) ) ;
assert! ( cache . insert_deno_symlink ( " package-a " , " package-a@1.0.0 " ) ) ;
assert! ( cache . insert_root_symlink ( " package-a " , " package-a@1.0.0 " ) ) ;
assert! ( cache
. with_dep ( " package-a " )
. insert ( " package-b " , " package-b@1.0.0 " ) ) ;
assert! ( cache . save ( ) ) ;
let mut cache = SetupCache ::load ( cache_bin_path . clone ( ) ) ;
assert! ( ! cache . insert_deno_symlink ( " package-a " , " package-a@1.0.0 " ) ) ;
assert! ( ! cache . insert_root_symlink ( " package-a " , " package-a@1.0.0 " ) ) ;
assert! ( ! cache
. with_dep ( " package-a " )
. insert ( " package-b " , " package-b@1.0.0 " ) ) ;
assert! ( ! cache . save ( ) ) ;
assert! ( cache . insert_root_symlink ( " package-b " , " package-b@0.2.0 " ) ) ;
assert! ( cache . save ( ) ) ;
let mut cache = SetupCache ::load ( cache_bin_path ) ;
cache . remove_dep ( " package-a " ) ;
assert! ( cache
. with_dep ( " package-a " )
. insert ( " package-b " , " package-b@1.0.0 " ) ) ;
}
2023-07-01 21:07:57 -04:00
}