2024-02-29 14:12:04 -05:00
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2024-11-01 22:10:35 -04:00
use std ::path ::Path ;
2024-02-29 14:12:04 -05:00
use std ::path ::PathBuf ;
2024-03-01 16:34:13 -05:00
use std ::sync ::Arc ;
2024-02-29 14:12:04 -05:00
use deno_core ::anyhow ::bail ;
use deno_core ::anyhow ::Context ;
use deno_core ::error ::AnyError ;
use deno_core ::futures ::FutureExt ;
use deno_core ::futures ::StreamExt ;
2024-10-21 14:17:08 -04:00
use deno_path_util ::url_to_file_path ;
use deno_semver ::jsr ::JsrPackageReqReference ;
use deno_semver ::npm ::NpmPackageReqReference ;
2024-06-10 19:56:43 -04:00
use deno_semver ::package ::PackageReq ;
2024-10-21 14:17:08 -04:00
use deno_semver ::VersionReq ;
use jsonc_parser ::cst ::CstObject ;
use jsonc_parser ::cst ::CstObjectProp ;
use jsonc_parser ::cst ::CstRootNode ;
use jsonc_parser ::json ;
2024-02-29 14:12:04 -05:00
use crate ::args ::AddFlags ;
use crate ::args ::CacheSetting ;
2024-09-06 13:18:13 -04:00
use crate ::args ::CliOptions ;
2024-02-29 14:12:04 -05:00
use crate ::args ::Flags ;
2024-08-12 16:17:25 -04:00
use crate ::args ::RemoveFlags ;
2024-02-29 14:12:04 -05:00
use crate ::factory ::CliFactory ;
use crate ::file_fetcher ::FileFetcher ;
2024-03-01 16:34:13 -05:00
use crate ::jsr ::JsrFetchResolver ;
2024-03-06 08:24:15 -05:00
use crate ::npm ::NpmFetchResolver ;
2024-02-29 14:12:04 -05:00
2024-10-21 14:17:08 -04:00
mod cache_deps ;
2024-05-08 15:34:46 -04:00
2024-10-21 14:17:08 -04:00
pub use cache_deps ::cache_top_level_deps ;
2024-05-08 15:34:46 -04:00
2024-10-21 14:17:08 -04:00
#[ derive(Debug, Copy, Clone) ]
enum ConfigKind {
DenoJson ,
PackageJson ,
2024-09-06 13:18:13 -04:00
}
2024-10-21 14:17:08 -04:00
struct ConfigUpdater {
kind : ConfigKind ,
cst : CstRootNode ,
root_object : CstObject ,
path : PathBuf ,
modified : bool ,
2024-09-06 13:18:13 -04:00
}
2024-10-21 14:17:08 -04:00
impl ConfigUpdater {
fn new (
kind : ConfigKind ,
config_file_path : PathBuf ,
) -> Result < Self , AnyError > {
let config_file_contents = std ::fs ::read_to_string ( & config_file_path )
. with_context ( | | {
format! ( " Reading config file ' {} ' " , config_file_path . display ( ) )
} ) ? ;
let cst = CstRootNode ::parse ( & config_file_contents , & Default ::default ( ) )
. with_context ( | | {
format! ( " Parsing config file ' {} ' " , config_file_path . display ( ) )
} ) ? ;
let root_object = cst . object_value_or_set ( ) ;
Ok ( Self {
kind ,
cst ,
root_object ,
path : config_file_path ,
modified : false ,
} )
2024-09-06 13:18:13 -04:00
}
2024-10-21 14:17:08 -04:00
fn display_path ( & self ) -> String {
deno_path_util ::url_from_file_path ( & self . path )
. map ( | u | u . to_string ( ) )
. unwrap_or_else ( | _ | self . path . display ( ) . to_string ( ) )
2024-09-06 13:18:13 -04:00
}
2024-10-21 14:17:08 -04:00
fn obj ( & self ) -> & CstObject {
& self . root_object
2024-09-06 13:18:13 -04:00
}
2024-10-21 14:17:08 -04:00
fn contents ( & self ) -> String {
self . cst . to_string ( )
2024-09-06 13:18:13 -04:00
}
fn add ( & mut self , selected : SelectedPackage , dev : bool ) {
2024-10-21 14:17:08 -04:00
fn insert_index ( object : & CstObject , searching_name : & str ) -> usize {
object
. properties ( )
. into_iter ( )
. take_while ( | prop | {
let prop_name =
prop . name ( ) . and_then ( | name | name . decoded_value ( ) . ok ( ) ) ;
match prop_name {
Some ( current_name ) = > {
searching_name . cmp ( & current_name ) = = std ::cmp ::Ordering ::Greater
}
None = > true ,
}
} )
. count ( )
2024-09-06 13:18:13 -04:00
}
2024-10-21 14:17:08 -04:00
match self . kind {
ConfigKind ::DenoJson = > {
let imports = self . root_object . object_value_or_set ( " imports " ) ;
let value =
format! ( " {} @ {} " , selected . package_name , selected . version_req ) ;
if let Some ( prop ) = imports . get ( & selected . import_name ) {
prop . set_value ( json! ( value ) ) ;
} else {
let index = insert_index ( & imports , & selected . import_name ) ;
imports . insert ( index , & selected . import_name , json! ( value ) ) ;
}
}
ConfigKind ::PackageJson = > {
let deps_prop = self . root_object . get ( " dependencies " ) ;
let dev_deps_prop = self . root_object . get ( " devDependencies " ) ;
let dependencies = if dev {
self
. root_object
. object_value ( " devDependencies " )
. unwrap_or_else ( | | {
let index = deps_prop
. as_ref ( )
. map ( | p | p . property_index ( ) + 1 )
. unwrap_or_else ( | | self . root_object . properties ( ) . len ( ) ) ;
self
. root_object
. insert ( index , " devDependencies " , json! ( { } ) )
. object_value_or_set ( )
} )
} else {
self
. root_object
. object_value ( " dependencies " )
. unwrap_or_else ( | | {
let index = dev_deps_prop
. as_ref ( )
. map ( | p | p . property_index ( ) )
. unwrap_or_else ( | | self . root_object . properties ( ) . len ( ) ) ;
self
. root_object
. insert ( index , " dependencies " , json! ( { } ) )
. object_value_or_set ( )
} )
} ;
let other_dependencies = if dev {
deps_prop . and_then ( | p | p . value ( ) . and_then ( | v | v . as_object ( ) ) )
} else {
dev_deps_prop . and_then ( | p | p . value ( ) . and_then ( | v | v . as_object ( ) ) )
} ;
2024-09-06 13:18:13 -04:00
2024-10-21 14:17:08 -04:00
let ( alias , value ) = package_json_dependency_entry ( selected ) ;
2024-09-06 13:18:13 -04:00
2024-10-21 14:17:08 -04:00
if let Some ( other ) = other_dependencies {
if let Some ( prop ) = other . get ( & alias ) {
remove_prop_and_maybe_parent_prop ( prop ) ;
}
}
2024-09-06 13:18:13 -04:00
2024-10-21 14:17:08 -04:00
if let Some ( prop ) = dependencies . get ( & alias ) {
prop . set_value ( json! ( value ) ) ;
} else {
let index = insert_index ( & dependencies , & alias ) ;
dependencies . insert ( index , & alias , json! ( value ) ) ;
}
2024-09-06 13:18:13 -04:00
}
2024-05-08 15:34:46 -04:00
}
2024-10-21 14:17:08 -04:00
2024-09-06 13:18:13 -04:00
self . modified = true ;
2024-05-08 15:34:46 -04:00
}
2024-09-06 13:18:13 -04:00
fn remove ( & mut self , package : & str ) -> bool {
2024-10-21 14:17:08 -04:00
let removed = match self . kind {
ConfigKind ::DenoJson = > {
if let Some ( prop ) = self
. root_object
. object_value ( " imports " )
. and_then ( | i | i . get ( package ) )
{
remove_prop_and_maybe_parent_prop ( prop ) ;
true
} else {
false
}
}
ConfigKind ::PackageJson = > {
let deps = [
self
. root_object
. object_value ( " dependencies " )
. and_then ( | deps | deps . get ( package ) ) ,
self
. root_object
. object_value ( " devDependencies " )
. and_then ( | deps | deps . get ( package ) ) ,
] ;
let removed = deps . iter ( ) . any ( | d | d . is_some ( ) ) ;
for dep in deps . into_iter ( ) . flatten ( ) {
remove_prop_and_maybe_parent_prop ( dep ) ;
}
removed
}
2024-09-06 13:18:13 -04:00
} ;
if removed {
self . modified = true ;
}
removed
}
2024-10-21 14:17:08 -04:00
fn commit ( & self ) -> Result < ( ) , AnyError > {
2024-09-06 13:18:13 -04:00
if ! self . modified {
return Ok ( ( ) ) ;
}
2024-10-21 14:17:08 -04:00
let new_text = self . contents ( ) ;
std ::fs ::write ( & self . path , new_text ) . with_context ( | | {
format! ( " failed writing to ' {} ' " , self . path . display ( ) )
} ) ? ;
2024-09-06 13:18:13 -04:00
Ok ( ( ) )
}
}
2024-10-21 14:17:08 -04:00
fn remove_prop_and_maybe_parent_prop ( prop : CstObjectProp ) {
let parent = prop . parent ( ) . unwrap ( ) . as_object ( ) . unwrap ( ) ;
prop . remove ( ) ;
if parent . properties ( ) . is_empty ( ) {
let parent_property = parent . parent ( ) . unwrap ( ) ;
let root_object = parent_property . parent ( ) . unwrap ( ) . as_object ( ) . unwrap ( ) ;
// remove the property
parent_property . remove ( ) ;
root_object . ensure_multiline ( ) ;
2024-05-08 15:34:46 -04:00
}
2024-09-06 13:18:13 -04:00
}
2024-05-08 15:34:46 -04:00
2024-09-06 13:18:13 -04:00
fn create_deno_json (
flags : & Arc < Flags > ,
options : & CliOptions ,
) -> Result < CliFactory , AnyError > {
std ::fs ::write ( options . initial_cwd ( ) . join ( " deno.json " ) , " {} \n " )
. context ( " Failed to create deno.json file " ) ? ;
log ::info! ( " Created deno.json configuration file. " ) ;
let factory = CliFactory ::from_flags ( flags . clone ( ) ) ;
Ok ( factory )
2024-05-08 15:34:46 -04:00
}
fn package_json_dependency_entry (
selected : SelectedPackage ,
) -> ( String , String ) {
if let Some ( npm_package ) = selected . package_name . strip_prefix ( " npm: " ) {
2024-10-14 15:35:52 -04:00
if selected . import_name = = npm_package {
( npm_package . into ( ) , selected . version_req )
} else {
(
selected . import_name ,
format! ( " npm: {} @ {} " , npm_package , selected . version_req ) ,
)
}
2024-05-08 15:34:46 -04:00
} else if let Some ( jsr_package ) = selected . package_name . strip_prefix ( " jsr: " ) {
let jsr_package = jsr_package . strip_prefix ( '@' ) . unwrap_or ( jsr_package ) ;
let scope_replaced = jsr_package . replace ( '/' , " __ " ) ;
let version_req =
format! ( " npm:@jsr/ {scope_replaced} @ {} " , selected . version_req ) ;
( selected . import_name , version_req )
} else {
( selected . package_name , selected . version_req )
}
}
2024-08-09 10:29:11 -04:00
#[ derive(Clone, Copy) ]
/// The name of the subcommand invoking the `add` operation.
pub enum AddCommandName {
Add ,
Install ,
}
impl std ::fmt ::Display for AddCommandName {
fn fmt ( & self , f : & mut std ::fmt ::Formatter < '_ > ) -> std ::fmt ::Result {
match self {
AddCommandName ::Add = > write! ( f , " add " ) ,
AddCommandName ::Install = > write! ( f , " install " ) ,
}
}
}
2024-09-06 13:18:13 -04:00
fn load_configs (
flags : & Arc < Flags > ,
2024-10-15 12:38:42 -04:00
has_jsr_specifiers : impl FnOnce ( ) -> bool ,
2024-10-21 14:17:08 -04:00
) -> Result < ( CliFactory , Option < ConfigUpdater > , Option < ConfigUpdater > ) , AnyError >
{
2024-09-06 13:18:13 -04:00
let cli_factory = CliFactory ::from_flags ( flags . clone ( ) ) ;
let options = cli_factory . cli_options ( ) ? ;
2024-10-21 14:17:08 -04:00
let start_dir = & options . start_dir ;
let npm_config = match start_dir . maybe_pkg_json ( ) {
Some ( pkg_json ) = > Some ( ConfigUpdater ::new (
ConfigKind ::PackageJson ,
pkg_json . path . clone ( ) ,
) ? ) ,
None = > None ,
} ;
let deno_config = match start_dir . maybe_deno_json ( ) {
Some ( deno_json ) = > Some ( ConfigUpdater ::new (
ConfigKind ::DenoJson ,
url_to_file_path ( & deno_json . specifier ) ? ,
) ? ) ,
None = > None ,
} ;
let ( cli_factory , deno_config ) = match deno_config {
2024-09-06 13:18:13 -04:00
Some ( config ) = > ( cli_factory , Some ( config ) ) ,
2024-10-15 12:38:42 -04:00
None if npm_config . is_some ( ) & & ! has_jsr_specifiers ( ) = > {
( cli_factory , None )
}
_ = > {
2024-09-06 13:18:13 -04:00
let factory = create_deno_json ( flags , options ) ? ;
let options = factory . cli_options ( ) ? . clone ( ) ;
2024-10-21 14:17:08 -04:00
let deno_json = options
. start_dir
. maybe_deno_json ( )
. expect ( " Just created deno.json " ) ;
2024-09-06 13:18:13 -04:00
(
factory ,
2024-10-21 14:17:08 -04:00
Some ( ConfigUpdater ::new (
ConfigKind ::DenoJson ,
url_to_file_path ( & deno_json . specifier ) ? ,
) ? ) ,
2024-09-06 13:18:13 -04:00
)
}
} ;
assert! ( deno_config . is_some ( ) | | npm_config . is_some ( ) ) ;
Ok ( ( cli_factory , npm_config , deno_config ) )
}
2024-11-01 22:10:35 -04:00
fn path_distance ( a : & Path , b : & Path ) -> usize {
let diff = pathdiff ::diff_paths ( a , b ) ;
let Some ( diff ) = diff else {
return usize ::MAX ;
} ;
diff . components ( ) . count ( )
}
2024-07-23 19:00:48 -04:00
pub async fn add (
flags : Arc < Flags > ,
add_flags : AddFlags ,
2024-08-09 10:29:11 -04:00
cmd_name : AddCommandName ,
2024-07-23 19:00:48 -04:00
) -> Result < ( ) , AnyError > {
2024-10-21 14:17:08 -04:00
let ( cli_factory , mut npm_config , mut deno_config ) =
load_configs ( & flags , | | {
add_flags . packages . iter ( ) . any ( | s | s . starts_with ( " jsr: " ) )
} ) ? ;
2024-09-06 13:18:13 -04:00
if let Some ( deno ) = & deno_config {
2024-10-21 14:17:08 -04:00
if deno . obj ( ) . get ( " importMap " ) . is_some ( ) {
2024-09-06 13:18:13 -04:00
bail! (
concat! (
" `deno {}` is not supported when configuration file contains an \" importMap \" field. " ,
" Inline the import map into the Deno configuration file. \n " ,
" at {} " ,
) ,
cmd_name ,
2024-10-21 14:17:08 -04:00
deno . display_path ( ) ,
2024-09-06 13:18:13 -04:00
) ;
}
2024-02-29 14:12:04 -05:00
}
2024-11-01 22:10:35 -04:00
let start_dir = cli_factory . cli_options ( ) ? . start_dir . dir_path ( ) ;
// only prefer to add npm deps to `package.json` if there isn't a closer deno.json.
// example: if deno.json is in the CWD and package.json is in the parent, we should add
// npm deps to deno.json, since it's closer
let prefer_npm_config = match ( npm_config . as_ref ( ) , deno_config . as_ref ( ) ) {
( Some ( npm ) , Some ( deno ) ) = > {
let npm_distance = path_distance ( & npm . path , & start_dir ) ;
let deno_distance = path_distance ( & deno . path , & start_dir ) ;
npm_distance < = deno_distance
}
( Some ( _ ) , None ) = > true ,
( None , _ ) = > false ,
} ;
2024-06-03 17:17:08 -04:00
let http_client = cli_factory . http_client_provider ( ) ;
2024-02-29 14:12:04 -05:00
let deps_http_cache = cli_factory . global_http_cache ( ) ? ;
let mut deps_file_fetcher = FileFetcher ::new (
deps_http_cache . clone ( ) ,
CacheSetting ::ReloadAll ,
true ,
http_client . clone ( ) ,
Default ::default ( ) ,
None ,
) ;
2024-10-24 14:03:56 -04:00
let npmrc = cli_factory . cli_options ( ) . unwrap ( ) . npmrc ( ) ;
2024-02-29 14:12:04 -05:00
deps_file_fetcher . set_download_log_level ( log ::Level ::Trace ) ;
2024-06-03 17:17:08 -04:00
let deps_file_fetcher = Arc ::new ( deps_file_fetcher ) ;
2024-03-06 08:24:15 -05:00
let jsr_resolver = Arc ::new ( JsrFetchResolver ::new ( deps_file_fetcher . clone ( ) ) ) ;
2024-10-24 14:03:56 -04:00
let npm_resolver =
Arc ::new ( NpmFetchResolver ::new ( deps_file_fetcher , npmrc . clone ( ) ) ) ;
2024-02-29 14:12:04 -05:00
2024-09-18 14:38:22 -04:00
let mut selected_packages = Vec ::with_capacity ( add_flags . packages . len ( ) ) ;
let mut package_reqs = Vec ::with_capacity ( add_flags . packages . len ( ) ) ;
for entry_text in add_flags . packages . iter ( ) {
2024-10-16 05:20:41 -04:00
let req = AddRmPackageReq ::parse ( entry_text ) . with_context ( | | {
2024-09-18 14:38:22 -04:00
format! ( " Failed to parse package required: {} " , entry_text )
} ) ? ;
match req {
Ok ( add_req ) = > package_reqs . push ( add_req ) ,
Err ( package_req ) = > {
if jsr_resolver . req_to_nv ( & package_req ) . await . is_some ( ) {
bail! (
" {entry_text} is missing a prefix. Did you mean `{}`? " ,
crate ::colors ::yellow ( format! ( " deno {cmd_name} jsr: {package_req} " ) )
)
} else if npm_resolver . req_to_nv ( & package_req ) . await . is_some ( ) {
bail! (
" {entry_text} is missing a prefix. Did you mean `{}`? " ,
crate ::colors ::yellow ( format! ( " deno {cmd_name} npm: {package_req} " ) )
)
} else {
bail! (
" {} was not found in either jsr or npm. " ,
crate ::colors ::red ( entry_text )
) ;
}
}
}
}
2024-02-29 14:12:04 -05:00
let package_futures = package_reqs
. into_iter ( )
2024-08-21 18:23:32 -04:00
. map ( {
let jsr_resolver = jsr_resolver . clone ( ) ;
move | package_req | {
find_package_and_select_version_for_req (
jsr_resolver . clone ( ) ,
npm_resolver . clone ( ) ,
package_req ,
)
. boxed_local ( )
}
2024-02-29 14:12:04 -05:00
} )
. collect ::< Vec < _ > > ( ) ;
let stream_of_futures = deno_core ::futures ::stream ::iter ( package_futures ) ;
2024-08-18 17:02:32 -04:00
let mut buffered = stream_of_futures . buffered ( 10 ) ;
2024-02-29 14:12:04 -05:00
while let Some ( package_and_version_result ) = buffered . next ( ) . await {
let package_and_version = package_and_version_result ? ;
match package_and_version {
2024-08-09 10:29:11 -04:00
PackageAndVersion ::NotFound {
package : package_name ,
found_npm_package ,
package_req ,
} = > {
if found_npm_package {
bail! ( " {} was not found, but a matching npm package exists. Did you mean `{}`? " , crate ::colors ::red ( package_name ) , crate ::colors ::yellow ( format! ( " deno {cmd_name} npm: {package_req} " ) ) ) ;
} else {
bail! ( " {} was not found. " , crate ::colors ::red ( package_name ) ) ;
}
2024-02-29 14:12:04 -05:00
}
PackageAndVersion ::Selected ( selected ) = > {
selected_packages . push ( selected ) ;
}
}
}
2024-09-07 05:22:27 -04:00
let dev = add_flags . dev ;
2024-02-29 14:12:04 -05:00
for selected_package in selected_packages {
log ::info! (
2024-08-08 10:25:05 -04:00
" Add {}{}{} " ,
crate ::colors ::green ( & selected_package . package_name ) ,
crate ::colors ::gray ( " @ " ) ,
selected_package . selected_version
2024-02-29 14:12:04 -05:00
) ;
2024-05-08 15:34:46 -04:00
2024-11-01 22:10:35 -04:00
if selected_package . package_name . starts_with ( " npm: " ) & & prefer_npm_config {
2024-09-06 13:18:13 -04:00
if let Some ( npm ) = & mut npm_config {
2024-09-07 05:22:27 -04:00
npm . add ( selected_package , dev ) ;
2024-09-06 13:18:13 -04:00
} else {
2024-09-07 05:22:27 -04:00
deno_config . as_mut ( ) . unwrap ( ) . add ( selected_package , dev ) ;
2024-09-06 13:18:13 -04:00
}
} else if let Some ( deno ) = & mut deno_config {
2024-09-07 05:22:27 -04:00
deno . add ( selected_package , dev ) ;
2024-05-08 15:34:46 -04:00
} else {
2024-09-07 05:22:27 -04:00
npm_config . as_mut ( ) . unwrap ( ) . add ( selected_package , dev ) ;
2024-09-06 13:18:13 -04:00
}
2024-02-29 14:12:04 -05:00
}
2024-09-06 13:18:13 -04:00
if let Some ( npm ) = npm_config {
2024-10-21 14:17:08 -04:00
npm . commit ( ) ? ;
2024-09-06 13:18:13 -04:00
}
if let Some ( deno ) = deno_config {
2024-10-21 14:17:08 -04:00
deno . commit ( ) ? ;
2024-09-06 13:18:13 -04:00
}
2024-02-29 14:12:04 -05:00
2024-10-04 03:52:00 -04:00
npm_install_after_modification ( flags , Some ( jsr_resolver ) ) . await ? ;
2024-02-29 14:12:04 -05:00
Ok ( ( ) )
}
struct SelectedPackage {
import_name : String ,
package_name : String ,
version_req : String ,
2024-08-08 10:25:05 -04:00
selected_version : String ,
2024-02-29 14:12:04 -05:00
}
enum PackageAndVersion {
2024-08-09 10:29:11 -04:00
NotFound {
package : String ,
found_npm_package : bool ,
package_req : PackageReq ,
} ,
2024-02-29 14:12:04 -05:00
Selected ( SelectedPackage ) ,
}
async fn find_package_and_select_version_for_req (
2024-03-01 16:34:13 -05:00
jsr_resolver : Arc < JsrFetchResolver > ,
2024-03-06 08:24:15 -05:00
npm_resolver : Arc < NpmFetchResolver > ,
2024-10-16 05:20:41 -04:00
add_package_req : AddRmPackageReq ,
2024-02-29 14:12:04 -05:00
) -> Result < PackageAndVersion , AnyError > {
2024-06-10 19:56:43 -04:00
match add_package_req . value {
2024-10-16 05:20:41 -04:00
AddRmPackageReqValue ::Jsr ( req ) = > {
2024-03-01 16:34:13 -05:00
let jsr_prefixed_name = format! ( " jsr: {} " , & req . name ) ;
2024-06-10 19:56:43 -04:00
let Some ( nv ) = jsr_resolver . req_to_nv ( & req ) . await else {
2024-08-09 10:29:11 -04:00
if npm_resolver . req_to_nv ( & req ) . await . is_some ( ) {
return Ok ( PackageAndVersion ::NotFound {
package : jsr_prefixed_name ,
found_npm_package : true ,
package_req : req ,
} ) ;
}
return Ok ( PackageAndVersion ::NotFound {
package : jsr_prefixed_name ,
found_npm_package : false ,
package_req : req ,
} ) ;
2024-03-01 16:34:13 -05:00
} ;
let range_symbol = if req . version_req . version_text ( ) . starts_with ( '~' ) {
2024-10-16 12:34:33 -04:00
" ~ "
} else if req . version_req . version_text ( ) = = nv . version . to_string ( ) {
" "
2024-03-01 16:34:13 -05:00
} else {
2024-10-16 12:34:33 -04:00
" ^ "
2024-03-01 16:34:13 -05:00
} ;
Ok ( PackageAndVersion ::Selected ( SelectedPackage {
2024-06-10 19:56:43 -04:00
import_name : add_package_req . alias ,
2024-03-01 16:34:13 -05:00
package_name : jsr_prefixed_name ,
version_req : format ! ( " {}{} " , range_symbol , & nv . version ) ,
2024-08-08 10:25:05 -04:00
selected_version : nv . version . to_string ( ) ,
2024-03-01 16:34:13 -05:00
} ) )
2024-02-29 14:12:04 -05:00
}
2024-10-16 05:20:41 -04:00
AddRmPackageReqValue ::Npm ( req ) = > {
2024-03-06 08:24:15 -05:00
let npm_prefixed_name = format! ( " npm: {} " , & req . name ) ;
2024-06-10 19:56:43 -04:00
let Some ( nv ) = npm_resolver . req_to_nv ( & req ) . await else {
2024-08-09 10:29:11 -04:00
return Ok ( PackageAndVersion ::NotFound {
package : npm_prefixed_name ,
found_npm_package : false ,
package_req : req ,
} ) ;
2024-03-06 08:24:15 -05:00
} ;
2024-10-16 12:34:33 -04:00
2024-03-06 08:24:15 -05:00
let range_symbol = if req . version_req . version_text ( ) . starts_with ( '~' ) {
2024-10-16 12:34:33 -04:00
" ~ "
} else if req . version_req . version_text ( ) = = nv . version . to_string ( ) {
" "
2024-03-06 08:24:15 -05:00
} else {
2024-10-16 12:34:33 -04:00
" ^ "
2024-03-06 08:24:15 -05:00
} ;
2024-10-16 12:34:33 -04:00
2024-03-06 08:24:15 -05:00
Ok ( PackageAndVersion ::Selected ( SelectedPackage {
2024-06-10 19:56:43 -04:00
import_name : add_package_req . alias ,
2024-03-06 08:24:15 -05:00
package_name : npm_prefixed_name ,
version_req : format ! ( " {}{} " , range_symbol , & nv . version ) ,
2024-08-08 10:25:05 -04:00
selected_version : nv . version . to_string ( ) ,
2024-03-06 08:24:15 -05:00
} ) )
2024-02-29 14:12:04 -05:00
}
}
}
2024-06-10 19:56:43 -04:00
#[ derive(Debug, PartialEq, Eq) ]
2024-10-16 05:20:41 -04:00
enum AddRmPackageReqValue {
2024-06-10 19:56:43 -04:00
Jsr ( PackageReq ) ,
Npm ( PackageReq ) ,
}
#[ derive(Debug, PartialEq, Eq) ]
2024-10-16 05:20:41 -04:00
struct AddRmPackageReq {
2024-06-10 19:56:43 -04:00
alias : String ,
2024-10-16 05:20:41 -04:00
value : AddRmPackageReqValue ,
2024-06-10 19:56:43 -04:00
}
2024-10-16 05:20:41 -04:00
impl AddRmPackageReq {
2024-09-18 14:38:22 -04:00
pub fn parse ( entry_text : & str ) -> Result < Result < Self , PackageReq > , AnyError > {
2024-06-10 19:56:43 -04:00
enum Prefix {
Jsr ,
Npm ,
}
fn parse_prefix ( text : & str ) -> ( Option < Prefix > , & str ) {
if let Some ( text ) = text . strip_prefix ( " jsr: " ) {
( Some ( Prefix ::Jsr ) , text )
} else if let Some ( text ) = text . strip_prefix ( " npm: " ) {
( Some ( Prefix ::Npm ) , text )
} else {
( None , text )
}
}
// parse the following:
// - alias@npm:<package_name>
// - other_alias@npm:<package_name>
// - @alias/other@jsr:<package_name>
fn parse_alias ( entry_text : & str ) -> Option < ( & str , & str ) > {
for prefix in [ " npm: " , " jsr: " ] {
let Some ( location ) = entry_text . find ( prefix ) else {
continue ;
} ;
let prefix = & entry_text [ .. location ] ;
if let Some ( alias ) = prefix . strip_suffix ( '@' ) {
return Some ( ( alias , & entry_text [ location .. ] ) ) ;
}
}
None
}
let ( maybe_prefix , entry_text ) = parse_prefix ( entry_text ) ;
let ( prefix , maybe_alias , entry_text ) = match maybe_prefix {
Some ( prefix ) = > ( prefix , None , entry_text ) ,
None = > match parse_alias ( entry_text ) {
Some ( ( alias , text ) ) = > {
let ( maybe_prefix , entry_text ) = parse_prefix ( text ) ;
2024-09-18 14:38:22 -04:00
if maybe_prefix . is_none ( ) {
return Ok ( Err ( PackageReq ::from_str ( entry_text ) ? ) ) ;
}
( maybe_prefix . unwrap ( ) , Some ( alias . to_string ( ) ) , entry_text )
2024-06-10 19:56:43 -04:00
}
2024-09-18 14:38:22 -04:00
None = > return Ok ( Err ( PackageReq ::from_str ( entry_text ) ? ) ) ,
2024-06-10 19:56:43 -04:00
} ,
} ;
match prefix {
Prefix ::Jsr = > {
2024-09-04 08:55:30 -04:00
let req_ref =
JsrPackageReqReference ::from_str ( & format! ( " jsr: {} " , entry_text ) ) ? ;
let package_req = req_ref . into_inner ( ) . req ;
2024-10-16 05:20:41 -04:00
Ok ( Ok ( AddRmPackageReq {
2024-06-10 19:56:43 -04:00
alias : maybe_alias . unwrap_or_else ( | | package_req . name . to_string ( ) ) ,
2024-10-16 05:20:41 -04:00
value : AddRmPackageReqValue ::Jsr ( package_req ) ,
2024-09-18 14:38:22 -04:00
} ) )
2024-06-10 19:56:43 -04:00
}
Prefix ::Npm = > {
2024-09-04 08:55:30 -04:00
let req_ref =
NpmPackageReqReference ::from_str ( & format! ( " npm: {} " , entry_text ) ) ? ;
2024-09-24 21:10:01 -04:00
let mut package_req = req_ref . into_inner ( ) . req ;
// deno_semver defaults to a version req of `*` if none is specified
// we want to default to `latest` instead
if package_req . version_req = = * deno_semver ::WILDCARD_VERSION_REQ
& & package_req . version_req . version_text ( ) = = " * "
& & ! entry_text . contains ( " @* " )
{
package_req . version_req = VersionReq ::from_raw_text_and_inner (
" latest " . into ( ) ,
deno_semver ::RangeSetOrTag ::Tag ( " latest " . into ( ) ) ,
) ;
}
2024-10-16 05:20:41 -04:00
Ok ( Ok ( AddRmPackageReq {
2024-06-10 19:56:43 -04:00
alias : maybe_alias . unwrap_or_else ( | | package_req . name . to_string ( ) ) ,
2024-10-16 05:20:41 -04:00
value : AddRmPackageReqValue ::Npm ( package_req ) ,
2024-09-18 14:38:22 -04:00
} ) )
2024-06-10 19:56:43 -04:00
}
}
}
2024-02-29 14:12:04 -05:00
}
2024-08-12 16:17:25 -04:00
pub async fn remove (
flags : Arc < Flags > ,
remove_flags : RemoveFlags ,
) -> Result < ( ) , AnyError > {
2024-10-15 12:38:42 -04:00
let ( _ , npm_config , deno_config ) = load_configs ( & flags , | | false ) ? ;
2024-08-12 16:17:25 -04:00
2024-10-21 14:17:08 -04:00
let mut configs = [ npm_config , deno_config ] ;
2024-08-12 16:17:25 -04:00
2024-09-06 13:18:13 -04:00
let mut removed_packages = vec! [ ] ;
2024-08-12 16:17:25 -04:00
2024-09-06 13:18:13 -04:00
for package in & remove_flags . packages {
2024-10-16 05:20:41 -04:00
let req = AddRmPackageReq ::parse ( package ) . with_context ( | | {
format! ( " Failed to parse package required: {} " , package )
} ) ? ;
let mut parsed_pkg_name = None ;
2024-09-06 13:18:13 -04:00
for config in configs . iter_mut ( ) . flatten ( ) {
2024-10-16 05:20:41 -04:00
match & req {
Ok ( rm_pkg ) = > {
if config . remove ( & rm_pkg . alias ) & & parsed_pkg_name . is_none ( ) {
parsed_pkg_name = Some ( rm_pkg . alias . clone ( ) ) ;
}
}
Err ( pkg ) = > {
// An alias or a package name without registry/version
// constraints. Try to remove the package anyway.
if config . remove ( & pkg . name ) & & parsed_pkg_name . is_none ( ) {
parsed_pkg_name = Some ( pkg . name . clone ( ) ) ;
}
}
}
2024-09-06 13:18:13 -04:00
}
2024-10-16 05:20:41 -04:00
if let Some ( pkg ) = parsed_pkg_name {
removed_packages . push ( pkg ) ;
2024-09-06 13:18:13 -04:00
}
2024-08-12 16:17:25 -04:00
}
if removed_packages . is_empty ( ) {
log ::info! ( " No packages were removed " ) ;
} else {
for package in & removed_packages {
log ::info! ( " Removed {} " , crate ::colors ::green ( package ) ) ;
}
2024-09-06 13:18:13 -04:00
for config in configs . into_iter ( ) . flatten ( ) {
2024-10-21 14:17:08 -04:00
config . commit ( ) ? ;
2024-09-06 13:18:13 -04:00
}
2024-10-04 03:52:00 -04:00
npm_install_after_modification ( flags , None ) . await ? ;
}
Ok ( ( ) )
}
async fn npm_install_after_modification (
flags : Arc < Flags > ,
// explicitly provided to prevent redownloading
jsr_resolver : Option < Arc < crate ::jsr ::JsrFetchResolver > > ,
) -> Result < ( ) , AnyError > {
// clear the previously cached package.json from memory before reloading it
node_resolver ::PackageJsonThreadLocalCache ::clear ( ) ;
// make a new CliFactory to pick up the updated config file
let cli_factory = CliFactory ::from_flags ( flags ) ;
// surface any errors in the package.json
let npm_resolver = cli_factory . npm_resolver ( ) . await ? ;
if let Some ( npm_resolver ) = npm_resolver . as_managed ( ) {
npm_resolver . ensure_no_pkg_json_dep_errors ( ) ? ;
2024-08-12 16:17:25 -04:00
}
2024-10-04 03:52:00 -04:00
// npm install
cache_deps ::cache_top_level_deps ( & cli_factory , jsr_resolver ) . await ? ;
2024-08-12 16:17:25 -04:00
2024-10-21 19:08:45 -04:00
if let Some ( lockfile ) = cli_factory . cli_options ( ) ? . maybe_lockfile ( ) {
lockfile . write_if_changed ( ) ? ;
}
2024-08-12 16:17:25 -04:00
Ok ( ( ) )
}
2024-06-10 19:56:43 -04:00
#[ cfg(test) ]
mod test {
use super ::* ;
#[ test ]
fn test_parse_add_package_req ( ) {
assert_eq! (
2024-10-16 05:20:41 -04:00
AddRmPackageReq ::parse ( " jsr:foo " ) . unwrap ( ) . unwrap ( ) ,
AddRmPackageReq {
2024-06-10 19:56:43 -04:00
alias : " foo " . to_string ( ) ,
2024-10-16 05:20:41 -04:00
value : AddRmPackageReqValue ::Jsr ( PackageReq ::from_str ( " foo " ) . unwrap ( ) )
2024-06-10 19:56:43 -04:00
}
) ;
assert_eq! (
2024-10-16 05:20:41 -04:00
AddRmPackageReq ::parse ( " alias@jsr:foo " ) . unwrap ( ) . unwrap ( ) ,
AddRmPackageReq {
2024-06-10 19:56:43 -04:00
alias : " alias " . to_string ( ) ,
2024-10-16 05:20:41 -04:00
value : AddRmPackageReqValue ::Jsr ( PackageReq ::from_str ( " foo " ) . unwrap ( ) )
2024-06-10 19:56:43 -04:00
}
) ;
assert_eq! (
2024-10-16 05:20:41 -04:00
AddRmPackageReq ::parse ( " @alias/pkg@npm:foo " )
. unwrap ( )
. unwrap ( ) ,
AddRmPackageReq {
2024-06-10 19:56:43 -04:00
alias : " @alias/pkg " . to_string ( ) ,
2024-10-16 05:20:41 -04:00
value : AddRmPackageReqValue ::Npm (
2024-09-24 21:10:01 -04:00
PackageReq ::from_str ( " foo@latest " ) . unwrap ( )
)
2024-06-10 19:56:43 -04:00
}
) ;
assert_eq! (
2024-10-16 05:20:41 -04:00
AddRmPackageReq ::parse ( " @alias/pkg@jsr:foo " )
. unwrap ( )
. unwrap ( ) ,
AddRmPackageReq {
2024-06-10 19:56:43 -04:00
alias : " @alias/pkg " . to_string ( ) ,
2024-10-16 05:20:41 -04:00
value : AddRmPackageReqValue ::Jsr ( PackageReq ::from_str ( " foo " ) . unwrap ( ) )
2024-06-10 19:56:43 -04:00
}
) ;
assert_eq! (
2024-10-16 05:20:41 -04:00
AddRmPackageReq ::parse ( " alias@jsr:foo@^1.5.0 " )
2024-09-18 14:38:22 -04:00
. unwrap ( )
. unwrap ( ) ,
2024-10-16 05:20:41 -04:00
AddRmPackageReq {
2024-06-10 19:56:43 -04:00
alias : " alias " . to_string ( ) ,
2024-10-16 05:20:41 -04:00
value : AddRmPackageReqValue ::Jsr (
2024-06-10 19:56:43 -04:00
PackageReq ::from_str ( " foo@^1.5.0 " ) . unwrap ( )
)
}
) ;
assert_eq! (
2024-10-16 05:20:41 -04:00
AddRmPackageReq ::parse ( " @scope/pkg@tag " )
2024-09-18 14:38:22 -04:00
. unwrap ( )
. unwrap_err ( )
. to_string ( ) ,
" @scope/pkg@tag " ,
2024-06-10 19:56:43 -04:00
) ;
}
}