2025-01-01 04:12:39 +09:00
// Copyright 2018-2025 the Deno authors. MIT license.
2022-08-10 15:23:58 -04:00
2022-11-27 13:25:08 -05:00
use std ::collections ::HashSet ;
2024-06-02 21:39:13 -04:00
use std ::io ::ErrorKind ;
2022-09-01 12:37:14 -04:00
use std ::path ::Path ;
2022-08-10 15:23:58 -04:00
use std ::path ::PathBuf ;
2023-05-01 16:42:05 -04:00
use std ::sync ::Arc ;
2022-08-10 15:23:58 -04:00
2024-12-16 18:39:40 -05:00
use deno_cache_dir ::file_fetcher ::CacheSetting ;
2024-09-28 08:50:16 -04:00
use deno_cache_dir ::npm ::NpmCacheDir ;
2025-01-08 14:52:32 -08:00
use deno_error ::JsErrorBox ;
2024-05-23 22:26:23 +01:00
use deno_npm ::npm_rc ::ResolvedNpmRc ;
2024-06-02 21:39:13 -04:00
use deno_npm ::registry ::NpmPackageInfo ;
2023-04-06 18:46:44 -04:00
use deno_npm ::NpmPackageCacheFolderId ;
2024-12-30 12:38:20 -05:00
use deno_path_util ::fs ::atomic_write_file_with_retries ;
2023-08-21 11:53:52 +02:00
use deno_semver ::package ::PackageNv ;
2024-12-20 16:14:37 -05:00
use deno_semver ::StackString ;
2024-09-28 08:50:16 -04:00
use deno_semver ::Version ;
2024-12-02 21:10:16 -05:00
use http ::HeaderName ;
use http ::HeaderValue ;
use http ::StatusCode ;
use parking_lot ::Mutex ;
2024-12-30 12:38:20 -05:00
use sys_traits ::FsCreateDirAll ;
use sys_traits ::FsHardLink ;
use sys_traits ::FsMetadata ;
use sys_traits ::FsOpen ;
use sys_traits ::FsReadDir ;
use sys_traits ::FsRemoveFile ;
use sys_traits ::FsRename ;
use sys_traits ::SystemRandom ;
use sys_traits ::ThreadSleep ;
2024-12-02 21:10:16 -05:00
use url ::Url ;
2022-08-10 15:23:58 -04:00
2024-12-30 12:38:20 -05:00
mod fs_util ;
2024-12-02 21:10:16 -05:00
mod registry_info ;
mod remote ;
2024-06-02 21:39:13 -04:00
mod tarball ;
mod tarball_extract ;
2024-12-30 12:38:20 -05:00
pub use fs_util ::hard_link_dir_recursive ;
2024-12-02 21:10:16 -05:00
// todo(#27198): make both of these private and get the rest of the code
// using RegistryInfoProvider.
pub use registry_info ::get_package_url ;
2024-12-31 12:13:39 -05:00
pub use registry_info ::RegistryInfoProvider ;
2024-12-02 21:10:16 -05:00
pub use remote ::maybe_auth_header_for_npm_registry ;
2025-01-08 14:52:32 -08:00
pub use tarball ::EnsurePackageError ;
2024-12-31 12:13:39 -05:00
pub use tarball ::TarballCache ;
2024-12-02 21:10:16 -05:00
2025-01-08 14:52:32 -08:00
#[ derive(Debug, deno_error::JsError) ]
#[ class(generic) ]
2024-12-02 21:10:16 -05:00
pub struct DownloadError {
pub status_code : Option < StatusCode > ,
2025-01-09 12:10:07 -05:00
pub error : JsErrorBox ,
2024-12-02 21:10:16 -05:00
}
impl std ::error ::Error for DownloadError {
fn source ( & self ) -> Option < & ( dyn std ::error ::Error + 'static ) > {
2024-12-03 19:44:56 -05:00
self . error . source ( )
2024-12-02 21:10:16 -05:00
}
}
impl std ::fmt ::Display for DownloadError {
fn fmt ( & self , f : & mut std ::fmt ::Formatter ) -> std ::fmt ::Result {
self . error . fmt ( f )
}
}
#[ async_trait::async_trait(?Send) ]
2024-12-30 12:38:20 -05:00
pub trait NpmCacheHttpClient : Send + Sync + 'static {
2024-12-02 21:10:16 -05:00
async fn download_with_retries_on_any_tokio_runtime (
& self ,
url : Url ,
maybe_auth_header : Option < ( HeaderName , HeaderValue ) > ,
) -> Result < Option < Vec < u8 > > , DownloadError > ;
}
/// Indicates how cached source files should be handled.
#[ derive(Debug, Clone, Eq, PartialEq) ]
pub enum NpmCacheSetting {
/// Only the cached files should be used. Any files not in the cache will
/// error. This is the equivalent of `--cached-only` in the CLI.
Only ,
/// No cached source files should be used, and all files should be reloaded.
/// This is the equivalent of `--reload` in the CLI.
ReloadAll ,
/// Only some cached resources should be used. This is the equivalent of
/// `--reload=npm:chalk`
ReloadSome { npm_package_names : Vec < String > } ,
/// The cached source files should be used for local modules. This is the
/// default behavior of the CLI.
Use ,
}
impl NpmCacheSetting {
2024-12-16 18:39:40 -05:00
pub fn from_cache_setting ( cache_setting : & CacheSetting ) -> NpmCacheSetting {
match cache_setting {
CacheSetting ::Only = > NpmCacheSetting ::Only ,
CacheSetting ::ReloadAll = > NpmCacheSetting ::ReloadAll ,
CacheSetting ::ReloadSome ( values ) = > {
if values . iter ( ) . any ( | v | v = = " npm: " ) {
NpmCacheSetting ::ReloadAll
} else {
NpmCacheSetting ::ReloadSome {
npm_package_names : values
. iter ( )
. filter_map ( | v | v . strip_prefix ( " npm: " ) )
. map ( | n | n . to_string ( ) )
. collect ( ) ,
}
}
}
CacheSetting ::RespectHeaders = > panic! ( " not supported " ) ,
CacheSetting ::Use = > NpmCacheSetting ::Use ,
}
}
2024-12-02 21:10:16 -05:00
pub fn should_use_for_npm_package ( & self , package_name : & str ) -> bool {
match self {
NpmCacheSetting ::ReloadAll = > false ,
NpmCacheSetting ::ReloadSome { npm_package_names } = > {
! npm_package_names . iter ( ) . any ( | n | n = = package_name )
}
_ = > true ,
}
}
}
2022-08-10 15:23:58 -04:00
/// Stores a single copy of npm packages in a cache.
2023-04-14 16:22:33 -04:00
#[ derive(Debug) ]
2024-12-30 12:38:20 -05:00
pub struct NpmCache <
TSys : FsCreateDirAll
+ FsHardLink
+ FsMetadata
+ FsOpen
+ FsReadDir
+ FsRemoveFile
+ FsRename
+ ThreadSleep
+ SystemRandom ,
> {
2024-11-01 12:27:00 -04:00
cache_dir : Arc < NpmCacheDir > ,
2024-12-30 12:38:20 -05:00
sys : TSys ,
2024-12-02 21:10:16 -05:00
cache_setting : NpmCacheSetting ,
2024-06-02 21:39:13 -04:00
npmrc : Arc < ResolvedNpmRc > ,
2023-08-21 11:53:52 +02:00
previously_reloaded_packages : Mutex < HashSet < PackageNv > > ,
2022-08-22 17:35:04 +02:00
}
2022-08-10 15:23:58 -04:00
2024-12-30 12:38:20 -05:00
impl <
TSys : FsCreateDirAll
+ FsHardLink
+ FsMetadata
+ FsOpen
+ FsReadDir
+ FsRemoveFile
+ FsRename
+ ThreadSleep
+ SystemRandom ,
> NpmCache < TSys >
{
2023-04-26 13:07:15 -04:00
pub fn new (
2024-11-01 12:27:00 -04:00
cache_dir : Arc < NpmCacheDir > ,
2024-12-30 12:38:20 -05:00
sys : TSys ,
2024-12-02 21:10:16 -05:00
cache_setting : NpmCacheSetting ,
2024-05-23 22:26:23 +01:00
npmrc : Arc < ResolvedNpmRc > ,
2022-09-09 21:57:39 +02:00
) -> Self {
2022-09-01 12:37:14 -04:00
Self {
2023-06-08 11:48:29 -04:00
cache_dir ,
2024-12-30 12:38:20 -05:00
sys ,
2022-08-22 17:35:04 +02:00
cache_setting ,
2024-05-23 22:26:23 +01:00
npmrc ,
2024-12-30 12:38:20 -05:00
previously_reloaded_packages : Default ::default ( ) ,
2022-09-01 12:37:14 -04:00
}
2022-08-10 15:23:58 -04:00
}
2024-12-02 21:10:16 -05:00
pub fn cache_setting ( & self ) -> & NpmCacheSetting {
2022-11-27 13:25:08 -05:00
& self . cache_setting
}
2024-11-01 12:27:00 -04:00
pub fn root_dir_path ( & self ) -> & Path {
self . cache_dir . root_dir ( )
}
2023-02-23 10:58:10 -05:00
pub fn root_dir_url ( & self ) -> & Url {
2023-06-08 11:48:29 -04:00
self . cache_dir . root_dir_url ( )
2023-02-23 10:58:10 -05:00
}
2022-11-27 13:25:08 -05:00
/// Checks if the cache should be used for the provided name and version.
/// NOTE: Subsequent calls for the same package will always return `true`
/// to ensure a package is only downloaded once per run of the CLI. This
/// prevents downloads from re-occurring when someone has `--reload` and
/// and imports a dynamic import that imports the same package again for example.
2024-06-02 21:39:13 -04:00
pub fn should_use_cache_for_package ( & self , package : & PackageNv ) -> bool {
2023-02-22 14:15:25 -05:00
self . cache_setting . should_use_for_npm_package ( & package . name )
2022-11-27 13:25:08 -05:00
| | ! self
. previously_reloaded_packages
. lock ( )
2023-02-22 14:15:25 -05:00
. insert ( package . clone ( ) )
2022-11-27 13:25:08 -05:00
}
2022-11-08 14:17:24 -05:00
/// Ensures a copy of the package exists in the global cache.
///
/// This assumes that the original package folder being hard linked
/// from exists before this is called.
pub fn ensure_copy_package (
2022-08-10 15:23:58 -04:00
& self ,
2023-02-22 14:15:25 -05:00
folder_id : & NpmPackageCacheFolderId ,
2025-01-08 14:52:32 -08:00
) -> Result < ( ) , WithFolderSyncLockError > {
2024-05-23 22:26:23 +01:00
let registry_url = self . npmrc . get_registry_url ( & folder_id . nv . name ) ;
2023-02-22 14:15:25 -05:00
assert_ne! ( folder_id . copy_index , 0 ) ;
2024-09-28 08:50:16 -04:00
let package_folder = self . cache_dir . package_folder_for_id (
& folder_id . nv . name ,
& folder_id . nv . version . to_string ( ) ,
folder_id . copy_index ,
registry_url ,
) ;
2022-11-08 14:17:24 -05:00
if package_folder . exists ( )
2024-05-14 14:26:48 -04:00
// if this file exists, then the package didn't successfully initialize
2022-11-08 14:17:24 -05:00
// the first time, or another process is currently extracting the zip file
& & ! package_folder . join ( NPM_PACKAGE_SYNC_LOCK_FILENAME ) . exists ( )
2023-02-22 14:15:25 -05:00
& & self . cache_setting . should_use_for_npm_package ( & folder_id . nv . name )
2022-11-08 14:17:24 -05:00
{
return Ok ( ( ) ) ;
}
2024-09-28 08:50:16 -04:00
let original_package_folder = self . cache_dir . package_folder_for_id (
& folder_id . nv . name ,
& folder_id . nv . version . to_string ( ) ,
0 , // original copy index
registry_url ,
) ;
2024-05-14 14:26:48 -04:00
// it seems Windows does an "AccessDenied" error when moving a
// directory with hard links, so that's why this solution is done
2023-02-22 14:15:25 -05:00
with_folder_sync_lock ( & folder_id . nv , & package_folder , | | {
2024-12-30 12:38:20 -05:00
hard_link_dir_recursive (
& self . sys ,
& original_package_folder ,
& package_folder ,
)
2025-01-08 14:52:32 -08:00
. map_err ( JsErrorBox ::from_err )
2023-02-22 14:15:25 -05:00
} ) ? ;
2022-11-08 14:17:24 -05:00
Ok ( ( ) )
}
2024-05-23 22:26:23 +01:00
pub fn package_folder_for_id ( & self , id : & NpmPackageCacheFolderId ) -> PathBuf {
let registry_url = self . npmrc . get_registry_url ( & id . nv . name ) ;
2024-09-28 08:50:16 -04:00
self . cache_dir . package_folder_for_id (
& id . nv . name ,
& id . nv . version . to_string ( ) ,
id . copy_index ,
registry_url ,
)
2022-11-08 14:17:24 -05:00
}
2024-06-02 21:39:13 -04:00
pub fn package_folder_for_nv ( & self , package : & PackageNv ) -> PathBuf {
let registry_url = self . npmrc . get_registry_url ( & package . name ) ;
self . package_folder_for_nv_and_url ( package , registry_url )
}
pub fn package_folder_for_nv_and_url (
2022-11-08 14:17:24 -05:00
& self ,
2023-08-21 11:53:52 +02:00
package : & PackageNv ,
2024-06-02 21:39:13 -04:00
registry_url : & Url ,
2022-08-10 15:23:58 -04:00
) -> PathBuf {
2024-09-28 08:50:16 -04:00
self . cache_dir . package_folder_for_id (
& package . name ,
& package . version . to_string ( ) ,
0 , // original copy_index
registry_url ,
)
2022-08-10 15:23:58 -04:00
}
2024-05-23 22:26:23 +01:00
pub fn package_name_folder ( & self , name : & str ) -> PathBuf {
let registry_url = self . npmrc . get_registry_url ( name ) ;
2023-06-08 11:48:29 -04:00
self . cache_dir . package_name_folder ( name , registry_url )
2022-08-10 15:23:58 -04:00
}
2022-11-08 14:17:24 -05:00
pub fn resolve_package_folder_id_from_specifier (
2022-08-10 15:23:58 -04:00
& self ,
2024-12-02 21:10:16 -05:00
specifier : & Url ,
2023-07-17 14:00:44 -04:00
) -> Option < NpmPackageCacheFolderId > {
2022-08-10 15:23:58 -04:00
self
2023-06-08 11:48:29 -04:00
. cache_dir
2024-05-23 22:26:23 +01:00
. resolve_package_folder_id_from_specifier ( specifier )
2024-09-28 08:50:16 -04:00
. and_then ( | cache_id | {
Some ( NpmPackageCacheFolderId {
nv : PackageNv {
2024-12-20 16:14:37 -05:00
name : StackString ::from_string ( cache_id . name ) ,
2024-09-28 08:50:16 -04:00
version : Version ::parse_from_npm ( & cache_id . version ) . ok ( ) ? ,
} ,
copy_index : cache_id . copy_index ,
} )
} )
2022-08-10 15:23:58 -04:00
}
2024-06-02 21:39:13 -04:00
pub fn load_package_info (
& self ,
name : & str ,
2025-01-08 14:52:32 -08:00
) -> Result < Option < NpmPackageInfo > , serde_json ::Error > {
2024-06-02 21:39:13 -04:00
let file_cache_path = self . get_registry_package_info_file_cache_path ( name ) ;
2024-12-02 21:10:16 -05:00
let file_text = match std ::fs ::read_to_string ( file_cache_path ) {
2024-06-02 21:39:13 -04:00
Ok ( file_text ) = > file_text ,
Err ( err ) if err . kind ( ) = = ErrorKind ::NotFound = > return Ok ( None ) ,
2025-01-08 14:52:32 -08:00
Err ( err ) = > return Err ( serde_json ::Error ::io ( err ) ) ,
2024-06-02 21:39:13 -04:00
} ;
2025-01-08 14:52:32 -08:00
serde_json ::from_str ( & file_text )
2024-06-02 21:39:13 -04:00
}
pub fn save_package_info (
& self ,
name : & str ,
package_info : & NpmPackageInfo ,
2025-01-09 12:10:07 -05:00
) -> Result < ( ) , JsErrorBox > {
2024-06-02 21:39:13 -04:00
let file_cache_path = self . get_registry_package_info_file_cache_path ( name ) ;
2025-01-09 12:10:07 -05:00
let file_text =
serde_json ::to_string ( & package_info ) . map_err ( JsErrorBox ::from_err ) ? ;
2024-12-30 12:38:20 -05:00
atomic_write_file_with_retries (
& self . sys ,
& file_cache_path ,
file_text . as_bytes ( ) ,
0o644 ,
2025-01-09 12:10:07 -05:00
)
. map_err ( JsErrorBox ::from_err ) ? ;
2024-06-02 21:39:13 -04:00
Ok ( ( ) )
}
fn get_registry_package_info_file_cache_path ( & self , name : & str ) -> PathBuf {
let name_folder_path = self . package_name_folder ( name ) ;
name_folder_path . join ( " registry.json " )
}
2022-08-10 15:23:58 -04:00
}
2022-08-14 09:09:16 -04:00
2023-10-02 17:53:55 -04:00
const NPM_PACKAGE_SYNC_LOCK_FILENAME : & str = " .deno_sync_lock " ;
2022-08-14 09:09:16 -04:00
2025-01-08 14:52:32 -08:00
#[ derive(Debug, thiserror::Error, deno_error::JsError) ]
pub enum WithFolderSyncLockError {
#[ class(inherit) ]
#[ error( " Error creating '{path}' " ) ]
CreateDir {
path : PathBuf ,
#[ source ]
#[ inherit ]
source : std ::io ::Error ,
} ,
#[ class(inherit) ]
#[ error( " Error creating package sync lock file at '{path}'. Maybe try manually deleting this folder. " ) ]
CreateLockFile {
path : PathBuf ,
#[ source ]
#[ inherit ]
source : std ::io ::Error ,
} ,
#[ class(inherit) ]
#[ error(transparent) ]
Action ( #[ from ] JsErrorBox ) ,
#[ class(generic) ]
#[ error( " Failed setting up package cache directory for {package}, then failed cleaning it up. \n \n Original error: \n \n {error} \n \n Remove error: \n \n {remove_error} \n \n Please manually delete this folder or you will run into issues using this package in the future: \n \n {output_folder} " ) ]
SetUpPackageCacheDir {
package : Box < PackageNv > ,
error : Box < WithFolderSyncLockError > ,
remove_error : std ::io ::Error ,
output_folder : PathBuf ,
} ,
}
2024-12-30 12:38:20 -05:00
// todo(dsherret): use `sys` here instead of `std::fs`.
2024-05-14 14:26:48 -04:00
fn with_folder_sync_lock (
2023-10-02 17:53:55 -04:00
package : & PackageNv ,
output_folder : & Path ,
2025-01-08 14:52:32 -08:00
action : impl FnOnce ( ) -> Result < ( ) , JsErrorBox > ,
) -> Result < ( ) , WithFolderSyncLockError > {
2023-10-02 17:53:55 -04:00
fn inner (
output_folder : & Path ,
2025-01-08 14:52:32 -08:00
action : impl FnOnce ( ) -> Result < ( ) , JsErrorBox > ,
) -> Result < ( ) , WithFolderSyncLockError > {
std ::fs ::create_dir_all ( output_folder ) . map_err ( | source | {
WithFolderSyncLockError ::CreateDir {
path : output_folder . to_path_buf ( ) ,
source ,
}
2023-10-02 17:53:55 -04:00
} ) ? ;
2022-11-16 13:44:31 -05:00
2023-10-02 17:53:55 -04:00
// This sync lock file is a way to ensure that partially created
// npm package directories aren't considered valid. This could maybe
// be a bit smarter in the future to not bother extracting here
// if another process has taken the lock in the past X seconds and
// wait for the other process to finish (it could try to create the
// file with `create_new(true)` then if it exists, check the metadata
// then wait until the other process finishes with a timeout), but
// for now this is good enough.
let sync_lock_path = output_folder . join ( NPM_PACKAGE_SYNC_LOCK_FILENAME ) ;
2024-12-02 21:10:16 -05:00
match std ::fs ::OpenOptions ::new ( )
2023-10-02 17:53:55 -04:00
. write ( true )
. create ( true )
2024-04-11 06:08:23 +08:00
. truncate ( false )
2023-10-02 17:53:55 -04:00
. open ( & sync_lock_path )
{
Ok ( _ ) = > {
action ( ) ? ;
// extraction succeeded, so only now delete this file
let _ignore = std ::fs ::remove_file ( & sync_lock_path ) ;
Ok ( ( ) )
}
2025-01-08 14:52:32 -08:00
Err ( err ) = > Err ( WithFolderSyncLockError ::CreateLockFile {
path : output_folder . to_path_buf ( ) ,
source : err ,
} ) ,
2023-10-02 17:53:55 -04:00
}
}
2022-11-16 13:44:31 -05:00
2023-10-02 17:53:55 -04:00
match inner ( output_folder , action ) {
Ok ( ( ) ) = > Ok ( ( ) ) ,
Err ( err ) = > {
2024-12-02 21:10:16 -05:00
if let Err ( remove_err ) = std ::fs ::remove_dir_all ( output_folder ) {
2023-10-02 17:53:55 -04:00
if remove_err . kind ( ) ! = std ::io ::ErrorKind ::NotFound {
2025-01-08 14:52:32 -08:00
return Err ( WithFolderSyncLockError ::SetUpPackageCacheDir {
package : Box ::new ( package . clone ( ) ) ,
error : Box ::new ( err ) ,
remove_error : remove_err ,
output_folder : output_folder . to_path_buf ( ) ,
} ) ;
2023-10-02 17:53:55 -04:00
}
}
Err ( err )
}
2022-08-14 09:09:16 -04:00
}
}