summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSylvain Cau <sylvaincau31@gmail.com>2021-09-30 23:38:07 +0800
committerGitHub <noreply@github.com>2021-09-30 11:38:07 -0400
commitee2e25fba76a3d25df9d0d5d8d4d0286be915043 (patch)
tree209010c1d1fd4501ae84a00df9a4002dfa5fae48
parentf602d63f48067851716d46184746ce26b2e674ba (diff)
feat(cli/uninstall): add uninstall command (#12209)
-rw-r--r--cli/flags.rs52
-rw-r--r--cli/main.rs10
-rw-r--r--cli/tools/installer.rs83
3 files changed, 145 insertions, 0 deletions
diff --git a/cli/flags.rs b/cli/flags.rs
index 208bfdca8..aaff45388 100644
--- a/cli/flags.rs
+++ b/cli/flags.rs
@@ -111,6 +111,12 @@ pub struct InstallFlags {
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
+pub struct UninstallFlags {
+ pub name: String,
+ pub root: Option<PathBuf>,
+}
+
+#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct LintFlags {
pub files: Vec<PathBuf>,
pub ignore: Vec<PathBuf>,
@@ -166,6 +172,7 @@ pub enum DenoSubcommand {
Fmt(FmtFlags),
Info(InfoFlags),
Install(InstallFlags),
+ Uninstall(UninstallFlags),
Lsp,
Lint(LintFlags),
Repl(ReplFlags),
@@ -428,6 +435,8 @@ pub fn flags_from_vec(args: Vec<String>) -> clap::Result<Flags> {
bundle_parse(&mut flags, m);
} else if let Some(m) = matches.subcommand_matches("install") {
install_parse(&mut flags, m);
+ } else if let Some(m) = matches.subcommand_matches("uninstall") {
+ uninstall_parse(&mut flags, m);
} else if let Some(m) = matches.subcommand_matches("completions") {
completions_parse(&mut flags, m);
} else if let Some(m) = matches.subcommand_matches("test") {
@@ -499,6 +508,7 @@ If the flag is set, restrict these messages to errors.",
.subcommand(fmt_subcommand())
.subcommand(info_subcommand())
.subcommand(install_subcommand())
+ .subcommand(uninstall_subcommand())
.subcommand(lsp_subcommand())
.subcommand(lint_subcommand())
.subcommand(repl_subcommand())
@@ -995,6 +1005,36 @@ The installation root is determined, in order of precedence:
These must be added to the path manually if required.")
}
+fn uninstall_subcommand<'a, 'b>() -> App<'a, 'b> {
+ SubCommand::with_name("uninstall")
+ .setting(AppSettings::TrailingVarArg)
+ .arg(
+ Arg::with_name("name")
+ .required(true)
+ .multiple(false)
+ .allow_hyphen_values(true))
+ .arg(
+ Arg::with_name("root")
+ .long("root")
+ .help("Installation root")
+ .takes_value(true)
+ .multiple(false))
+ .about("Uninstall a script previously installed with deno install")
+ .long_about(
+ "Uninstalls an executable script in the installation root's bin directory.
+
+ deno uninstall serve
+
+To change the installation root, use --root:
+
+ deno uninstall --root /usr/local serve
+
+The installation root is determined, in order of precedence:
+ - --root option
+ - DENO_INSTALL_ROOT environment variable
+ - $HOME/.deno")
+}
+
fn lsp_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("lsp")
.about("Start the language server")
@@ -1896,6 +1936,18 @@ fn install_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
});
}
+fn uninstall_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
+ let root = if matches.is_present("root") {
+ let install_root = matches.value_of("root").unwrap();
+ Some(PathBuf::from(install_root))
+ } else {
+ None
+ };
+
+ let name = matches.value_of("name").unwrap().to_string();
+ flags.subcommand = DenoSubcommand::Uninstall(UninstallFlags { name, root });
+}
+
fn lsp_parse(flags: &mut Flags, _matches: &clap::ArgMatches) {
flags.subcommand = DenoSubcommand::Lsp;
}
diff --git a/cli/main.rs b/cli/main.rs
index ba16ea590..9176aca6f 100644
--- a/cli/main.rs
+++ b/cli/main.rs
@@ -53,6 +53,7 @@ use crate::flags::LintFlags;
use crate::flags::ReplFlags;
use crate::flags::RunFlags;
use crate::flags::TestFlags;
+use crate::flags::UninstallFlags;
use crate::flags::UpgradeFlags;
use crate::fmt_errors::PrettyJsError;
use crate::module_loader::CliModuleLoader;
@@ -487,6 +488,12 @@ async fn install_command(
)
}
+async fn uninstall_command(
+ uninstall_flags: UninstallFlags,
+) -> Result<(), AnyError> {
+ tools::installer::uninstall(uninstall_flags.name, uninstall_flags.root)
+}
+
async fn lsp_command() -> Result<(), AnyError> {
lsp::start().await
}
@@ -1149,6 +1156,9 @@ fn get_subcommand(
DenoSubcommand::Install(install_flags) => {
install_command(flags, install_flags).boxed_local()
}
+ DenoSubcommand::Uninstall(uninstall_flags) => {
+ uninstall_command(uninstall_flags).boxed_local()
+ }
DenoSubcommand::Lsp => lsp_command().boxed_local(),
DenoSubcommand::Lint(lint_flags) => {
lint_command(flags, lint_flags).boxed_local()
diff --git a/cli/tools/installer.rs b/cli/tools/installer.rs
index 4d4709e28..18d2d20e7 100644
--- a/cli/tools/installer.rs
+++ b/cli/tools/installer.rs
@@ -139,6 +139,57 @@ pub fn infer_name_from_url(url: &Url) -> Option<String> {
Some(stem)
}
+pub fn uninstall(name: String, root: Option<PathBuf>) -> Result<(), AnyError> {
+ let root = if let Some(root) = root {
+ canonicalize_path(&root)?
+ } else {
+ get_installer_root()?
+ };
+ let installation_dir = root.join("bin");
+
+ // ensure directory exists
+ if let Ok(metadata) = fs::metadata(&installation_dir) {
+ if !metadata.is_dir() {
+ return Err(generic_error("Installation path is not a directory"));
+ }
+ }
+
+ let mut file_path = installation_dir.join(&name);
+
+ let mut removed = false;
+
+ if file_path.exists() {
+ fs::remove_file(&file_path)?;
+ println!("deleted {}", file_path.to_string_lossy());
+ removed = true
+ };
+
+ if cfg!(windows) {
+ file_path = file_path.with_extension("cmd");
+ if file_path.exists() {
+ fs::remove_file(&file_path)?;
+ println!("deleted {}", file_path.to_string_lossy());
+ removed = true
+ }
+ }
+
+ if !removed {
+ return Err(generic_error(format!("No installation found for {}", name)));
+ }
+
+ // There might be some extra files to delete
+ for ext in ["tsconfig.json", "lock.json"] {
+ file_path = file_path.with_extension(ext);
+ if file_path.exists() {
+ fs::remove_file(&file_path)?;
+ println!("deleted {}", file_path.to_string_lossy());
+ }
+ }
+
+ println!("✅ Successfully uninstalled {}", name);
+ Ok(())
+}
+
pub fn install(
flags: Flags,
module_url: &str,
@@ -926,4 +977,36 @@ mod tests {
let content = fs::read_to_string(file_path).unwrap();
assert!(content.contains(&expected_string));
}
+
+ #[test]
+ fn uninstall_basic() {
+ let temp_dir = TempDir::new().expect("tempdir fail");
+ let bin_dir = temp_dir.path().join("bin");
+ std::fs::create_dir(&bin_dir).unwrap();
+
+ let mut file_path = bin_dir.join("echo_test");
+ File::create(&file_path).unwrap();
+ if cfg!(windows) {
+ file_path = file_path.with_extension("cmd");
+ File::create(&file_path).unwrap();
+ }
+
+ // create extra files
+ file_path = file_path.with_extension("tsconfig.json");
+ File::create(&file_path).unwrap();
+ file_path = file_path.with_extension("lock.json");
+ File::create(&file_path).unwrap();
+
+ uninstall("echo_test".to_string(), Some(temp_dir.path().to_path_buf()))
+ .expect("Uninstall failed");
+
+ assert!(!file_path.exists());
+ assert!(!file_path.with_extension("tsconfig.json").exists());
+ assert!(!file_path.with_extension("lock.json").exists());
+
+ if cfg!(windows) {
+ file_path = file_path.with_extension("cmd");
+ assert!(!file_path.exists());
+ }
+ }
}