diff --git a/qlty-cli/src/commands/plugins.rs b/qlty-cli/src/commands/plugins.rs index e94fd4c49..82530e0bc 100644 --- a/qlty-cli/src/commands/plugins.rs +++ b/qlty-cli/src/commands/plugins.rs @@ -5,10 +5,12 @@ use clap::{Args, Subcommand}; mod disable; mod enable; mod list; +mod upgrade; pub use disable::Disable; pub use enable::Enable; pub use list::List; +pub use upgrade::Upgrade; #[derive(Debug, Args)] pub struct Arguments { @@ -27,6 +29,9 @@ pub enum Commands { /// List all available plugins List(List), + + /// Upgrades given plugin + Upgrade(Upgrade), } impl Arguments { @@ -35,6 +40,7 @@ impl Arguments { Commands::Enable(command) => command.execute(args), Commands::Disable(command) => command.execute(args), Commands::List(command) => command.execute(args), + Commands::Upgrade(command) => command.execute(args), } } } diff --git a/qlty-cli/src/commands/plugins/upgrade.rs b/qlty-cli/src/commands/plugins/upgrade.rs new file mode 100644 index 000000000..55b64efc2 --- /dev/null +++ b/qlty-cli/src/commands/plugins/upgrade.rs @@ -0,0 +1,223 @@ +use crate::{Arguments, CommandError, CommandSuccess}; +use anyhow::{bail, Context, Result}; +use clap::Args; +use qlty_config::Workspace; +use std::fs; +use toml_edit::{value, DocumentMut}; + +#[derive(Args, Debug)] +pub struct Upgrade { + /// Plugin to upgrade + pub plugin: String, + + /// Optional - Specific version to upgrade to + #[clap(long)] + pub version: Option, +} + +struct ConfigDocument { + workspace: Workspace, + document: DocumentMut, +} + +impl ConfigDocument { + pub fn new(workspace: &Workspace) -> Result { + let contents = fs::read_to_string(workspace.config_path()?)?; + let document = contents.parse::().expect("Invalid config doc"); + + Ok(Self { + workspace: workspace.clone(), + document, + }) + } + + pub fn upgrade_plugin(&mut self, name: &str, version: &Option) -> Result<()> { + let version = if let Some(version) = version { + version + } else { + let config = self.workspace.config()?; + + let plugin = config + .plugins + .definitions + .get(name) + .context("Plugin not found")?; + + &plugin.latest_version.clone().unwrap_or_else(|| { + plugin + .known_good_version + .clone() + .unwrap_or("latest".to_string()) + }) + }; + + if self.document.get("plugin").is_none() { + bail!("No plugins found in qlty.toml"); + } + + let mut updated = false; + + if let Some(plugin_tables) = self.document["plugin"].as_array_of_tables_mut() { + for plugin_table in plugin_tables.iter_mut() { + if plugin_table["name"].as_str() == Some(name) { + updated = true; + if version != "latest" { + plugin_table["version"] = value(version); + } + } + } + } + + if !updated { + bail!("Plugin not found in qlty.toml"); + } + + Ok(()) + } + + pub fn write(&self) -> Result<()> { + fs::write(self.workspace.config_path()?, self.document.to_string())?; + Ok(()) + } +} + +impl Upgrade { + pub fn execute(&self, _args: &Arguments) -> Result { + let workspace = Workspace::require_initialized()?; + workspace.fetch_sources()?; + + let mut config = ConfigDocument::new(&workspace)?; + + config.upgrade_plugin(&self.plugin, &self.version)?; + + config.write()?; + CommandSuccess::ok() + } +} + +#[cfg(test)] +mod tests { + use qlty_analysis::utils::fs::path_to_native_string; + use qlty_test_utilities::git::sample_repo; + + use super::*; + + #[test] + fn test_upgrade_plugin() { + let (temp_dir, _) = sample_repo(); + let temp_path = temp_dir.path().to_path_buf(); + + fs::create_dir_all(&temp_path.join(path_to_native_string(".qlty"))).ok(); + fs::write( + &temp_path.join(path_to_native_string(".qlty/qlty.toml")), + r#" +config_version = "0" + +[plugins.definitions.upgradeable] +file_types = ["ALL"] +latest_version = "1.1.0" + +[plugins.definitions.upgradeable.drivers.lint] +script = "ls -l ${target}" +success_codes = [0] +output = "pass_fail" + +[[plugin]] +name = "upgradeable" +version = "1.0.0" + "#, + ) + .ok(); + + let workspace = Workspace { + root: temp_path.clone(), + }; + + let mut config = ConfigDocument::new(&workspace).unwrap(); + config.upgrade_plugin("upgradeable", &None).unwrap(); + + let expected = r#" +config_version = "0" + +[plugins.definitions.upgradeable] +file_types = ["ALL"] +latest_version = "1.1.0" + +[plugins.definitions.upgradeable.drivers.lint] +script = "ls -l ${target}" +success_codes = [0] +output = "pass_fail" + +[[plugin]] +name = "upgradeable" +version = "1.1.0" + "#; + + assert_eq!(config.document.to_string().trim(), expected.trim()); + } + + #[test] + fn test_upgrade_plugin_wrong_plugin_name() { + let (temp_dir, _) = sample_repo(); + let temp_path = temp_dir.path().to_path_buf(); + + fs::create_dir_all(&temp_path.join(path_to_native_string(".qlty"))).ok(); + fs::write( + &temp_path.join(path_to_native_string(".qlty/qlty.toml")), + r#" +config_version = "0" + +[[plugin]] +name = "actual_plugin" +version = "1.0.0" + "#, + ) + .ok(); + + let workspace = Workspace { + root: temp_path.clone(), + }; + + let mut config = ConfigDocument::new(&workspace).unwrap(); + + assert!(config.upgrade_plugin("actual_typo", &None).is_err()); + } + + #[test] + fn test_upgrade_plugin_with_given_version() { + let (temp_dir, _) = sample_repo(); + let temp_path = temp_dir.path().to_path_buf(); + + fs::create_dir_all(&temp_path.join(path_to_native_string(".qlty"))).ok(); + fs::write( + &temp_path.join(path_to_native_string(".qlty/qlty.toml")), + r#" +config_version = "0" + +[[plugin]] +name = "plugin_to_upgrade" +version = "1.0.0" + "#, + ) + .ok(); + + let workspace = Workspace { + root: temp_path.clone(), + }; + + let mut config = ConfigDocument::new(&workspace).unwrap(); + config + .upgrade_plugin("plugin_to_upgrade", &Some("2.1.0".to_string())) + .unwrap(); + + let expected = r#" +config_version = "0" + +[[plugin]] +name = "plugin_to_upgrade" +version = "2.1.0" + "#; + + assert_eq!(config.document.to_string().trim(), expected.trim()); + } +}