Browse Source

feat(cli): handle known target specific plugins on permission add #10596 (#10598)

Closes #10596
Lucas Fernandes Nogueira 11 months ago
parent
commit
f35bcda289

+ 7 - 0
.changes/permissions-add-target-specific.md

@@ -0,0 +1,7 @@
+---
+"tauri-cli": patch:enhance
+"@tauri-apps/cli": patch:enhance
+---
+
+`permission add` and `add` commands now check if the plugin is known and if it is either desktop or mobile only
+we add the permission to a target-specific capability.

+ 101 - 6
tooling/cli/src/acl/permission/add.rs

@@ -31,14 +31,36 @@ impl TomlOrJson {
     }
   }
 
-  fn insert_permission(&mut self, idenitifer: String) {
+  fn platforms(&self) -> Option<Vec<&str>> {
+    match self {
+      TomlOrJson::Toml(t) => t.get("platforms").and_then(|k| {
+        k.as_array()
+          .and_then(|array| array.iter().map(|v| v.as_str()).collect())
+      }),
+      TomlOrJson::Json(j) => j.get("platforms").and_then(|k| {
+        if let Some(array) = k.as_array() {
+          let mut items = Vec::new();
+          for item in array {
+            if let Some(s) = item.as_str() {
+              items.push(s);
+            }
+          }
+          Some(items)
+        } else {
+          None
+        }
+      }),
+    }
+  }
+
+  fn insert_permission(&mut self, identifier: String) {
     match self {
       TomlOrJson::Toml(t) => {
         let permissions = t.entry("permissions").or_insert_with(|| {
           toml_edit::Item::Value(toml_edit::Value::Array(toml_edit::Array::new()))
         });
         if let Some(permissions) = permissions.as_array_mut() {
-          permissions.push(idenitifer)
+          permissions.push(identifier)
         };
       }
 
@@ -48,7 +70,7 @@ impl TomlOrJson {
             .entry("permissions")
             .or_insert_with(|| serde_json::Value::Array(Vec::new()));
           if let Some(permissions) = permissions.as_array_mut() {
-            permissions.push(serde_json::Value::String(idenitifer))
+            permissions.push(serde_json::Value::String(identifier))
           };
         }
       }
@@ -100,7 +122,13 @@ pub fn command(options: Options) -> Result<()> {
     );
   }
 
-  let capabilities = std::fs::read_dir(&capabilities_dir)?
+  let known_plugins = crate::helpers::plugins::known_plugins();
+  let known_plugin = options
+    .identifier
+    .split_once(':')
+    .and_then(|(plugin, _permission)| known_plugins.get(&plugin));
+
+  let capabilities_iter = std::fs::read_dir(&capabilities_dir)?
     .flatten()
     .filter(|e| e.file_type().map(|e| e.is_file()).unwrap_or_default())
     .filter_map(|e| {
@@ -109,8 +137,66 @@ pub fn command(options: Options) -> Result<()> {
         Some(c) => (c == capability.identifier()).then_some((capability, path)),
         None => Some((capability, path)),
       })
-    })
-    .collect::<Vec<_>>();
+    });
+
+  let (desktop_only, mobile_only) = known_plugin
+    .map(|p| (p.desktop_only, p.mobile_only))
+    .unwrap_or_default();
+
+  let expected_capability_config = if desktop_only {
+    Some((
+      vec![
+        tauri_utils::platform::Target::MacOS.to_string(),
+        tauri_utils::platform::Target::Windows.to_string(),
+        tauri_utils::platform::Target::Linux.to_string(),
+      ],
+      "desktop",
+    ))
+  } else if mobile_only {
+    Some((
+      vec![
+        tauri_utils::platform::Target::Android.to_string(),
+        tauri_utils::platform::Target::Ios.to_string(),
+      ],
+      "mobile",
+    ))
+  } else {
+    None
+  };
+
+  let capabilities = if let Some((expected_platforms, target_name)) = expected_capability_config {
+    let mut capabilities = capabilities_iter
+        .filter(|(capability, _path)| {
+          capability.platforms().map_or(
+            false, /* allows any target, so we should skip it since we're adding a target-specific plugin */
+            |platforms| {
+              // all platforms must be in the expected platforms list
+              platforms.iter().all(|p| expected_platforms.contains(&p.to_string()))
+            },
+          )
+        })
+        .collect::<Vec<_>>();
+
+    if capabilities.is_empty() {
+      let identifier = format!("{target_name}-capability");
+      let capability_path = capabilities_dir.join(target_name).with_extension("json");
+      log::info!(
+        "Capability matching platforms {expected_platforms:?} not found, creating {}",
+        capability_path.display()
+      );
+      capabilities.push((
+        TomlOrJson::Json(serde_json::json!({
+          "identifier": identifier,
+          "platforms": expected_platforms
+        })),
+        capability_path,
+      ));
+    }
+
+    capabilities
+  } else {
+    capabilities_iter.collect::<Vec<_>>()
+  };
 
   let mut capabilities = if capabilities.len() > 1 {
     let selections = prompts::multiselect(
@@ -132,6 +218,11 @@ pub fn command(options: Options) -> Result<()> {
         .as_slice(),
       None,
     )?;
+
+    if selections.is_empty() {
+      anyhow::bail!("You did not select any capabilities to update");
+    }
+
     selections
       .into_iter()
       .map(|idx| capabilities[idx].clone())
@@ -140,6 +231,10 @@ pub fn command(options: Options) -> Result<()> {
     capabilities
   };
 
+  if capabilities.is_empty() {
+    anyhow::bail!("Could not find a capability to update");
+  }
+
   for (capability, path) in &mut capabilities {
     capability.insert_permission(options.identifier.clone());
     std::fs::write(&*path, capability.to_string()?)?;

+ 2 - 56
tooling/cli/src/add.rs

@@ -16,61 +16,7 @@ use crate::{
   Result,
 };
 
-use std::{collections::HashMap, process::Command};
-
-#[derive(Default)]
-struct PluginMetadata {
-  desktop_only: bool,
-  mobile_only: bool,
-  rust_only: bool,
-  builder: bool,
-}
-
-// known plugins with particular cases
-fn plugins() -> HashMap<&'static str, PluginMetadata> {
-  let mut plugins: HashMap<&'static str, PluginMetadata> = HashMap::new();
-
-  // desktop-only
-  for p in [
-    "authenticator",
-    "autostart",
-    "cli",
-    "global-shortcut",
-    "positioner",
-    "single-instance",
-    "updater",
-    "window-state",
-  ] {
-    plugins.entry(p).or_default().desktop_only = true;
-  }
-
-  // mobile-only
-  for p in ["barcode-scanner", "biometric", "nfc"] {
-    plugins.entry(p).or_default().mobile_only = true;
-  }
-
-  // uses builder pattern
-  for p in [
-    "global-shortcut",
-    "localhost",
-    "log",
-    "sql",
-    "store",
-    "stronghold",
-    "updater",
-    "window-state",
-  ] {
-    plugins.entry(p).or_default().builder = true;
-  }
-
-  // rust-only
-  #[allow(clippy::single_element_loop)]
-  for p in ["localhost", "persisted-scope", "single-instance"] {
-    plugins.entry(p).or_default().rust_only = true;
-  }
-
-  plugins
-}
+use std::process::Command;
 
 #[derive(Debug, Parser)]
 #[clap(about = "Add a tauri plugin to the project")]
@@ -104,7 +50,7 @@ pub fn command(options: Options) -> Result<()> {
   let crate_name = format!("tauri-plugin-{plugin}");
   let npm_name = format!("@tauri-apps/plugin-{plugin}");
 
-  let mut plugins = plugins();
+  let mut plugins = crate::helpers::plugins::known_plugins();
   let metadata = plugins.remove(plugin).unwrap_or_default();
 
   let app_dir = resolve_app_dir();

+ 1 - 0
tooling/cli/src/helpers/mod.rs

@@ -9,6 +9,7 @@ pub mod config;
 pub mod flock;
 pub mod framework;
 pub mod npm;
+pub mod plugins;
 pub mod prompts;
 pub mod template;
 pub mod updater_signature;

+ 59 - 0
tooling/cli/src/helpers/plugins.rs

@@ -0,0 +1,59 @@
+// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use std::collections::HashMap;
+
+#[derive(Default)]
+pub struct PluginMetadata {
+  pub desktop_only: bool,
+  pub mobile_only: bool,
+  pub rust_only: bool,
+  pub builder: bool,
+}
+
+// known plugins with particular cases
+pub fn known_plugins() -> HashMap<&'static str, PluginMetadata> {
+  let mut plugins: HashMap<&'static str, PluginMetadata> = HashMap::new();
+
+  // desktop-only
+  for p in [
+    "authenticator",
+    "autostart",
+    "cli",
+    "global-shortcut",
+    "positioner",
+    "single-instance",
+    "updater",
+    "window-state",
+  ] {
+    plugins.entry(p).or_default().desktop_only = true;
+  }
+
+  // mobile-only
+  for p in ["barcode-scanner", "biometric", "nfc", "haptics"] {
+    plugins.entry(p).or_default().mobile_only = true;
+  }
+
+  // uses builder pattern
+  for p in [
+    "global-shortcut",
+    "localhost",
+    "log",
+    "sql",
+    "store",
+    "stronghold",
+    "updater",
+    "window-state",
+  ] {
+    plugins.entry(p).or_default().builder = true;
+  }
+
+  // rust-only
+  #[allow(clippy::single_element_loop)]
+  for p in ["localhost", "persisted-scope", "single-instance"] {
+    plugins.entry(p).or_default().rust_only = true;
+  }
+
+  plugins
+}