diff --git a/.changes/acl-default-permission-verification.md b/.changes/acl-default-permission-verification.md new file mode 100644 index 000000000..478b48a52 --- /dev/null +++ b/.changes/acl-default-permission-verification.md @@ -0,0 +1,6 @@ +--- +"tauri-build": patch:enhance +"tauri-utils": patch:enhance +--- + +Fallback to an empty permission set if the plugin did not define its `default` permissions. diff --git a/.changes/tauri-cli-add-default-perm.md b/.changes/tauri-cli-add-default-perm.md new file mode 100644 index 000000000..814a6d26b --- /dev/null +++ b/.changes/tauri-cli-add-default-perm.md @@ -0,0 +1,6 @@ +--- +'tauri-cli': 'patch:feat' +'@tauri-apps/cli': 'patch:feat' +--- + +Add default permission for a plugin to capabilities when using `tauri add `. diff --git a/core/tauri-build/src/acl.rs b/core/tauri-build/src/acl.rs index 240141254..ea0961a73 100644 --- a/core/tauri-build/src/acl.rs +++ b/core/tauri-build/src/acl.rs @@ -131,13 +131,14 @@ fn capabilities_schema(acl_manifests: &BTreeMap) -> RootSchema permission_schemas.push(schema_from(key, set_id, Some(&set.description))); } - if let Some(default) = &manifest.default_permission { - permission_schemas.push(schema_from( - key, - "default", - Some(default.description.as_ref()), - )); - } + permission_schemas.push(schema_from( + key, + "default", + manifest + .default_permission + .as_ref() + .map(|d| d.description.as_ref()), + )); for (permission_id, permission) in &manifest.permissions { permission_schemas.push(schema_from( @@ -198,9 +199,14 @@ fn capabilities_schema(acl_manifests: &BTreeMap) -> RootSchema }; let mut permission_schemas = Vec::new(); - if let Some(default) = &manifest.default_permission { - permission_schemas.push(schema_from(key, "default", Some(&default.description))); - } + permission_schemas.push(schema_from( + key, + "default", + manifest + .default_permission + .as_ref() + .map(|d| d.description.as_ref()), + )); for set in manifest.permission_sets.values() { permission_schemas.push(schema_from(key, &set.identifier, Some(&set.description))); } @@ -471,12 +477,10 @@ pub fn validate_capabilities( let permission_exists = acl_manifests .get(key) .map(|manifest| { - if permission_name == "default" { - manifest.default_permission.is_some() - } else { - manifest.permissions.contains_key(permission_name) - || manifest.permission_sets.contains_key(permission_name) - } + // the default permission is always treated as valid, the CLI automatically adds it on the `tauri add` command + permission_name == "default" + || manifest.permissions.contains_key(permission_name) + || manifest.permission_sets.contains_key(permission_name) }) .unwrap_or(false); diff --git a/core/tauri-utils/src/acl/resolved.rs b/core/tauri-utils/src/acl/resolved.rs index 081b4ec88..57c71e991 100644 --- a/core/tauri-utils/src/acl/resolved.rs +++ b/core/tauri-utils/src/acl/resolved.rs @@ -367,15 +367,8 @@ fn get_permissions<'a>( manifest .default_permission .as_ref() - .ok_or_else(|| Error::UnknownPermission { - key: if key == APP_ACL_KEY { - "app manifest".to_string() - } else { - key.to_string() - }, - permission: permission_name.to_string(), - }) - .and_then(|default| get_permission_set_permissions(manifest, default)) + .map(|default| get_permission_set_permissions(manifest, default)) + .unwrap_or_else(|| Ok(Vec::new())) } else if let Some(set) = manifest.permission_sets.get(permission_name) { get_permission_set_permissions(manifest, set) } else if let Some(permission) = manifest.permissions.get(permission_name) { diff --git a/tooling/cli/src/acl/permission/add.rs b/tooling/cli/src/acl/permission/add.rs index 4380aa94a..9370a2634 100644 --- a/tooling/cli/src/acl/permission/add.rs +++ b/tooling/cli/src/acl/permission/add.rs @@ -80,10 +80,10 @@ fn capability_from_path>(path: P) -> Option { #[derive(Debug, Parser)] #[clap(about = "Add a permission to capabilities")] pub struct Options { - /// Permission to remove. - identifier: String, + /// Permission to add. + pub identifier: String, /// Capability to add the permission to. - capability: Option, + pub capability: Option, } pub fn command(options: Options) -> Result<()> { @@ -114,7 +114,10 @@ pub fn command(options: Options) -> Result<()> { let mut capabilities = if capabilities.len() > 1 { let selections = prompts::multiselect( - "Choose which capabilities to add the permission to:", + &format!( + "Choose which capabilities to add the permission `{}` to:", + options.identifier + ), capabilities .iter() .map(|(c, p)| { diff --git a/tooling/cli/src/acl/permission/mod.rs b/tooling/cli/src/acl/permission/mod.rs index 3afbcc45d..e65295919 100644 --- a/tooling/cli/src/acl/permission/mod.rs +++ b/tooling/cli/src/acl/permission/mod.rs @@ -6,7 +6,7 @@ use clap::{Parser, Subcommand}; use crate::Result; -mod add; +pub mod add; mod ls; mod new; mod rm; diff --git a/tooling/cli/src/add.rs b/tooling/cli/src/add.rs index 4fd421ae5..b4f8c4761 100644 --- a/tooling/cli/src/add.rs +++ b/tooling/cli/src/add.rs @@ -2,15 +2,15 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use anyhow::Context; use clap::Parser; use colored::Colorize; use regex::Regex; use crate::{ + acl, helpers::{ app_paths::{app_dir, tauri_dir}, - cross_command, + cargo, npm::PackageManager, }, Result, @@ -18,165 +18,6 @@ use crate::{ use std::{collections::HashMap, process::Command}; -#[derive(Debug, Parser)] -#[clap(about = "Add a tauri plugin to the project")] -pub struct Options { - /// The plugin to add. - pub plugin: String, - /// Git tag to use. - #[clap(short, long)] - pub tag: Option, - /// Git rev to use. - #[clap(short, long)] - pub rev: Option, - /// Git branch to use. - #[clap(short, long)] - pub branch: Option, -} - -pub fn command(options: Options) -> Result<()> { - let plugin = options.plugin; - let plugin_snake_case = plugin.replace('-', "_"); - let crate_name = format!("tauri-plugin-{plugin}"); - let npm_name = format!("@tauri-apps/plugin-{plugin}"); - - let mut plugins = plugins(); - let metadata = plugins.remove(plugin.as_str()).unwrap_or_default(); - - let tauri_dir = tauri_dir(); - - let mut cargo = Command::new("cargo"); - cargo.current_dir(&tauri_dir).arg("add").arg(&crate_name); - - if options.tag.is_some() || options.rev.is_some() || options.branch.is_some() { - cargo - .arg("--git") - .arg("https://github.com/tauri-apps/plugins-workspace"); - } - - if metadata.desktop_only { - cargo - .arg("--target") - .arg(r#"cfg(not(any(target_os = "android", target_os = "ios")))"#); - } - - let npm_spec = match (options.tag, options.rev, options.branch) { - (Some(tag), None, None) => { - cargo.args(["--tag", &tag]); - format!("tauri-apps/tauri-plugin-{plugin}#{tag}") - } - (None, Some(rev), None) => { - cargo.args(["--rev", &rev]); - format!("tauri-apps/tauri-plugin-{plugin}#{rev}") - } - (None, None, Some(branch)) => { - cargo.args(["--branch", &branch]); - format!("tauri-apps/tauri-plugin-{plugin}#{branch}") - } - (None, None, None) => npm_name, - _ => anyhow::bail!("Only one of --tag, --rev and --branch can be specified"), - }; - - log::info!("Installing Cargo dependency {crate_name}..."); - let status = cargo.status().context("failed to run `cargo add`")?; - if !status.success() { - anyhow::bail!("Failed to install Cargo dependency"); - } - - if !metadata.rust_only { - if let Some(manager) = std::panic::catch_unwind(app_dir) - .map(Some) - .unwrap_or_default() - .map(PackageManager::from_project) - .and_then(|managers| managers.into_iter().next()) - { - let mut cmd = match manager { - PackageManager::Npm => cross_command("npm"), - PackageManager::Pnpm => cross_command("pnpm"), - PackageManager::Yarn => cross_command("yarn"), - PackageManager::YarnBerry => cross_command("yarn"), - PackageManager::Bun => cross_command("bun"), - }; - - cmd.arg("add").arg(&npm_spec); - - log::info!("Installing NPM dependency {npm_spec}..."); - let status = cmd - .status() - .with_context(|| format!("failed to run {manager}"))?; - if !status.success() { - anyhow::bail!("Failed to install NPM dependency"); - } - } - } - - // add plugin init code to main.rs or lib.rs - let plugin_init_fn = if plugin == "stronghold" { - "Builder::new(|pass| todo!()).build()" - } else if metadata.builder { - "Builder::new().build()" - } else { - "init()" - }; - let plugin_init = format!(".plugin(tauri_plugin_{plugin_snake_case}::{plugin_init_fn})"); - let re = Regex::new(r"(tauri\s*::\s*Builder\s*::\s*default\(\))(\s*)")?; - for file in [tauri_dir.join("src/main.rs"), tauri_dir.join("src/lib.rs")] { - let contents = std::fs::read_to_string(&file)?; - - if contents.contains(&plugin_init) { - log::info!( - "Plugin initialization code already found on {}", - file.display() - ); - return Ok(()); - } - - if re.is_match(&contents) { - let out = re.replace(&contents, format!("$1$2{plugin_init}$2")); - - log::info!("Adding plugin to {}", file.display()); - std::fs::write(file, out.as_bytes())?; - - // run cargo fmt - log::info!("Running `cargo fmt`..."); - let _ = Command::new("cargo") - .arg("fmt") - .current_dir(&tauri_dir) - .status(); - - return Ok(()); - } - } - - let builder_code = if metadata.builder { - format!(r#"+ .plugin(tauri_plugin_{plugin_snake_case}::Builder::new().build())"#,) - } else { - format!(r#"+ .plugin(tauri_plugin_{plugin_snake_case}::init())"#) - }; - - let rust_code = format!( - r#" {} -{} - {}"#, - "tauri::Builder::default()".dimmed(), - builder_code.normal().green(), - r#".invoke_handler(tauri::generate_handler![]) - .run(tauri::generate_context!()) - .expect("error while running tauri application");"# - .dimmed(), - ); - - log::warn!( - "Couldn't find `{}` in `{}` or `{}`, you must enable the plugin in your Rust code manually:\n\n{}", - "tauri::Builder".cyan(), - "main.rs".cyan(), - "lib.rs".cyan(), - rust_code - ); - - Ok(()) -} - #[derive(Default)] struct PluginMetadata { desktop_only: bool, @@ -221,3 +62,137 @@ fn plugins() -> HashMap<&'static str, PluginMetadata> { plugins } + +#[derive(Debug, Parser)] +#[clap(about = "Add a tauri plugin to the project")] +pub struct Options { + /// The plugin to add. + pub plugin: String, + /// Git tag to use. + #[clap(short, long)] + pub tag: Option, + /// Git rev to use. + #[clap(short, long)] + pub rev: Option, + /// Git branch to use. + #[clap(short, long)] + pub branch: Option, +} + +pub fn command(options: Options) -> Result<()> { + let plugin = options.plugin; + let plugin_snake_case = plugin.replace('-', "_"); + let crate_name = format!("tauri-plugin-{plugin}"); + let npm_name = format!("@tauri-apps/plugin-{plugin}"); + + let mut plugins = plugins(); + let metadata = plugins.remove(plugin.as_str()).unwrap_or_default(); + + let tauri_dir = tauri_dir(); + + cargo::install_one(cargo::CargoInstallOptions { + name: &crate_name, + branch: options.branch.as_deref(), + rev: options.rev.as_deref(), + tag: options.tag.as_deref(), + cwd: Some(&tauri_dir), + target: metadata + .desktop_only + .then_some(r#"cfg(not(any(target_os = "android", target_os = "ios")))"#), + })?; + + if !metadata.rust_only { + if let Some(manager) = std::panic::catch_unwind(app_dir) + .map(Some) + .unwrap_or_default() + .map(PackageManager::from_project) + .and_then(|managers| managers.into_iter().next()) + { + let npm_spec = match (options.tag, options.rev, options.branch) { + (Some(tag), None, None) => { + format!("tauri-apps/tauri-plugin-{plugin}#{tag}") + } + (None, Some(rev), None) => { + format!("tauri-apps/tauri-plugin-{plugin}#{rev}") + } + (None, None, Some(branch)) => { + format!("tauri-apps/tauri-plugin-{plugin}#{branch}") + } + (None, None, None) => npm_name, + _ => anyhow::bail!("Only one of --tag, --rev and --branch can be specified"), + }; + manager.install(&[npm_spec])?; + } + } + + let _ = acl::permission::add::command(acl::permission::add::Options { + identifier: format!("{plugin}:default"), + capability: None, + }); + + // add plugin init code to main.rs or lib.rs + let plugin_init_fn = if plugin == "stronghold" { + "Builder::new(|pass| todo!()).build()" + } else if metadata.builder { + "Builder::new().build()" + } else { + "init()" + }; + let plugin_init = format!(".plugin(tauri_plugin_{plugin_snake_case}::{plugin_init_fn})"); + + let re = Regex::new(r"(tauri\s*::\s*Builder\s*::\s*default\(\))(\s*)")?; + for file in [tauri_dir.join("src/main.rs"), tauri_dir.join("src/lib.rs")] { + let contents = std::fs::read_to_string(&file)?; + + if contents.contains(&plugin_init) { + log::info!( + "Plugin initialization code already found on {}", + file.display() + ); + return Ok(()); + } + + if re.is_match(&contents) { + let out = re.replace(&contents, format!("$1$2{plugin_init}$2")); + + log::info!("Adding plugin to {}", file.display()); + std::fs::write(file, out.as_bytes())?; + + // run cargo fmt + log::info!("Running `cargo fmt`..."); + let _ = Command::new("cargo") + .arg("fmt") + .current_dir(&tauri_dir) + .status(); + return Ok(()); + } + } + + let builder_code = if metadata.builder { + format!(r#"+ .plugin(tauri_plugin_{plugin_snake_case}::Builder::new().build())"#,) + } else { + format!(r#"+ .plugin(tauri_plugin_{plugin_snake_case}::init())"#) + }; + + let rust_code = format!( + r#" {} +{} + {}"#, + "tauri::Builder::default()".dimmed(), + builder_code.normal().green(), + r#".invoke_handler(tauri::generate_handler![]) + .run(tauri::generate_context!()) + .expect("error while running tauri application");"# + .dimmed(), + ); + + log::warn!( + "Couldn't find `{}` in `{}` or `{}`, you must enable the plugin in your Rust code manually:\n\n{}", + "tauri::Builder".cyan(), + "main.rs".cyan(), + "lib.rs".cyan(), + rust_code + ); + + Ok(()) +} diff --git a/tooling/cli/src/helpers/cargo.rs b/tooling/cli/src/helpers/cargo.rs new file mode 100644 index 000000000..c13e37a02 --- /dev/null +++ b/tooling/cli/src/helpers/cargo.rs @@ -0,0 +1,87 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::{path::Path, process::Command}; + +use anyhow::Context; + +#[derive(Debug, Default, Clone, Copy)] +pub struct CargoInstallOptions<'a> { + pub name: &'a str, + pub rev: Option<&'a str>, + pub tag: Option<&'a str>, + pub branch: Option<&'a str>, + pub cwd: Option<&'a std::path::Path>, + pub target: Option<&'a str>, +} + +pub fn install(dependencies: &[String], cwd: Option<&Path>) -> crate::Result<()> { + let dependencies_str = if dependencies.len() > 1 { + "dependencies" + } else { + "dependency" + }; + log::info!( + "Installing Cargo {dependencies_str} {}...", + dependencies + .iter() + .map(|d| format!("\"{d}\"")) + .collect::>() + .join(", ") + ); + + let mut cmd = Command::new("cargo"); + cmd.arg("add").args(dependencies); + + if let Some(cwd) = cwd { + cmd.current_dir(cwd); + } + + let status = cmd.status().with_context(|| "failed to run cargo")?; + + if !status.success() { + anyhow::bail!("Failed to install Cargo {dependencies_str}"); + } + + Ok(()) +} + +pub fn install_one(options: CargoInstallOptions) -> crate::Result<()> { + let mut cargo = Command::new("cargo"); + cargo.args(["add", options.name]); + + if options.tag.is_some() || options.rev.is_some() || options.branch.is_some() { + cargo.args(["--git", "https://github.com/tauri-apps/plugins-workspace"]); + } + + match (options.tag, options.rev, options.branch) { + (Some(tag), None, None) => { + cargo.args(["--tag", &tag]); + } + (None, Some(rev), None) => { + cargo.args(["--rev", &rev]); + } + (None, None, Some(branch)) => { + cargo.args(["--branch", &branch]); + } + (None, None, None) => {} + _ => anyhow::bail!("Only one of --tag, --rev and --branch can be specified"), + }; + + if let Some(target) = options.target { + cargo.args(["--target", target]); + } + + if let Some(cwd) = options.cwd { + cargo.current_dir(cwd); + } + + log::info!("Installing Cargo dependency \"{}\"...", options.name); + let status = cargo.status().context("failed to run `cargo add`")?; + if !status.success() { + anyhow::bail!("Failed to install Cargo dependency"); + } + + Ok(()) +} diff --git a/tooling/cli/src/helpers/mod.rs b/tooling/cli/src/helpers/mod.rs index be6238324..5256b3802 100644 --- a/tooling/cli/src/helpers/mod.rs +++ b/tooling/cli/src/helpers/mod.rs @@ -3,6 +3,7 @@ // SPDX-License-Identifier: MIT pub mod app_paths; +pub mod cargo; pub mod config; pub mod flock; pub mod framework; diff --git a/tooling/cli/src/helpers/npm.rs b/tooling/cli/src/helpers/npm.rs index 5f4890de3..37e527a31 100644 --- a/tooling/cli/src/helpers/npm.rs +++ b/tooling/cli/src/helpers/npm.rs @@ -2,8 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use crate::{helpers::cross_command, Result}; -use std::{fmt::Display, path::Path, process::ExitStatus}; +use anyhow::Context; + +use crate::helpers::cross_command; +use std::{fmt::Display, path::Path, process::Command}; #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum PackageManager { @@ -69,48 +71,42 @@ impl PackageManager { found } - pub fn install(&self, dependencies: &[String]) -> Result { + fn cross_command(&self) -> Command { match self { - PackageManager::Yarn => { - let mut cmd = cross_command("yarn"); - cmd - .arg("add") - .args(dependencies) - .status() - .map_err(Into::into) - } - PackageManager::YarnBerry => { - let mut cmd = cross_command("yarn"); - cmd - .arg("add") - .args(dependencies) - .status() - .map_err(Into::into) - } - PackageManager::Npm => { - let mut cmd = cross_command("npm"); - cmd - .arg("install") - .args(dependencies) - .status() - .map_err(Into::into) - } - PackageManager::Pnpm => { - let mut cmd = cross_command("pnpm"); - cmd - .arg("install") - .args(dependencies) - .status() - .map_err(Into::into) - } - PackageManager::Bun => { - let mut cmd = cross_command("bun"); - cmd - .arg("install") - .args(dependencies) - .status() - .map_err(Into::into) - } + PackageManager::Yarn => cross_command("yarn"), + PackageManager::YarnBerry => cross_command("yarn"), + PackageManager::Npm => cross_command("npm"), + PackageManager::Pnpm => cross_command("pnpm"), + PackageManager::Bun => cross_command("bun"), } } + + pub fn install(&self, dependencies: &[String]) -> crate::Result<()> { + let dependencies_str = if dependencies.len() > 1 { + "dependencies" + } else { + "dependency" + }; + log::info!( + "Installing NPM {dependencies_str} {}...", + dependencies + .iter() + .map(|d| format!("\"{d}\"")) + .collect::>() + .join(", ") + ); + + let status = self + .cross_command() + .arg("add") + .args(dependencies) + .status() + .with_context(|| format!("failed to run {self}"))?; + + if !status.success() { + anyhow::bail!("Failed to install NPM {dependencies_str}"); + } + + Ok(()) + } } diff --git a/tooling/cli/src/migrate/frontend.rs b/tooling/cli/src/migrate/frontend.rs index b97e25a50..0dd404339 100644 --- a/tooling/cli/src/migrate/frontend.rs +++ b/tooling/cli/src/migrate/frontend.rs @@ -3,14 +3,13 @@ // SPDX-License-Identifier: MIT use crate::{ - helpers::{app_paths::walk_builder, npm::PackageManager}, + helpers::{app_paths::walk_builder, cargo, npm::PackageManager}, Result, }; use std::{ fs::{read_to_string, write}, path::Path, - process::Command, }; const CORE_API_MODULES: &[&str] = &["dpi", "event", "path", "core", "window", "mocks"]; @@ -78,23 +77,11 @@ pub fn migrate(app_dir: &Path, tauri_dir: &Path) -> Result<()> { } if !new_npm_packages.is_empty() { - log::info!( - "Installing NPM packages for plugins: {}", - new_npm_packages.join(", ") - ); pm.install(&new_npm_packages)?; } if !new_cargo_packages.is_empty() { - log::info!( - "Installing Cargo dependencies for plugins: {}", - new_cargo_packages.join(", ") - ); - Command::new("cargo") - .arg("add") - .args(new_cargo_packages) - .current_dir(tauri_dir) - .status()?; + cargo::install(&new_cargo_packages, Some(tauri_dir))?; } Ok(())