Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cli/uninstall): Add uninstall command #12209

Merged
merged 9 commits into from
Sep 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions cli/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,12 @@ pub struct InstallFlags {
pub force: bool,
}

#[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>,
Expand Down Expand Up @@ -166,6 +172,7 @@ pub enum DenoSubcommand {
Fmt(FmtFlags),
Info(InfoFlags),
Install(InstallFlags),
Uninstall(UninstallFlags),
Lsp,
Lint(LintFlags),
Repl(ReplFlags),
Expand Down Expand Up @@ -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") {
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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;
}
Expand Down
10 changes: 10 additions & 0 deletions cli/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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()
Expand Down
83 changes: 83 additions & 0 deletions cli/tools/installer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another minor point: It might be useful to the user to include the resolved installation_dir path in this error message.

}
}

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) {
dsherret marked this conversation as resolved.
Show resolved Hide resolved
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(
dsherret marked this conversation as resolved.
Show resolved Hide resolved
flags: Flags,
module_url: &str,
Expand Down Expand Up @@ -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");
sylc marked this conversation as resolved.
Show resolved Hide resolved
assert!(!file_path.exists());
}
}
}