2022-11-03 20:39:12 +08:00
use std ::fmt ::Display ;
2022-11-08 05:54:35 -05:00
#[ cfg(target_os = " linux " ) ]
use std ::os ::unix ::fs ::MetadataExt ;
2022-01-14 22:46:10 +02:00
use std ::path ::PathBuf ;
use std ::process ::Command ;
2024-10-03 12:47:35 +02:00
use crate ::utils ::{ get_require_sudo_string , require_option } ;
2023-05-01 00:02:13 +05:30
use crate ::HOME_DIR ;
2022-11-11 09:39:29 -05:00
use color_eyre ::eyre ::Result ;
2022-11-08 05:54:35 -05:00
#[ cfg(target_os = " linux " ) ]
2021-01-14 09:49:18 +02:00
use nix ::unistd ::Uid ;
2024-10-03 12:47:35 +02:00
use rust_i18n ::t ;
2022-06-02 11:28:20 +03:00
use semver ::Version ;
2022-11-16 13:43:57 -05:00
use tracing ::debug ;
2022-01-14 22:46:10 +02:00
2022-11-08 05:54:35 -05:00
use crate ::command ::CommandExt ;
2024-08-01 11:26:22 +01:00
use crate ::terminal ::{ print_info , print_separator } ;
2022-01-14 22:46:10 +02:00
use crate ::utils ::{ require , PathExt } ;
use crate ::{ error ::SkipStep , execution_context ::ExecutionContext } ;
2018-08-19 14:45:23 +03:00
2022-11-03 20:39:12 +08:00
enum NPMVariant {
Npm ,
Pnpm ,
}
impl NPMVariant {
const fn short_name ( & self ) -> & str {
match self {
NPMVariant ::Npm = > " npm " ,
NPMVariant ::Pnpm = > " pnpm " ,
}
}
const fn is_npm ( & self ) -> bool {
matches! ( self , NPMVariant ::Npm )
}
}
impl Display for NPMVariant {
fn fmt ( & self , f : & mut std ::fmt ::Formatter < '_ > ) -> std ::fmt ::Result {
f . write_str ( self . short_name ( ) )
}
}
2021-04-06 09:52:34 +03:00
#[ allow(clippy::upper_case_acronyms) ]
2018-08-19 14:45:23 +03:00
struct NPM {
command : PathBuf ,
2022-11-03 20:39:12 +08:00
variant : NPMVariant ,
2018-08-19 14:45:23 +03:00
}
impl NPM {
2022-11-03 20:39:12 +08:00
fn new ( command : PathBuf , variant : NPMVariant ) -> Self {
Self { command , variant }
}
/// Is the “NPM” version larger than 8.11.0?
fn is_npm_8 ( & self ) -> bool {
let v = self . version ( ) ;
self . variant . is_npm ( ) & & matches! ( v , Ok ( v ) if v > = Version ::new ( 8 , 11 , 0 ) )
}
/// Get the most suitable “global location” argument
/// of this NPM instance.
///
/// If the “NPM” version is larger than 8.11.0, we use
/// `--location=global`; otherwise, use `-g`.
fn global_location_arg ( & self ) -> & str {
if self . is_npm_8 ( ) {
" --location=global "
} else {
" -g "
}
2018-08-19 14:45:23 +03:00
}
2021-01-14 09:49:18 +02:00
#[ cfg(target_os = " linux " ) ]
2019-12-11 23:05:38 +02:00
fn root ( & self ) -> Result < PathBuf > {
2022-11-03 20:39:12 +08:00
let args = [ " root " , self . global_location_arg ( ) ] ;
2019-01-01 22:22:07 +02:00
Command ::new ( & self . command )
2022-06-02 11:28:20 +03:00
. args ( args )
2022-11-08 05:54:35 -05:00
. output_checked_utf8 ( )
. map ( | s | PathBuf ::from ( s . stdout . trim ( ) ) )
2018-08-19 14:45:23 +03:00
}
2022-06-02 11:28:20 +03:00
fn version ( & self ) -> Result < Version > {
let version_str = Command ::new ( & self . command )
2022-10-23 11:34:30 +00:00
. args ( [ " --version " ] )
2022-11-08 05:54:35 -05:00
. output_checked_utf8 ( )
. map ( | s | s . stdout . trim ( ) . to_owned ( ) ) ;
2022-06-02 11:28:20 +03:00
Version ::parse ( & version_str ? ) . map_err ( | err | err . into ( ) )
}
2022-11-25 17:19:32 -05:00
fn upgrade ( & self , ctx : & ExecutionContext , use_sudo : bool ) -> Result < ( ) > {
2022-11-03 20:39:12 +08:00
let args = [ " update " , self . global_location_arg ( ) ] ;
2021-06-09 10:52:48 +03:00
if use_sudo {
2024-10-03 12:47:35 +02:00
let sudo = require_option ( ctx . sudo ( ) . clone ( ) , get_require_sudo_string ( ) ) ? ;
2022-11-25 17:19:32 -05:00
ctx . run_type ( )
. execute ( sudo )
. arg ( & self . command )
. args ( args )
. status_checked ( ) ? ;
2021-06-09 10:52:48 +03:00
} else {
2022-11-25 17:19:32 -05:00
ctx . run_type ( ) . execute ( & self . command ) . args ( args ) . status_checked ( ) ? ;
2021-06-09 10:52:48 +03:00
}
2018-08-19 14:45:23 +03:00
Ok ( ( ) )
}
2020-01-28 16:27:43 +02:00
2021-01-14 09:49:18 +02:00
#[ cfg(target_os = " linux " ) ]
2021-10-25 22:27:35 +03:00
pub fn should_use_sudo ( & self ) -> Result < bool > {
let npm_root = self . root ( ) ? ;
2021-01-15 06:59:13 +02:00
if ! npm_root . exists ( ) {
2022-11-03 20:39:12 +08:00
return Err ( SkipStep ( format! ( " {} root at {} doesn't exist " , self . variant , npm_root . display ( ) ) ) . into ( ) ) ;
2021-01-15 06:59:13 +02:00
}
2021-01-14 09:49:18 +02:00
let metadata = std ::fs ::metadata ( & npm_root ) ? ;
let uid = Uid ::effective ( ) ;
2021-10-25 22:27:35 +03:00
Ok ( metadata . uid ( ) ! = uid . as_raw ( ) & & metadata . uid ( ) = = 0 )
}
}
2022-10-10 20:08:11 +00:00
struct Yarn {
command : PathBuf ,
yarn : Option < PathBuf > ,
}
impl Yarn {
fn new ( command : PathBuf ) -> Self {
Self {
command ,
yarn : require ( " yarn " ) . ok ( ) ,
}
}
2022-11-03 20:20:16 +08:00
fn has_global_subcmd ( & self ) -> bool {
// Get the version of Yarn. After Yarn 2.x (berry),
// “yarn global” has been replaced with “yarn dlx”.
//
// As “yarn dlx” don't need to “upgrade”, we
// ignore the whole task if Yarn is 2.x or above.
2022-11-08 05:54:35 -05:00
let version = Command ::new ( & self . command ) . args ( [ " --version " ] ) . output_checked_utf8 ( ) ;
2022-11-03 20:20:16 +08:00
2022-11-08 05:54:35 -05:00
matches! ( version , Ok ( ver ) if ver . stdout . starts_with ( '1' ) | | ver . stdout . starts_with ( '0' ) )
2022-11-03 20:20:16 +08:00
}
2022-10-10 20:08:11 +00:00
#[ cfg(target_os = " linux " ) ]
fn root ( & self ) -> Result < PathBuf > {
let args = [ " global " , " dir " ] ;
Command ::new ( & self . command )
. args ( args )
2022-11-08 05:54:35 -05:00
. output_checked_utf8 ( )
. map ( | s | PathBuf ::from ( s . stdout . trim ( ) ) )
2022-10-10 20:08:11 +00:00
}
2022-11-25 17:19:32 -05:00
fn upgrade ( & self , ctx : & ExecutionContext , use_sudo : bool ) -> Result < ( ) > {
2022-10-10 20:08:11 +00:00
let args = [ " global " , " upgrade " ] ;
2022-10-10 22:41:39 +02:00
2022-10-10 20:08:11 +00:00
if use_sudo {
2024-10-03 12:47:35 +02:00
let sudo = require_option ( ctx . sudo ( ) . clone ( ) , get_require_sudo_string ( ) ) ? ;
2022-11-25 17:19:32 -05:00
ctx . run_type ( )
. execute ( sudo )
2022-10-10 20:08:11 +00:00
. arg ( self . yarn . as_ref ( ) . unwrap_or ( & self . command ) )
. args ( args )
2022-11-08 05:54:35 -05:00
. status_checked ( ) ? ;
2022-10-10 20:08:11 +00:00
} else {
2022-11-25 17:19:32 -05:00
ctx . run_type ( ) . execute ( & self . command ) . args ( args ) . status_checked ( ) ? ;
2022-10-10 20:08:11 +00:00
}
Ok ( ( ) )
}
#[ cfg(target_os = " linux " ) ]
pub fn should_use_sudo ( & self ) -> Result < bool > {
let yarn_root = self . root ( ) ? ;
if ! yarn_root . exists ( ) {
2022-11-03 20:39:12 +08:00
return Err ( SkipStep ( format! ( " Yarn root at {} doesn't exist " , yarn_root . display ( ) , ) ) . into ( ) ) ;
2022-10-10 20:08:11 +00:00
}
let metadata = std ::fs ::metadata ( & yarn_root ) ? ;
let uid = Uid ::effective ( ) ;
Ok ( metadata . uid ( ) ! = uid . as_raw ( ) & & metadata . uid ( ) = = 0 )
}
}
2024-10-29 11:09:47 +01:00
struct Deno {
command : PathBuf ,
}
impl Deno {
fn new ( command : PathBuf ) -> Self {
Self { command }
}
fn upgrade ( & self , ctx : & ExecutionContext ) -> Result < ( ) > {
let mut args = vec! [ ] ;
let version = ctx . config ( ) . deno_version ( ) ;
if let Some ( version ) = version {
let bin_version = self . version ( ) ? ;
if bin_version > = Version ::new ( 2 , 0 , 0 ) {
args . push ( version ) ;
} else if bin_version > = Version ::new ( 1 , 6 , 0 ) {
match version {
" stable " = > { /* do nothing, as stable is the default channel to upgrade */ }
" rc " = > {
return Err ( SkipStep (
" Deno (1.6.0-2.0.0) cannot be upgraded to a release candidate " . to_string ( ) ,
)
. into ( ) ) ;
}
" canary " = > args . push ( " --canary " ) ,
_ = > {
if Version ::parse ( version ) . is_err ( ) {
return Err ( SkipStep ( " Invalid Deno version " . to_string ( ) ) . into ( ) ) ;
}
args . push ( " --version " ) ;
args . push ( version ) ;
}
}
} else if bin_version > = Version ::new ( 1 , 0 , 0 ) {
match version {
" stable " | " rc " | " canary " = > {
// Prior to v1.6.0, `deno upgrade` is not able fetch the latest tag version.
return Err (
SkipStep ( " Deno (1.0.0-1.6.0) cannot be upgraded to a named channel " . to_string ( ) ) . into ( ) ,
) ;
}
_ = > {
if Version ::parse ( version ) . is_err ( ) {
return Err ( SkipStep ( " Invalid Deno version " . to_string ( ) ) . into ( ) ) ;
}
args . push ( " --version " ) ;
args . push ( version ) ;
}
}
} else {
// v0.x cannot be upgraded with `deno upgrade` to v1.x or v2.x
// nor can be upgraded to a specific version.
return Err ( SkipStep ( " Unsupported Deno version " . to_string ( ) ) . into ( ) ) ;
}
}
ctx . run_type ( )
. execute ( & self . command )
. arg ( " upgrade " )
. args ( args )
. status_checked ( ) ? ;
Ok ( ( ) )
}
/// Get the version of Deno.
///
/// This function will return the version of Deno installed on the system.
/// The version is parsed from the output of `deno -V`.
///
/// ```sh
/// deno -V # deno 1.6.0
/// ```
fn version ( & self ) -> Result < Version > {
let version_str = Command ::new ( & self . command )
. args ( [ " -V " ] )
. output_checked_utf8 ( )
. map ( | s | s . stdout . trim ( ) . to_owned ( ) . split_off ( 5 ) ) ; // remove "deno " prefix
Version ::parse ( & version_str ? ) . map_err ( | err | err . into ( ) )
}
}
2021-10-25 22:27:35 +03:00
#[ cfg(target_os = " linux " ) ]
fn should_use_sudo ( npm : & NPM , ctx : & ExecutionContext ) -> Result < bool > {
if npm . should_use_sudo ( ) ? {
if ctx . config ( ) . npm_use_sudo ( ) {
Ok ( true )
} else {
Err ( SkipStep ( " NPM root is owned by another user which is not the current user. Set use_sudo = true under the NPM section in your configuration to run NPM as sudo " . to_string ( ) )
. into ( ) )
2020-01-28 16:27:43 +02:00
}
2021-10-25 22:27:35 +03:00
} else {
Ok ( false )
2018-08-19 14:45:23 +03:00
}
2021-10-25 22:27:35 +03:00
}
2022-10-10 20:08:11 +00:00
#[ cfg(target_os = " linux " ) ]
fn should_use_sudo_yarn ( yarn : & Yarn , ctx : & ExecutionContext ) -> Result < bool > {
if yarn . should_use_sudo ( ) ? {
if ctx . config ( ) . yarn_use_sudo ( ) {
Ok ( true )
} else {
Err ( SkipStep ( " NPM root is owned by another user which is not the current user. Set use_sudo = true under the NPM section in your configuration to run NPM as sudo " . to_string ( ) )
. into ( ) )
}
} else {
Ok ( false )
}
}
2021-10-25 22:27:35 +03:00
pub fn run_npm_upgrade ( ctx : & ExecutionContext ) -> Result < ( ) > {
2022-11-03 20:39:12 +08:00
let npm = require ( " npm " ) . map ( | b | NPM ::new ( b , NPMVariant ::Npm ) ) ? ;
2018-08-19 14:45:23 +03:00
2024-10-03 12:47:35 +02:00
print_separator ( t! ( " Node Package Manager " ) ) ;
2022-11-11 09:42:53 -05:00
2021-10-25 22:27:35 +03:00
#[ cfg(target_os = " linux " ) ]
{
2022-11-25 17:19:32 -05:00
npm . upgrade ( ctx , should_use_sudo ( & npm , ctx ) ? )
2021-10-25 22:27:35 +03:00
}
#[ cfg(not(target_os = " linux " )) ]
{
2022-11-25 17:19:32 -05:00
npm . upgrade ( ctx , false )
2021-10-25 22:27:35 +03:00
}
2019-03-10 21:48:49 +02:00
}
2018-08-19 14:45:23 +03:00
2022-11-03 20:39:12 +08:00
pub fn run_pnpm_upgrade ( ctx : & ExecutionContext ) -> Result < ( ) > {
let pnpm = require ( " pnpm " ) . map ( | b | NPM ::new ( b , NPMVariant ::Pnpm ) ) ? ;
2024-10-03 12:47:35 +02:00
print_separator ( t! ( " Performant Node Package Manager " ) ) ;
2022-11-11 09:42:53 -05:00
2022-11-03 20:39:12 +08:00
#[ cfg(target_os = " linux " ) ]
{
2022-11-25 17:19:32 -05:00
pnpm . upgrade ( ctx , should_use_sudo ( & pnpm , ctx ) ? )
2022-11-03 20:39:12 +08:00
}
#[ cfg(not(target_os = " linux " )) ]
{
2022-11-25 17:19:32 -05:00
pnpm . upgrade ( ctx , false )
2022-11-03 20:39:12 +08:00
}
}
2022-10-10 20:08:11 +00:00
pub fn run_yarn_upgrade ( ctx : & ExecutionContext ) -> Result < ( ) > {
let yarn = require ( " yarn " ) . map ( Yarn ::new ) ? ;
2022-11-03 20:20:16 +08:00
if ! yarn . has_global_subcmd ( ) {
debug! ( " Yarn is 2.x or above, skipping global upgrade " ) ;
return Ok ( ( ) ) ;
}
2024-10-03 12:47:35 +02:00
print_separator ( t! ( " Yarn Package Manager " ) ) ;
2022-11-11 09:42:53 -05:00
2022-10-10 20:08:11 +00:00
#[ cfg(target_os = " linux " ) ]
{
2022-11-25 17:19:32 -05:00
yarn . upgrade ( ctx , should_use_sudo_yarn ( & yarn , ctx ) ? )
2022-10-10 20:08:11 +00:00
}
#[ cfg(not(target_os = " linux " )) ]
{
2022-11-25 17:19:32 -05:00
yarn . upgrade ( ctx , false )
2022-10-10 20:08:11 +00:00
}
}
2020-10-01 15:59:06 -04:00
pub fn deno_upgrade ( ctx : & ExecutionContext ) -> Result < ( ) > {
2024-10-29 11:09:47 +01:00
let deno = require ( " deno " ) . map ( Deno ::new ) ? ;
2023-05-01 00:02:13 +05:30
let deno_dir = HOME_DIR . join ( " .deno " ) ;
2021-09-02 18:53:48 +02:00
2024-10-29 11:09:47 +01:00
if ! deno . command . canonicalize ( ) ? . is_descendant_of ( & deno_dir ) {
2024-10-03 12:47:35 +02:00
let skip_reason = SkipStep ( t! ( " Deno installed outside of .deno directory " ) . to_string ( ) ) ;
2021-09-02 18:53:48 +02:00
return Err ( skip_reason . into ( ) ) ;
}
2020-10-01 15:59:06 -04:00
print_separator ( " Deno " ) ;
2024-10-29 11:09:47 +01:00
deno . upgrade ( ctx )
2020-10-01 15:59:06 -04:00
}
2024-08-01 11:26:22 +01:00
/// There is no `volta upgrade` command, so we need to upgrade each package
pub fn run_volta_packages_upgrade ( ctx : & ExecutionContext ) -> Result < ( ) > {
let volta = require ( " volta " ) ? ;
print_separator ( " Volta " ) ;
if ctx . run_type ( ) . dry ( ) {
2024-10-03 12:47:35 +02:00
print_info ( t! ( " Updating Volta packages... " ) ) ;
2024-08-01 11:26:22 +01:00
return Ok ( ( ) ) ;
}
let list_output = ctx
. run_type ( )
. execute ( & volta )
. args ( [ " list " , " --format=plain " ] )
. output_checked_utf8 ( ) ?
. stdout ;
let installed_packages : Vec < & str > = list_output
. lines ( )
. filter_map ( | line | {
// format is 'kind package@version ...'
let mut parts = line . split_whitespace ( ) ;
parts . next ( ) ;
let package_part = parts . next ( ) ? ;
let version_index = package_part . rfind ( '@' ) . unwrap_or ( package_part . len ( ) ) ;
Some ( package_part [ .. version_index ] . trim ( ) )
} )
. collect ( ) ;
if installed_packages . is_empty ( ) {
2024-10-03 12:47:35 +02:00
print_info ( t! ( " No packages installed with Volta " ) ) ;
2024-08-01 11:26:22 +01:00
return Ok ( ( ) ) ;
}
for package in installed_packages . iter ( ) {
ctx . run_type ( )
. execute ( & volta )
. args ( [ " install " , package ] )
. status_checked ( ) ? ;
}
Ok ( ( ) )
}