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 ;
2022-11-11 09:39:29 -05:00
use color_eyre ::eyre ::Result ;
2022-11-23 15:23:00 +00:00
use log ::debug ;
2022-11-08 05:54:35 -05:00
#[ cfg(target_os = " linux " ) ]
2021-01-14 09:49:18 +02:00
use nix ::unistd ::Uid ;
2022-06-02 11:28:20 +03:00
use semver ::Version ;
2022-01-14 22:46:10 +02:00
2022-11-08 05:54:35 -05:00
use crate ::command ::CommandExt ;
use crate ::executor ::RunType ;
2022-01-14 22:46:10 +02:00
use crate ::terminal ::print_separator ;
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 ( ) )
}
2021-06-09 10:52:48 +03:00
fn upgrade ( & self , run_type : RunType , 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 {
2022-11-08 05:54:35 -05:00
run_type . execute ( " sudo " ) . args ( args ) . status_checked ( ) ? ;
2021-06-09 10:52:48 +03:00
} else {
2022-11-08 05:54:35 -05:00
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
}
fn upgrade ( & self , run_type : RunType , use_sudo : bool ) -> Result < ( ) > {
let args = [ " global " , " upgrade " ] ;
2022-10-10 22:41:39 +02:00
2022-10-10 20:08:11 +00:00
if use_sudo {
run_type
. execute ( " sudo " )
. 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-08 05:54:35 -05:00
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 )
}
}
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
2022-11-11 09:42:53 -05:00
print_separator ( " Node Package Manager " ) ;
2021-10-25 22:27:35 +03:00
#[ cfg(target_os = " linux " ) ]
{
npm . upgrade ( ctx . run_type ( ) , should_use_sudo ( & npm , ctx ) ? )
}
#[ cfg(not(target_os = " linux " )) ]
{
npm . upgrade ( ctx . run_type ( ) , false )
}
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 ) ) ? ;
2022-11-11 09:42:53 -05:00
print_separator ( " Node Package Manager " ) ;
2022-11-03 20:39:12 +08:00
#[ cfg(target_os = " linux " ) ]
{
pnpm . upgrade ( ctx . run_type ( ) , should_use_sudo ( & pnpm , ctx ) ? )
}
#[ cfg(not(target_os = " linux " )) ]
{
pnpm . upgrade ( ctx . run_type ( ) , false )
}
}
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 ( ( ) ) ;
}
2022-11-11 09:42:53 -05:00
print_separator ( " Yarn Package Manager " ) ;
2022-10-10 20:08:11 +00:00
#[ cfg(target_os = " linux " ) ]
{
yarn . upgrade ( ctx . run_type ( ) , should_use_sudo_yarn ( & yarn , ctx ) ? )
}
#[ cfg(not(target_os = " linux " )) ]
{
yarn . upgrade ( ctx . run_type ( ) , false )
}
}
2020-10-01 15:59:06 -04:00
pub fn deno_upgrade ( ctx : & ExecutionContext ) -> Result < ( ) > {
let deno = require ( " deno " ) ? ;
2021-09-02 18:53:48 +02:00
let deno_dir = ctx . base_dirs ( ) . home_dir ( ) . join ( " .deno " ) ;
if ! deno . canonicalize ( ) ? . is_descendant_of ( & deno_dir ) {
let skip_reason = SkipStep ( " Deno installed outside of .deno directory " . to_string ( ) ) ;
return Err ( skip_reason . into ( ) ) ;
}
2020-10-01 15:59:06 -04:00
print_separator ( " Deno " ) ;
2022-11-08 05:54:35 -05:00
ctx . run_type ( ) . execute ( & deno ) . arg ( " upgrade " ) . status_checked ( )
2020-10-01 15:59:06 -04:00
}