2024-01-01 14:58:21 -05:00
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2023-04-06 18:46:44 -04:00
2024-08-28 14:17:47 -04:00
use std ::collections ::HashSet ;
2023-01-23 17:41:02 -05:00
use std ::path ::PathBuf ;
2023-04-06 18:46:44 -04:00
2024-07-23 19:00:48 -04:00
use deno_config ::deno_json ::ConfigFile ;
2024-07-12 15:33:30 -04:00
use deno_config ::workspace ::Workspace ;
2024-05-28 14:58:43 -04:00
use deno_core ::anyhow ::Context ;
2023-04-06 18:46:44 -04:00
use deno_core ::error ::AnyError ;
2024-06-28 20:18:21 -04:00
use deno_core ::parking_lot ::Mutex ;
use deno_core ::parking_lot ::MutexGuard ;
2024-07-23 19:00:48 -04:00
use deno_lockfile ::WorkspaceMemberConfig ;
use deno_package_json ::PackageJsonDepValue ;
use deno_runtime ::deno_node ::PackageJson ;
2024-08-28 14:17:47 -04:00
use deno_semver ::jsr ::JsrDepPackageReq ;
2019-11-03 10:39:27 -05:00
2024-05-28 14:58:43 -04:00
use crate ::cache ;
2024-05-31 23:25:08 -04:00
use crate ::util ::fs ::atomic_write_file_with_retries ;
2022-11-02 11:32:30 -04:00
use crate ::Flags ;
2021-12-07 19:21:04 -05:00
2024-06-28 20:18:21 -04:00
use crate ::args ::DenoSubcommand ;
use crate ::args ::InstallFlags ;
use crate ::args ::InstallKind ;
use deno_lockfile ::Lockfile ;
#[ derive(Debug) ]
pub struct CliLockfile {
lockfile : Mutex < Lockfile > ,
pub filename : PathBuf ,
2024-07-02 18:00:16 -04:00
pub frozen : bool ,
2024-06-28 20:18:21 -04:00
}
pub struct Guard < ' a , T > {
guard : MutexGuard < ' a , T > ,
}
impl < ' a , T > std ::ops ::Deref for Guard < ' a , T > {
type Target = T ;
fn deref ( & self ) -> & Self ::Target {
& self . guard
}
}
impl < ' a , T > std ::ops ::DerefMut for Guard < ' a , T > {
fn deref_mut ( & mut self ) -> & mut Self ::Target {
& mut self . guard
}
}
impl CliLockfile {
2024-07-02 18:00:16 -04:00
pub fn new ( lockfile : Lockfile , frozen : bool ) -> Self {
2024-06-28 20:18:21 -04:00
let filename = lockfile . filename . clone ( ) ;
Self {
lockfile : Mutex ::new ( lockfile ) ,
filename ,
2024-07-02 18:00:16 -04:00
frozen ,
2024-06-28 20:18:21 -04:00
}
}
/// Get the inner deno_lockfile::Lockfile.
pub fn lock ( & self ) -> Guard < Lockfile > {
Guard {
guard : self . lockfile . lock ( ) ,
}
}
pub fn set_workspace_config (
& self ,
options : deno_lockfile ::SetWorkspaceConfigOptions ,
) {
self . lockfile . lock ( ) . set_workspace_config ( options ) ;
}
pub fn overwrite ( & self ) -> bool {
self . lockfile . lock ( ) . overwrite
}
pub fn write_if_changed ( & self ) -> Result < ( ) , AnyError > {
2024-07-02 18:00:16 -04:00
self . error_if_changed ( ) ? ;
2024-06-28 20:18:21 -04:00
let mut lockfile = self . lockfile . lock ( ) ;
let Some ( bytes ) = lockfile . resolve_write_bytes ( ) else {
return Ok ( ( ) ) ; // nothing to do
} ;
// do an atomic write to reduce the chance of multiple deno
// processes corrupting the file
atomic_write_file_with_retries (
& lockfile . filename ,
bytes ,
cache ::CACHE_PERM ,
2023-01-23 17:41:02 -05:00
)
2024-06-28 20:18:21 -04:00
. context ( " Failed writing lockfile. " ) ? ;
lockfile . has_content_changed = false ;
Ok ( ( ) )
2023-01-23 17:41:02 -05:00
}
2024-06-28 20:18:21 -04:00
pub fn discover (
flags : & Flags ,
2024-07-12 15:33:30 -04:00
workspace : & Workspace ,
2024-06-28 20:18:21 -04:00
) -> Result < Option < CliLockfile > , AnyError > {
2024-08-28 14:17:47 -04:00
fn pkg_json_deps (
maybe_pkg_json : Option < & PackageJson > ,
) -> HashSet < JsrDepPackageReq > {
2024-07-23 19:00:48 -04:00
let Some ( pkg_json ) = maybe_pkg_json else {
return Default ::default ( ) ;
} ;
pkg_json
. resolve_local_package_json_deps ( )
. values ( )
. filter_map ( | dep | dep . as_ref ( ) . ok ( ) )
. filter_map ( | dep | match dep {
2024-08-28 14:17:47 -04:00
PackageJsonDepValue ::Req ( req ) = > {
Some ( JsrDepPackageReq ::npm ( req . clone ( ) ) )
}
2024-07-23 19:00:48 -04:00
PackageJsonDepValue ::Workspace ( _ ) = > None ,
} )
. collect ( )
}
fn deno_json_deps (
maybe_deno_json : Option < & ConfigFile > ,
2024-08-28 14:17:47 -04:00
) -> HashSet < JsrDepPackageReq > {
2024-07-23 19:00:48 -04:00
maybe_deno_json
. map ( | c | {
crate ::args ::deno_json ::deno_json_deps ( c )
. into_iter ( )
. collect ( )
} )
. unwrap_or_default ( )
}
2024-06-28 20:18:21 -04:00
if flags . no_lock
| | matches! (
flags . subcommand ,
DenoSubcommand ::Install ( InstallFlags {
kind : InstallKind ::Global ( .. ) ,
..
} ) | DenoSubcommand ::Uninstall ( _ )
)
{
return Ok ( None ) ;
}
let filename = match flags . lock {
Some ( ref lock ) = > PathBuf ::from ( lock ) ,
2024-07-12 15:33:30 -04:00
None = > match workspace . resolve_lockfile_path ( ) ? {
Some ( path ) = > path ,
None = > return Ok ( None ) ,
2024-05-23 15:31:05 -04:00
} ,
2024-06-28 20:18:21 -04:00
} ;
2024-05-28 14:58:43 -04:00
2024-08-20 10:55:47 -04:00
let root_folder = workspace . root_folder_configs ( ) ;
// CLI flag takes precedence over the config
let frozen = flags . frozen_lockfile . unwrap_or_else ( | | {
root_folder
. deno_json
. as_ref ( )
. and_then ( | c | c . to_lock_config ( ) . ok ( ) . flatten ( ) . map ( | c | c . frozen ( ) ) )
. unwrap_or ( false )
} ) ;
2024-08-27 23:23:51 -04:00
let lockfile = Self ::read_from_path ( filename , frozen ) ? ;
2024-07-23 19:00:48 -04:00
// initialize the lockfile with the workspace's configuration
let root_url = workspace . root_dir ( ) ;
let config = deno_lockfile ::WorkspaceConfig {
root : WorkspaceMemberConfig {
package_json_deps : pkg_json_deps ( root_folder . pkg_json . as_deref ( ) ) ,
dependencies : deno_json_deps ( root_folder . deno_json . as_deref ( ) ) ,
} ,
members : workspace
. config_folders ( )
. iter ( )
. filter ( | ( folder_url , _ ) | * folder_url ! = root_url )
. filter_map ( | ( folder_url , folder ) | {
Some ( (
{
// should never be None here, but just ignore members that
// do fail for this
let mut relative_path = root_url . make_relative ( folder_url ) ? ;
if relative_path . ends_with ( '/' ) {
// make it slightly cleaner by removing the trailing slash
relative_path . pop ( ) ;
}
relative_path
} ,
{
let config = WorkspaceMemberConfig {
package_json_deps : pkg_json_deps ( folder . pkg_json . as_deref ( ) ) ,
dependencies : deno_json_deps ( folder . deno_json . as_deref ( ) ) ,
} ;
if config . package_json_deps . is_empty ( )
& & config . dependencies . is_empty ( )
{
// exclude empty workspace members
return None ;
}
config
} ,
) )
} )
. collect ( ) ,
} ;
lockfile . set_workspace_config ( deno_lockfile ::SetWorkspaceConfigOptions {
no_npm : flags . no_npm ,
no_config : flags . config_flag = = super ::ConfigFlag ::Disabled ,
config ,
} ) ;
2024-06-28 20:18:21 -04:00
Ok ( Some ( lockfile ) )
}
2024-08-28 14:17:47 -04:00
2024-07-02 18:00:16 -04:00
pub fn read_from_path (
2024-08-18 23:01:39 -04:00
file_path : PathBuf ,
2024-07-02 18:00:16 -04:00
frozen : bool ,
) -> Result < CliLockfile , AnyError > {
2024-08-18 23:01:39 -04:00
match std ::fs ::read_to_string ( & file_path ) {
2024-07-02 18:00:16 -04:00
Ok ( text ) = > Ok ( CliLockfile ::new (
2024-08-18 23:01:39 -04:00
Lockfile ::new ( deno_lockfile ::NewLockfileOptions {
file_path ,
content : & text ,
overwrite : false ,
} ) ? ,
2024-07-02 18:00:16 -04:00
frozen ,
) ) ,
2024-08-26 19:01:50 -04:00
Err ( err ) if err . kind ( ) = = std ::io ::ErrorKind ::NotFound = > Ok (
CliLockfile ::new ( Lockfile ::new_empty ( file_path , false ) , frozen ) ,
) ,
2024-06-28 20:18:21 -04:00
Err ( err ) = > Err ( err ) . with_context ( | | {
2024-08-18 23:01:39 -04:00
format! ( " Failed reading lockfile ' {} ' " , file_path . display ( ) )
2024-06-28 20:18:21 -04:00
} ) ,
2024-05-28 14:58:43 -04:00
}
}
2024-08-18 23:01:39 -04:00
2024-07-02 18:00:16 -04:00
pub fn error_if_changed ( & self ) -> Result < ( ) , AnyError > {
if ! self . frozen {
return Ok ( ( ) ) ;
}
let lockfile = self . lockfile . lock ( ) ;
if lockfile . has_content_changed {
let contents =
std ::fs ::read_to_string ( & lockfile . filename ) . unwrap_or_default ( ) ;
let new_contents = lockfile . as_json_string ( ) ;
let diff = crate ::util ::diff ::diff ( & contents , & new_contents ) ;
// has an extra newline at the end
let diff = diff . trim_end ( ) ;
Err ( deno_core ::anyhow ::anyhow! (
2024-08-30 17:58:24 -04:00
" The lockfile is out of date. Run `deno cache --frozen=false`, `deno install --frozen=false`, or rerun with `--frozen=false` to update it. \n changes: \n {diff} "
2024-07-02 18:00:16 -04:00
) )
} else {
Ok ( ( ) )
}
}
2024-05-28 14:58:43 -04:00
}