diff --git a/config.example.toml b/config.example.toml index bcaa9c7e..2b72d046 100644 --- a/config.example.toml +++ b/config.example.toml @@ -81,3 +81,7 @@ # to upgrade it. Use this only if you installed Topgrade by using a package # manager such as Scoop to Cargo #self_rename = true + +[npm] +# Use sudo if the NPM directory isn't owned by the current user +#use_sudo = true diff --git a/src/config.rs b/src/config.rs index 452a6983..60d0dc8e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -147,6 +147,13 @@ pub struct Windows { open_remotes_in_new_terminal: Option, } +#[derive(Deserialize, Default, Debug)] +#[serde(deny_unknown_fields)] +#[allow(clippy::upper_case_acronyms)] +pub struct NPM { + use_sudo: Option, +} + #[derive(Deserialize, Default, Debug)] #[serde(deny_unknown_fields)] pub struct Brew { @@ -204,6 +211,7 @@ pub struct ConfigFile { linux: Option, git: Option, windows: Option, + npm: Option, vagrant: Option, } @@ -713,6 +721,15 @@ impl Config { .unwrap_or(false) } + #[cfg(target_os = "linux")] + pub fn npm_use_sudo(&self) -> bool { + self.config_file + .npm + .as_ref() + .and_then(|npm| npm.use_sudo) + .unwrap_or(false) + } + #[cfg(target_os = "linux")] str_value!(linux, emerge_sync_flags); diff --git a/src/main.rs b/src/main.rs index 28f8cf54..4e7fc989 100644 --- a/src/main.rs +++ b/src/main.rs @@ -297,7 +297,7 @@ fn run() -> Result<()> { runner.execute(Step::Vim, "vim", || vim::upgrade_vim(&base_dirs, &ctx))?; runner.execute(Step::Vim, "Neovim", || vim::upgrade_neovim(&base_dirs, &ctx))?; runner.execute(Step::Vim, "voom", || vim::run_voom(&base_dirs, run_type))?; - runner.execute(Step::Node, "npm", || node::run_npm_upgrade(&base_dirs, run_type))?; + runner.execute(Step::Node, "npm", || node::run_npm_upgrade(&ctx))?; runner.execute(Step::Node, "yarn", || node::yarn_global_update(run_type))?; runner.execute(Step::Deno, "deno", || node::deno_upgrade(&ctx))?; runner.execute(Step::Composer, "composer", || generic::run_composer_update(&ctx))?; diff --git a/src/steps/node.rs b/src/steps/node.rs index f3b3612b..fe4daaa3 100644 --- a/src/steps/node.rs +++ b/src/steps/node.rs @@ -32,15 +32,25 @@ impl NPM { .map(|s| PathBuf::from(s.trim())) } - fn upgrade(&self, run_type: RunType) -> Result<()> { - run_type.execute(&self.command).args(&["update", "-g"]).check_run()?; + fn upgrade(&self, run_type: RunType, use_sudo: bool) -> Result<()> { + if use_sudo { + run_type + .execute("sudo") + .arg(&self.command) + .args(&["update", "-g"]) + .check_run()?; + } else { + run_type.execute(&self.command).args(&["update", "-g"]).check_run()?; + } Ok(()) } } -pub fn run_npm_upgrade(_base_dirs: &BaseDirs, run_type: RunType) -> Result<()> { +pub fn run_npm_upgrade(ctx: &ExecutionContext) -> Result<()> { let npm = require("npm").map(NPM::new)?; + #[allow(unused_mut)] + let mut use_sudo = false; #[cfg(target_os = "linux")] { @@ -53,17 +63,21 @@ pub fn run_npm_upgrade(_base_dirs: &BaseDirs, run_type: RunType) -> Result<()> { let uid = Uid::effective(); if metadata.uid() != uid.as_raw() { - return Err(SkipStep(format!( - "NPM root at {} is owned by {} which is not the current user", - npm_root.display(), - metadata.uid() - )) - .into()); + if metadata.uid() == 0 && (ctx.config().npm_use_sudo()) { + use_sudo = true; + } else { + return Err(SkipStep(format!( + "NPM root at {} is owned by {} which is not the current user. Set use_sudo = true under the NPM section in your configuration to run NPM as sudo", + npm_root.display(), + metadata.uid() + )) + .into()); + } } } print_separator("Node Package Manager"); - npm.upgrade(run_type) + npm.upgrade(ctx.run_type(), use_sudo) } pub fn yarn_global_update(run_type: RunType) -> Result<()> {