mirror of
https://github.com/tauri-apps/tauri.git
synced 2024-11-28 03:47:37 +03:00
feat(cli): add new acl subcommands (#8827)
* unify `CI` var handling, and lay foundation for `permission` subcommand * feat(cli/init&new): create `permissions` directory by default for plugins * generate permissions with consistent pathing on windows and unix * `pemrission create` initial implementation * add ls command * finalize `permission create` subcommand * `permission rm` subcommand * `permission add` subcommand * remove empty `permission copy` subcommand * clippy * `capability create` subcommand and move modules under `acl` directory * fix multiselect for `permission add` when capabilty doesn't have identifier * clippy * `create` -> `new` and change file * license headers * more license headers * clippy * Discard changes to examples/resources/src-tauri/.gitignore * fix build * cleanup --------- Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
This commit is contained in:
parent
9be314f07a
commit
06d63d67a0
12
.changes/cli-acl-subcommands.md
Normal file
12
.changes/cli-acl-subcommands.md
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
'tauri-cli': 'patch:feat'
|
||||||
|
'@tauri-apps/cli': 'patch:feat'
|
||||||
|
---
|
||||||
|
|
||||||
|
Add new subcommands for managing permissions and cababilities:
|
||||||
|
|
||||||
|
- `tauri permission new`
|
||||||
|
- `tauri permission add`
|
||||||
|
- `tauri permission rm`
|
||||||
|
- `tauri permission ls`
|
||||||
|
- `tauri capability new`
|
@ -105,7 +105,7 @@ impl<'a> Builder<'a> {
|
|||||||
let _ = std::fs::remove_file(format!(
|
let _ = std::fs::remove_file(format!(
|
||||||
"./permissions/{}/{}",
|
"./permissions/{}/{}",
|
||||||
acl::build::PERMISSION_SCHEMAS_FOLDER_NAME,
|
acl::build::PERMISSION_SCHEMAS_FOLDER_NAME,
|
||||||
acl::build::PERMISSION_SCHEMA_FILE_NAME
|
acl::PERMISSION_SCHEMA_FILE_NAME
|
||||||
));
|
));
|
||||||
let _ = std::fs::remove_file(autogenerated.join(acl::build::PERMISSION_DOCS_FILE_NAME));
|
let _ = std::fs::remove_file(autogenerated.join(acl::build::PERMISSION_DOCS_FILE_NAME));
|
||||||
} else {
|
} else {
|
||||||
|
@ -20,6 +20,7 @@ use schemars::{
|
|||||||
use super::{
|
use super::{
|
||||||
capability::{Capability, CapabilityFile},
|
capability::{Capability, CapabilityFile},
|
||||||
plugin::PermissionFile,
|
plugin::PermissionFile,
|
||||||
|
PERMISSION_SCHEMA_FILE_NAME,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Known name of the folder containing autogenerated permissions.
|
/// Known name of the folder containing autogenerated permissions.
|
||||||
@ -37,9 +38,6 @@ pub const PERMISSION_FILE_EXTENSIONS: &[&str] = &["json", "toml"];
|
|||||||
/// Known foldername of the permission schema files
|
/// Known foldername of the permission schema files
|
||||||
pub const PERMISSION_SCHEMAS_FOLDER_NAME: &str = "schemas";
|
pub const PERMISSION_SCHEMAS_FOLDER_NAME: &str = "schemas";
|
||||||
|
|
||||||
/// Known filename of the permission schema JSON file
|
|
||||||
pub const PERMISSION_SCHEMA_FILE_NAME: &str = "schema.json";
|
|
||||||
|
|
||||||
/// Known filename of the permission documentation file
|
/// Known filename of the permission documentation file
|
||||||
pub const PERMISSION_DOCS_FILE_NAME: &str = "reference.md";
|
pub const PERMISSION_DOCS_FILE_NAME: &str = "reference.md";
|
||||||
|
|
||||||
|
@ -57,6 +57,7 @@ pub struct Capability {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub description: String,
|
pub description: String,
|
||||||
/// Configure remote URLs that can use the capability permissions.
|
/// Configure remote URLs that can use the capability permissions.
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub remote: Option<CapabilityRemote>,
|
pub remote: Option<CapabilityRemote>,
|
||||||
/// Whether this capability is enabled for local app URLs or not. Defaults to `true`.
|
/// Whether this capability is enabled for local app URLs or not. Defaults to `true`.
|
||||||
#[serde(default = "default_capability_local")]
|
#[serde(default = "default_capability_local")]
|
||||||
@ -74,7 +75,7 @@ pub struct Capability {
|
|||||||
/// List of permissions attached to this capability. Must include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`.
|
/// List of permissions attached to this capability. Must include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`.
|
||||||
pub permissions: Vec<PermissionEntry>,
|
pub permissions: Vec<PermissionEntry>,
|
||||||
/// Target platforms this capability applies. By default all platforms applies.
|
/// Target platforms this capability applies. By default all platforms applies.
|
||||||
#[serde(default = "default_platforms")]
|
#[serde(default = "default_platforms", skip_serializing_if = "Vec::is_empty")]
|
||||||
pub platforms: Vec<Target>,
|
pub platforms: Vec<Target>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,9 @@ use thiserror::Error;
|
|||||||
|
|
||||||
pub use self::{identifier::*, value::*};
|
pub use self::{identifier::*, value::*};
|
||||||
|
|
||||||
|
/// Known filename of the permission schema JSON file
|
||||||
|
pub const PERMISSION_SCHEMA_FILE_NAME: &str = "schema.json";
|
||||||
|
|
||||||
#[cfg(feature = "build")]
|
#[cfg(feature = "build")]
|
||||||
pub mod build;
|
pub mod build;
|
||||||
pub mod capability;
|
pub mod capability;
|
||||||
@ -142,6 +145,12 @@ pub struct Scopes {
|
|||||||
pub deny: Option<Vec<Value>>,
|
pub deny: Option<Vec<Value>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Scopes {
|
||||||
|
fn is_empty(&self) -> bool {
|
||||||
|
self.allow.is_none() && self.deny.is_none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Descriptions of explicit privileges of commands.
|
/// Descriptions of explicit privileges of commands.
|
||||||
///
|
///
|
||||||
/// It can enable commands to be accessible in the frontend of the application.
|
/// It can enable commands to be accessible in the frontend of the application.
|
||||||
@ -151,12 +160,14 @@ pub struct Scopes {
|
|||||||
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||||
pub struct Permission {
|
pub struct Permission {
|
||||||
/// The version of the permission.
|
/// The version of the permission.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub version: Option<NonZeroU64>,
|
pub version: Option<NonZeroU64>,
|
||||||
|
|
||||||
/// A unique identifier for the permission.
|
/// A unique identifier for the permission.
|
||||||
pub identifier: String,
|
pub identifier: String,
|
||||||
|
|
||||||
/// Human-readable description of what the permission does.
|
/// Human-readable description of what the permission does.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
|
|
||||||
/// Allowed or denied commands when using this permission.
|
/// Allowed or denied commands when using this permission.
|
||||||
@ -164,7 +175,7 @@ pub struct Permission {
|
|||||||
pub commands: Commands,
|
pub commands: Commands,
|
||||||
|
|
||||||
/// Allowed or denied scoped when using this permission.
|
/// Allowed or denied scoped when using this permission.
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Scopes::is_empty")]
|
||||||
pub scope: Scopes,
|
pub scope: Scopes,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
/// The default permission set of the plugin.
|
/// The default permission set of the plugin.
|
||||||
///
|
///
|
||||||
/// Works similarly to a permission with the "default" identifier.
|
/// Works similarly to a permission with the "default" identifier.
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||||
pub struct DefaultPermission {
|
pub struct DefaultPermission {
|
||||||
/// The version of the permission.
|
/// The version of the permission.
|
||||||
@ -26,14 +26,14 @@ pub struct DefaultPermission {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Permission file that can define a default permission, a set of permissions or a list of inlined permissions.
|
/// Permission file that can define a default permission, a set of permissions or a list of inlined permissions.
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||||
pub struct PermissionFile {
|
pub struct PermissionFile {
|
||||||
/// The default permission set for the plugin
|
/// The default permission set for the plugin
|
||||||
pub default: Option<DefaultPermission>,
|
pub default: Option<DefaultPermission>,
|
||||||
|
|
||||||
/// A list of permissions sets defined
|
/// A list of permissions sets defined
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
pub set: Vec<PermissionSet>,
|
pub set: Vec<PermissionSet>,
|
||||||
|
|
||||||
/// A list of inlined permissions
|
/// A list of inlined permissions
|
||||||
|
15
tooling/cli/Cargo.lock
generated
15
tooling/cli/Cargo.lock
generated
@ -4112,6 +4112,7 @@ version = "1.0.113"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79"
|
checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"indexmap 2.2.3",
|
||||||
"itoa 1.0.10",
|
"itoa 1.0.10",
|
||||||
"ryu",
|
"ryu",
|
||||||
"serde",
|
"serde",
|
||||||
@ -4697,6 +4698,7 @@ dependencies = [
|
|||||||
"ctrlc",
|
"ctrlc",
|
||||||
"dialoguer",
|
"dialoguer",
|
||||||
"duct",
|
"duct",
|
||||||
|
"dunce",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"glob",
|
"glob",
|
||||||
"handlebars 5.1.0",
|
"handlebars 5.1.0",
|
||||||
@ -4737,7 +4739,7 @@ dependencies = [
|
|||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml 0.8.10",
|
"toml 0.8.10",
|
||||||
"toml_edit 0.21.1",
|
"toml_edit 0.22.6",
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
"ureq",
|
"ureq",
|
||||||
"url",
|
"url",
|
||||||
@ -5103,17 +5105,6 @@ dependencies = [
|
|||||||
"winnow 0.5.40",
|
"winnow 0.5.40",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "toml_edit"
|
|
||||||
version = "0.21.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1"
|
|
||||||
dependencies = [
|
|
||||||
"indexmap 2.2.3",
|
|
||||||
"toml_datetime",
|
|
||||||
"winnow 0.5.40",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_edit"
|
name = "toml_edit"
|
||||||
version = "0.22.6"
|
version = "0.22.6"
|
||||||
|
@ -52,12 +52,12 @@ anyhow = "1.0"
|
|||||||
tauri-bundler = { version = "2.0.1-beta.0", default-features = false, path = "../bundler" }
|
tauri-bundler = { version = "2.0.1-beta.0", default-features = false, path = "../bundler" }
|
||||||
colored = "2.0"
|
colored = "2.0"
|
||||||
serde = { version = "1.0", features = [ "derive" ] }
|
serde = { version = "1.0", features = [ "derive" ] }
|
||||||
serde_json = "1.0"
|
serde_json = { version = "1.0", features = [ "preserve_order" ] }
|
||||||
notify = "6.1"
|
notify = "6.1"
|
||||||
notify-debouncer-mini = "0.4"
|
notify-debouncer-mini = "0.4"
|
||||||
shared_child = "1.0"
|
shared_child = "1.0"
|
||||||
duct = "0.13"
|
duct = "0.13"
|
||||||
toml_edit = "0.21"
|
toml_edit = { version = "0.22", features = [ "serde" ] }
|
||||||
json-patch = "1.2"
|
json-patch = "1.2"
|
||||||
tauri-utils = { version = "2.0.0-beta.4", path = "../../core/tauri-utils", features = [ "isolation", "schema", "config-json5", "config-toml" ] }
|
tauri-utils = { version = "2.0.0-beta.4", path = "../../core/tauri-utils", features = [ "isolation", "schema", "config-json5", "config-toml" ] }
|
||||||
tauri-utils-v1 = { version = "1", package = "tauri-utils", features = [ "isolation", "schema", "config-json5", "config-toml" ] }
|
tauri-utils-v1 = { version = "1", package = "tauri-utils", features = [ "isolation", "schema", "config-json5", "config-toml" ] }
|
||||||
@ -93,6 +93,7 @@ itertools = "0.11"
|
|||||||
local-ip-address = "0.5"
|
local-ip-address = "0.5"
|
||||||
css-color = "0.2"
|
css-color = "0.2"
|
||||||
resvg = "0.36.0"
|
resvg = "0.36.0"
|
||||||
|
dunce = "1"
|
||||||
glob = "0.3"
|
glob = "0.3"
|
||||||
|
|
||||||
[target."cfg(windows)".dependencies]
|
[target."cfg(windows)".dependencies]
|
||||||
|
28
tooling/cli/src/acl/capability/mod.rs
Normal file
28
tooling/cli/src/acl/capability/mod.rs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
use clap::{Parser, Subcommand};
|
||||||
|
|
||||||
|
use crate::Result;
|
||||||
|
|
||||||
|
mod new;
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
#[clap(about = "Manage or create capabilities for your app")]
|
||||||
|
pub struct Cli {
|
||||||
|
#[clap(subcommand)]
|
||||||
|
command: Commands,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand, Debug)]
|
||||||
|
enum Commands {
|
||||||
|
#[clap(alias = "create")]
|
||||||
|
New(new::Options),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn command(cli: Cli) -> Result<()> {
|
||||||
|
match cli.command {
|
||||||
|
Commands::New(options) => new::command(options),
|
||||||
|
}
|
||||||
|
}
|
141
tooling/cli/src/acl/capability/new.rs
Normal file
141
tooling/cli/src/acl/capability/new.rs
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
use std::{collections::HashSet, path::PathBuf};
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use tauri_utils::acl::capability::{Capability, PermissionEntry};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
acl::FileFormat,
|
||||||
|
helpers::{app_paths::tauri_dir, prompts},
|
||||||
|
Result,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
#[clap(about = "Create a new permission file")]
|
||||||
|
pub struct Options {
|
||||||
|
/// Capability identifier.
|
||||||
|
identifier: Option<String>,
|
||||||
|
/// Capability description
|
||||||
|
#[clap(long)]
|
||||||
|
description: Option<String>,
|
||||||
|
/// Capability windows
|
||||||
|
#[clap(long)]
|
||||||
|
windows: Option<Vec<String>>,
|
||||||
|
/// Capability permissions
|
||||||
|
#[clap(long)]
|
||||||
|
permission: Option<Vec<String>>,
|
||||||
|
/// Output file format.
|
||||||
|
#[clap(long, default_value_t = FileFormat::Json)]
|
||||||
|
format: FileFormat,
|
||||||
|
/// The output file.
|
||||||
|
#[clap(short, long)]
|
||||||
|
out: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn command(options: Options) -> Result<()> {
|
||||||
|
let identifier = match options.identifier {
|
||||||
|
Some(i) => i,
|
||||||
|
None => prompts::input("What's the capability identifier?", None, false, false)?.unwrap(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let description = match options.description {
|
||||||
|
Some(d) => Some(d),
|
||||||
|
None => prompts::input::<String>("What's the capability description?", None, false, true)?
|
||||||
|
.and_then(|d| if d.is_empty() { None } else { Some(d) }),
|
||||||
|
};
|
||||||
|
|
||||||
|
let windows = match options.windows.map(FromIterator::from_iter) {
|
||||||
|
Some(w) => w,
|
||||||
|
None => prompts::input::<String>(
|
||||||
|
"Which windows should be affected by this? (comma separated)",
|
||||||
|
Some("main".into()),
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
)?
|
||||||
|
.and_then(|d| {
|
||||||
|
if d.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(d.split(',').map(ToString::to_string).collect())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or_default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let permissions: HashSet<String> = match options.permission.map(FromIterator::from_iter) {
|
||||||
|
Some(p) => p,
|
||||||
|
None => prompts::input::<String>(
|
||||||
|
"What permissions to enable? (comma separated)",
|
||||||
|
None,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
)?
|
||||||
|
.and_then(|p| {
|
||||||
|
if p.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(p.split(',').map(ToString::to_string).collect())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or_default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let capability = Capability {
|
||||||
|
identifier,
|
||||||
|
description: description.unwrap_or_default(),
|
||||||
|
remote: None,
|
||||||
|
local: true,
|
||||||
|
windows,
|
||||||
|
webviews: Vec::new(),
|
||||||
|
permissions: permissions
|
||||||
|
.into_iter()
|
||||||
|
.map(|p| {
|
||||||
|
PermissionEntry::PermissionRef(
|
||||||
|
p.clone()
|
||||||
|
.try_into()
|
||||||
|
.unwrap_or_else(|_| panic!("invalid permission {}", p)),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
platforms: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let path = match options.out {
|
||||||
|
Some(o) => o.canonicalize()?,
|
||||||
|
None => {
|
||||||
|
let dir = tauri_dir();
|
||||||
|
let capabilities_dir = dir.join("capabilities");
|
||||||
|
capabilities_dir.join(format!(
|
||||||
|
"{}.{}",
|
||||||
|
capability.identifier,
|
||||||
|
options.format.extension()
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if path.exists() {
|
||||||
|
let msg = format!(
|
||||||
|
"Capability already exists at {}",
|
||||||
|
dunce::simplified(&path).display()
|
||||||
|
);
|
||||||
|
let overwrite = prompts::confirm(&format!("{msg}, overwrite?"), Some(false))?;
|
||||||
|
if overwrite {
|
||||||
|
std::fs::remove_file(&path)?;
|
||||||
|
} else {
|
||||||
|
anyhow::bail!(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(parent) = path.parent() {
|
||||||
|
std::fs::create_dir_all(parent)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::fs::write(&path, options.format.serialize(&capability)?)?;
|
||||||
|
|
||||||
|
log::info!(action = "Created"; "capability at {}", dunce::simplified(&path).display());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
41
tooling/cli/src/acl/mod.rs
Normal file
41
tooling/cli/src/acl/mod.rs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
use serde::Serialize;
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
pub mod capability;
|
||||||
|
pub mod permission;
|
||||||
|
|
||||||
|
#[derive(Debug, clap::ValueEnum, Clone)]
|
||||||
|
enum FileFormat {
|
||||||
|
Json,
|
||||||
|
Toml,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for FileFormat {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Json => write!(f, "json"),
|
||||||
|
Self::Toml => write!(f, "toml"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileFormat {
|
||||||
|
pub fn extension(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Json => "json",
|
||||||
|
Self::Toml => "toml",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn serialize<S: Serialize>(&self, s: &S) -> crate::Result<String> {
|
||||||
|
let contents = match self {
|
||||||
|
Self::Json => serde_json::to_string_pretty(s)?,
|
||||||
|
Self::Toml => toml_edit::ser::to_string_pretty(s)?,
|
||||||
|
};
|
||||||
|
Ok(contents)
|
||||||
|
}
|
||||||
|
}
|
147
tooling/cli/src/acl/permission/add.rs
Normal file
147
tooling/cli/src/acl/permission/add.rs
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
helpers::{app_paths::tauri_dir_opt, prompts},
|
||||||
|
Result,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum TomlOrJson {
|
||||||
|
Toml(toml_edit::Document),
|
||||||
|
Json(serde_json::Value),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TomlOrJson {
|
||||||
|
fn identifier(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
TomlOrJson::Toml(t) => t
|
||||||
|
.get("identifier")
|
||||||
|
.and_then(|k| k.as_str())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
TomlOrJson::Json(j) => j
|
||||||
|
.get("identifier")
|
||||||
|
.and_then(|k| k.as_str())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_permission(&mut self, idenitifer: 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)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
TomlOrJson::Json(j) => {
|
||||||
|
if let Some(o) = j.as_object_mut() {
|
||||||
|
let permissions = o
|
||||||
|
.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))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_string(&self) -> Result<String> {
|
||||||
|
Ok(match self {
|
||||||
|
TomlOrJson::Toml(t) => t.to_string(),
|
||||||
|
TomlOrJson::Json(j) => serde_json::to_string_pretty(&j)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn capability_from_path<P: AsRef<Path>>(path: P) -> Option<TomlOrJson> {
|
||||||
|
match path.as_ref().extension().and_then(|o| o.to_str()) {
|
||||||
|
Some("toml") => std::fs::read_to_string(&path)
|
||||||
|
.ok()
|
||||||
|
.and_then(|c| c.parse::<toml_edit::Document>().ok())
|
||||||
|
.map(TomlOrJson::Toml),
|
||||||
|
Some("json") => std::fs::read(&path)
|
||||||
|
.ok()
|
||||||
|
.and_then(|c| serde_json::from_slice::<serde_json::Value>(&c).ok())
|
||||||
|
.map(TomlOrJson::Json),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
#[clap(about = "Add a permission to capabilities")]
|
||||||
|
pub struct Options {
|
||||||
|
/// Permission to remove.
|
||||||
|
identifier: String,
|
||||||
|
/// Capability to add the permission to.
|
||||||
|
capability: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn command(options: Options) -> Result<()> {
|
||||||
|
let dir = match tauri_dir_opt() {
|
||||||
|
Some(t) => t,
|
||||||
|
None => std::env::current_dir()?,
|
||||||
|
};
|
||||||
|
|
||||||
|
let capabilities_dir = dir.join("capabilities");
|
||||||
|
if !capabilities_dir.exists() {
|
||||||
|
anyhow::bail!(
|
||||||
|
"Couldn't find capabilities directory at {}",
|
||||||
|
dunce::simplified(&capabilities_dir).display()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let capabilities = std::fs::read_dir(&capabilities_dir)?
|
||||||
|
.flatten()
|
||||||
|
.filter(|e| e.file_type().map(|e| e.is_file()).unwrap_or_default())
|
||||||
|
.filter_map(|e| {
|
||||||
|
let path = e.path();
|
||||||
|
capability_from_path(&path).and_then(|capability| match &options.capability {
|
||||||
|
Some(c) => (c == capability.identifier()).then_some((capability, path)),
|
||||||
|
None => Some((capability, path)),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let mut capabilities = if capabilities.len() > 1 {
|
||||||
|
let selections = prompts::multiselect(
|
||||||
|
"Choose which capabilities to add the permission to:",
|
||||||
|
capabilities
|
||||||
|
.iter()
|
||||||
|
.map(|(c, p)| {
|
||||||
|
let id = c.identifier();
|
||||||
|
if id.is_empty() {
|
||||||
|
dunce::simplified(p).to_str().unwrap_or_default()
|
||||||
|
} else {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.as_slice(),
|
||||||
|
None,
|
||||||
|
)?;
|
||||||
|
selections
|
||||||
|
.into_iter()
|
||||||
|
.map(|idx| capabilities[idx].clone())
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
capabilities
|
||||||
|
};
|
||||||
|
|
||||||
|
for (capability, path) in &mut capabilities {
|
||||||
|
capability.insert_permission(options.identifier.clone());
|
||||||
|
std::fs::write(&path, capability.to_string()?)?;
|
||||||
|
log::info!(action = "Added"; "permission `{}` to `{}` at {}", options.identifier, capability.identifier(), dunce::simplified(path).display());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
148
tooling/cli/src/acl/permission/ls.rs
Normal file
148
tooling/cli/src/acl/permission/ls.rs
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
use crate::{helpers::app_paths::tauri_dir, Result};
|
||||||
|
use colored::Colorize;
|
||||||
|
use tauri_utils::acl::plugin::Manifest;
|
||||||
|
|
||||||
|
use std::{collections::BTreeMap, fs::read_to_string};
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
#[clap(about = "List permissions available to your application")]
|
||||||
|
pub struct Options {
|
||||||
|
/// Name of the plugin to list permissions.
|
||||||
|
plugin: Option<String>,
|
||||||
|
/// Permission identifier filter.
|
||||||
|
#[clap(short, long)]
|
||||||
|
filter: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn command(options: Options) -> Result<()> {
|
||||||
|
let tauri_dir = tauri_dir();
|
||||||
|
let plugin_manifests_path = tauri_dir
|
||||||
|
.join("gen")
|
||||||
|
.join("schemas")
|
||||||
|
.join("plugin-manifests.json");
|
||||||
|
|
||||||
|
if plugin_manifests_path.exists() {
|
||||||
|
let plugin_manifest_json = read_to_string(&plugin_manifests_path)?;
|
||||||
|
let acl = serde_json::from_str::<BTreeMap<String, Manifest>>(&plugin_manifest_json)?;
|
||||||
|
|
||||||
|
for (plugin, manifest) in acl {
|
||||||
|
if options
|
||||||
|
.plugin
|
||||||
|
.as_ref()
|
||||||
|
.map(|p| p != &plugin)
|
||||||
|
.unwrap_or_default()
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut permissions = Vec::new();
|
||||||
|
|
||||||
|
if let Some(default) = manifest.default_permission {
|
||||||
|
if options
|
||||||
|
.filter
|
||||||
|
.as_ref()
|
||||||
|
.map(|f| "default".contains(f))
|
||||||
|
.unwrap_or(true)
|
||||||
|
{
|
||||||
|
permissions.push(format!(
|
||||||
|
"{}:{}\n{}\nPermissions: {}",
|
||||||
|
plugin.magenta(),
|
||||||
|
"default".cyan(),
|
||||||
|
default.description,
|
||||||
|
default
|
||||||
|
.permissions
|
||||||
|
.iter()
|
||||||
|
.map(|c| c.cyan().to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for set in manifest.permission_sets.values() {
|
||||||
|
if options
|
||||||
|
.filter
|
||||||
|
.as_ref()
|
||||||
|
.map(|f| set.identifier.contains(f))
|
||||||
|
.unwrap_or(true)
|
||||||
|
{
|
||||||
|
permissions.push(format!(
|
||||||
|
"{}:{}\n{}\nPermissions: {}",
|
||||||
|
plugin.magenta(),
|
||||||
|
set.identifier.cyan(),
|
||||||
|
set.description,
|
||||||
|
set
|
||||||
|
.permissions
|
||||||
|
.iter()
|
||||||
|
.map(|c| c.cyan().to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for permission in manifest.permissions.into_values() {
|
||||||
|
if options
|
||||||
|
.filter
|
||||||
|
.as_ref()
|
||||||
|
.map(|f| permission.identifier.contains(f))
|
||||||
|
.unwrap_or(true)
|
||||||
|
{
|
||||||
|
permissions.push(format!(
|
||||||
|
"{}:{}{}{}{}",
|
||||||
|
plugin.magenta(),
|
||||||
|
permission.identifier.cyan(),
|
||||||
|
permission
|
||||||
|
.description
|
||||||
|
.map(|d| format!("\n{d}"))
|
||||||
|
.unwrap_or_default(),
|
||||||
|
if permission.commands.allow.is_empty() {
|
||||||
|
"".to_string()
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"\n{}: {}",
|
||||||
|
"Allow commands".bold(),
|
||||||
|
permission
|
||||||
|
.commands
|
||||||
|
.allow
|
||||||
|
.iter()
|
||||||
|
.map(|c| c.green().to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ")
|
||||||
|
)
|
||||||
|
},
|
||||||
|
if permission.commands.deny.is_empty() {
|
||||||
|
"".to_string()
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"\n{}: {}",
|
||||||
|
"Deny commands".bold(),
|
||||||
|
permission
|
||||||
|
.commands
|
||||||
|
.deny
|
||||||
|
.iter()
|
||||||
|
.map(|c| c.red().to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ")
|
||||||
|
)
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !permissions.is_empty() {
|
||||||
|
println!("{}\n", permissions.join("\n\n"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
anyhow::bail!("permission file not found, please build your application once first")
|
||||||
|
}
|
||||||
|
}
|
39
tooling/cli/src/acl/permission/mod.rs
Normal file
39
tooling/cli/src/acl/permission/mod.rs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
use clap::{Parser, Subcommand};
|
||||||
|
|
||||||
|
use crate::Result;
|
||||||
|
|
||||||
|
mod add;
|
||||||
|
mod ls;
|
||||||
|
mod new;
|
||||||
|
mod rm;
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
#[clap(about = "Manage or create permissions for your app or plugin")]
|
||||||
|
pub struct Cli {
|
||||||
|
#[clap(subcommand)]
|
||||||
|
command: Commands,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand, Debug)]
|
||||||
|
enum Commands {
|
||||||
|
#[clap(alias = "create")]
|
||||||
|
New(new::Options),
|
||||||
|
Add(add::Options),
|
||||||
|
#[clap(alias = "remove")]
|
||||||
|
Rm(rm::Options),
|
||||||
|
#[clap(alias = "list")]
|
||||||
|
Ls(ls::Options),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn command(cli: Cli) -> Result<()> {
|
||||||
|
match cli.command {
|
||||||
|
Commands::New(options) => new::command(options),
|
||||||
|
Commands::Add(options) => add::command(options),
|
||||||
|
Commands::Rm(options) => rm::command(options),
|
||||||
|
Commands::Ls(options) => ls::command(options),
|
||||||
|
}
|
||||||
|
}
|
113
tooling/cli/src/acl/permission/new.rs
Normal file
113
tooling/cli/src/acl/permission/new.rs
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
acl::FileFormat,
|
||||||
|
helpers::{app_paths::tauri_dir_opt, prompts},
|
||||||
|
Result,
|
||||||
|
};
|
||||||
|
|
||||||
|
use tauri_utils::acl::{plugin::PermissionFile, Commands, Permission};
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
#[clap(about = "Create a new permission file")]
|
||||||
|
pub struct Options {
|
||||||
|
/// Permission identifier.
|
||||||
|
identifier: Option<String>,
|
||||||
|
/// Permission description
|
||||||
|
#[clap(long)]
|
||||||
|
description: Option<String>,
|
||||||
|
/// List of commands to allow
|
||||||
|
#[clap(short, long, use_value_delimiter = true)]
|
||||||
|
allow: Option<Vec<String>>,
|
||||||
|
/// List of commands to deny
|
||||||
|
#[clap(short, long, use_value_delimiter = true)]
|
||||||
|
deny: Option<Vec<String>>,
|
||||||
|
/// Output file format.
|
||||||
|
#[clap(long, default_value_t = FileFormat::Json)]
|
||||||
|
format: FileFormat,
|
||||||
|
/// The output file.
|
||||||
|
#[clap(short, long)]
|
||||||
|
out: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn command(options: Options) -> Result<()> {
|
||||||
|
let identifier = match options.identifier {
|
||||||
|
Some(i) => i,
|
||||||
|
None => prompts::input("What's the permission identifier?", None, false, false)?.unwrap(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let description = match options.description {
|
||||||
|
Some(d) => Some(d),
|
||||||
|
None => prompts::input::<String>("What's the permission description?", None, false, true)?
|
||||||
|
.and_then(|d| if d.is_empty() { None } else { Some(d) }),
|
||||||
|
};
|
||||||
|
|
||||||
|
let allow: Vec<String> = options
|
||||||
|
.allow
|
||||||
|
.map(FromIterator::from_iter)
|
||||||
|
.unwrap_or_default();
|
||||||
|
let deny: Vec<String> = options
|
||||||
|
.deny
|
||||||
|
.map(FromIterator::from_iter)
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let permission = Permission {
|
||||||
|
version: None,
|
||||||
|
identifier,
|
||||||
|
description,
|
||||||
|
commands: Commands { allow, deny },
|
||||||
|
scope: Default::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let path = match options.out {
|
||||||
|
Some(o) => o.canonicalize()?,
|
||||||
|
None => {
|
||||||
|
let dir = match tauri_dir_opt() {
|
||||||
|
Some(t) => t,
|
||||||
|
None => std::env::current_dir()?,
|
||||||
|
};
|
||||||
|
let permissions_dir = dir.join("permissions");
|
||||||
|
permissions_dir.join(format!(
|
||||||
|
"{}.{}",
|
||||||
|
permission.identifier,
|
||||||
|
options.format.extension()
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if path.exists() {
|
||||||
|
let msg = format!(
|
||||||
|
"Permission already exists at {}",
|
||||||
|
dunce::simplified(&path).display()
|
||||||
|
);
|
||||||
|
let overwrite = prompts::confirm(&format!("{msg}, overwrite?"), Some(false))?;
|
||||||
|
if overwrite {
|
||||||
|
std::fs::remove_file(&path)?;
|
||||||
|
} else {
|
||||||
|
anyhow::bail!(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(parent) = path.parent() {
|
||||||
|
std::fs::create_dir_all(parent)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::fs::write(
|
||||||
|
&path,
|
||||||
|
options.format.serialize(&PermissionFile {
|
||||||
|
default: None,
|
||||||
|
set: Vec::new(),
|
||||||
|
permission: vec![permission],
|
||||||
|
})?,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
log::info!(action = "Created"; "permission at {}", dunce::simplified(&path).display());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
137
tooling/cli/src/acl/permission/rm.rs
Normal file
137
tooling/cli/src/acl/permission/rm.rs
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use tauri_utils::acl::{plugin::PermissionFile, PERMISSION_SCHEMA_FILE_NAME};
|
||||||
|
|
||||||
|
use crate::{acl::FileFormat, helpers::app_paths::tauri_dir_opt, Result};
|
||||||
|
|
||||||
|
fn rm_permission_files(identifier: &str, dir: &Path) -> Result<()> {
|
||||||
|
for entry in std::fs::read_dir(dir)?.flatten() {
|
||||||
|
let file_type = entry.file_type()?;
|
||||||
|
let path = entry.path();
|
||||||
|
if file_type.is_dir() {
|
||||||
|
rm_permission_files(identifier, &path)?;
|
||||||
|
} else {
|
||||||
|
if path
|
||||||
|
.file_name()
|
||||||
|
.map(|name| name == PERMISSION_SCHEMA_FILE_NAME)
|
||||||
|
.unwrap_or_default()
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (mut permission_file, format): (PermissionFile, FileFormat) =
|
||||||
|
match path.extension().and_then(|o| o.to_str()) {
|
||||||
|
Some("toml") => {
|
||||||
|
let content = std::fs::read_to_string(&path)?;
|
||||||
|
(toml::from_str(&content)?, FileFormat::Toml)
|
||||||
|
}
|
||||||
|
Some("json") => {
|
||||||
|
let content = std::fs::read(&path)?;
|
||||||
|
(serde_json::from_slice(&content)?, FileFormat::Json)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut updated;
|
||||||
|
|
||||||
|
if identifier == "default" {
|
||||||
|
updated = permission_file.default.is_some();
|
||||||
|
permission_file.default = None;
|
||||||
|
} else {
|
||||||
|
let set_len = permission_file.set.len();
|
||||||
|
permission_file.set.retain(|s| s.identifier != identifier);
|
||||||
|
updated = permission_file.set.len() != set_len;
|
||||||
|
|
||||||
|
let permission_len = permission_file.permission.len();
|
||||||
|
permission_file
|
||||||
|
.permission
|
||||||
|
.retain(|s| s.identifier != identifier);
|
||||||
|
updated = updated || permission_file.permission.len() != permission_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the file is empty, let's remove it
|
||||||
|
if permission_file.default.is_none()
|
||||||
|
&& permission_file.set.is_empty()
|
||||||
|
&& permission_file.permission.is_empty()
|
||||||
|
{
|
||||||
|
std::fs::remove_file(&path)?;
|
||||||
|
log::info!(action = "Removed"; "file {}", dunce::simplified(&path).display());
|
||||||
|
} else if updated {
|
||||||
|
std::fs::write(&path, format.serialize(&permission_file)?)?;
|
||||||
|
log::info!(action = "Removed"; "permission {identifier} from {}", dunce::simplified(&path).display());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rm_permission_from_capabilities(identifier: &str, dir: &Path) -> Result<()> {
|
||||||
|
for entry in std::fs::read_dir(dir)?.flatten() {
|
||||||
|
let file_type = entry.file_type()?;
|
||||||
|
if file_type.is_file() {
|
||||||
|
let path = entry.path();
|
||||||
|
match path.extension().and_then(|o| o.to_str()) {
|
||||||
|
Some("toml") => {
|
||||||
|
let content = std::fs::read_to_string(&path)?;
|
||||||
|
if let Ok(mut value) = content.parse::<toml_edit::Document>() {
|
||||||
|
if let Some(permissions) = value.get_mut("permissions").and_then(|p| p.as_array_mut()) {
|
||||||
|
let prev_len = permissions.len();
|
||||||
|
permissions.retain(|p| p.as_str().map(|p| p != identifier).unwrap_or(false));
|
||||||
|
if prev_len != permissions.len() {
|
||||||
|
std::fs::write(&path, value.to_string())?;
|
||||||
|
log::info!(action = "Removed"; "permission from capability at {}", dunce::simplified(&path).display());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some("json") => {
|
||||||
|
let content = std::fs::read(&path)?;
|
||||||
|
if let Ok(mut value) = serde_json::from_slice::<serde_json::Value>(&content) {
|
||||||
|
if let Some(permissions) = value.get_mut("permissions").and_then(|p| p.as_array_mut()) {
|
||||||
|
let prev_len = permissions.len();
|
||||||
|
permissions.retain(|p| p.as_str().map(|p| p != identifier).unwrap_or(false));
|
||||||
|
if prev_len != permissions.len() {
|
||||||
|
std::fs::write(&path, serde_json::to_vec_pretty(&value)?)?;
|
||||||
|
log::info!(action = "Removed"; "permission from capability at {}", dunce::simplified(&path).display());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
#[clap(about = "Remove a permission file, and its reference from any capability")]
|
||||||
|
pub struct Options {
|
||||||
|
/// Permission to remove.
|
||||||
|
identifier: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn command(options: Options) -> Result<()> {
|
||||||
|
let permissions_dir = std::env::current_dir()?.join("permissions");
|
||||||
|
if permissions_dir.exists() {
|
||||||
|
rm_permission_files(&options.identifier, &permissions_dir)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(tauri_dir) = tauri_dir_opt() {
|
||||||
|
let capabilities_dir = tauri_dir.join("capabilities");
|
||||||
|
if capabilities_dir.exists() {
|
||||||
|
rm_permission_from_capabilities(&options.identifier, &capabilities_dir)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -60,12 +60,11 @@ pub struct Options {
|
|||||||
/// Command line arguments passed to the runner. Use `--` to explicitly mark the start of the arguments.
|
/// Command line arguments passed to the runner. Use `--` to explicitly mark the start of the arguments.
|
||||||
pub args: Vec<String>,
|
pub args: Vec<String>,
|
||||||
/// Skip prompting for values
|
/// Skip prompting for values
|
||||||
#[clap(long)]
|
#[clap(long, env = "CI")]
|
||||||
pub ci: bool,
|
pub ci: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn command(mut options: Options, verbosity: u8) -> Result<()> {
|
pub fn command(mut options: Options, verbosity: u8) -> Result<()> {
|
||||||
options.ci = options.ci || std::env::var("CI").is_ok();
|
|
||||||
let ci = options.ci;
|
let ci = options.ci;
|
||||||
|
|
||||||
let target = options
|
let target = options
|
||||||
|
@ -66,14 +66,16 @@ fn lookup<F: Fn(&PathBuf) -> bool>(dir: &Path, checker: F) -> Option<PathBuf> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_tauri_dir() -> PathBuf {
|
pub fn tauri_dir_opt() -> Option<PathBuf> {
|
||||||
let cwd = current_dir().expect("failed to read cwd");
|
let Ok(cwd) = current_dir() else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
if cwd.join(ConfigFormat::Json.into_file_name()).exists()
|
if cwd.join(ConfigFormat::Json.into_file_name()).exists()
|
||||||
|| cwd.join(ConfigFormat::Json5.into_file_name()).exists()
|
|| cwd.join(ConfigFormat::Json5.into_file_name()).exists()
|
||||||
|| cwd.join(ConfigFormat::Toml.into_file_name()).exists()
|
|| cwd.join(ConfigFormat::Toml.into_file_name()).exists()
|
||||||
{
|
{
|
||||||
return cwd;
|
return Some(cwd);
|
||||||
}
|
}
|
||||||
|
|
||||||
let src_tauri = cwd.join("src-tauri");
|
let src_tauri = cwd.join("src-tauri");
|
||||||
@ -83,12 +85,23 @@ fn get_tauri_dir() -> PathBuf {
|
|||||||
.exists()
|
.exists()
|
||||||
|| src_tauri.join(ConfigFormat::Toml.into_file_name()).exists()
|
|| src_tauri.join(ConfigFormat::Toml.into_file_name()).exists()
|
||||||
{
|
{
|
||||||
return src_tauri;
|
return Some(src_tauri);
|
||||||
}
|
}
|
||||||
|
|
||||||
lookup(&cwd, |path| folder_has_configuration_file(Target::Linux, path) || is_configuration_file(Target::Linux, path))
|
lookup(&cwd, |path| {
|
||||||
.map(|p| if p.is_dir() { p } else { p.parent().unwrap().to_path_buf() })
|
folder_has_configuration_file(Target::Linux, path) || is_configuration_file(Target::Linux, path)
|
||||||
.unwrap_or_else(||
|
})
|
||||||
|
.map(|p| {
|
||||||
|
if p.is_dir() {
|
||||||
|
p
|
||||||
|
} else {
|
||||||
|
p.parent().unwrap().to_path_buf()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tauri_dir() -> PathBuf {
|
||||||
|
tauri_dir_opt().unwrap_or_else(||
|
||||||
panic!("Couldn't recognize the current folder as a Tauri project. It must contain a `{}`, `{}` or `{}` file in any subfolder.",
|
panic!("Couldn't recognize the current folder as a Tauri project. It must contain a `{}`, `{}` or `{}` file in any subfolder.",
|
||||||
ConfigFormat::Json.into_file_name(),
|
ConfigFormat::Json.into_file_name(),
|
||||||
ConfigFormat::Json5.into_file_name(),
|
ConfigFormat::Json5.into_file_name(),
|
||||||
@ -116,11 +129,6 @@ fn get_app_dir() -> Option<PathBuf> {
|
|||||||
|
|
||||||
pub fn app_dir() -> &'static PathBuf {
|
pub fn app_dir() -> &'static PathBuf {
|
||||||
static APP_DIR: OnceLock<PathBuf> = OnceLock::new();
|
static APP_DIR: OnceLock<PathBuf> = OnceLock::new();
|
||||||
APP_DIR.get_or_init(|| {
|
APP_DIR
|
||||||
get_app_dir().unwrap_or_else(|| get_tauri_dir().parent().unwrap().to_path_buf())
|
.get_or_init(|| get_app_dir().unwrap_or_else(|| tauri_dir().parent().unwrap().to_path_buf()))
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tauri_dir() -> PathBuf {
|
|
||||||
get_tauri_dir()
|
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ pub mod config;
|
|||||||
pub mod flock;
|
pub mod flock;
|
||||||
pub mod framework;
|
pub mod framework;
|
||||||
pub mod npm;
|
pub mod npm;
|
||||||
|
pub mod prompts;
|
||||||
pub mod template;
|
pub mod template;
|
||||||
pub mod updater_signature;
|
pub mod updater_signature;
|
||||||
pub mod web_dev_server;
|
pub mod web_dev_server;
|
||||||
|
57
tooling/cli/src/helpers/prompts.rs
Normal file
57
tooling/cli/src/helpers/prompts.rs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
use std::{fmt::Display, str::FromStr};
|
||||||
|
|
||||||
|
use crate::Result;
|
||||||
|
|
||||||
|
pub fn input<T>(
|
||||||
|
prompt: &str,
|
||||||
|
initial: Option<T>,
|
||||||
|
skip: bool,
|
||||||
|
allow_empty: bool,
|
||||||
|
) -> Result<Option<T>>
|
||||||
|
where
|
||||||
|
T: Clone + FromStr + Display + ToString,
|
||||||
|
T::Err: Display + std::fmt::Debug,
|
||||||
|
{
|
||||||
|
if skip {
|
||||||
|
Ok(initial)
|
||||||
|
} else {
|
||||||
|
let theme = dialoguer::theme::ColorfulTheme::default();
|
||||||
|
let mut builder = dialoguer::Input::with_theme(&theme)
|
||||||
|
.with_prompt(prompt)
|
||||||
|
.allow_empty(allow_empty);
|
||||||
|
|
||||||
|
if let Some(v) = initial {
|
||||||
|
builder = builder.with_initial_text(v.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.interact_text().map(Some).map_err(Into::into)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn confirm(prompt: &str, default: Option<bool>) -> Result<bool> {
|
||||||
|
let theme = dialoguer::theme::ColorfulTheme::default();
|
||||||
|
let mut builder = dialoguer::Confirm::with_theme(&theme).with_prompt(prompt);
|
||||||
|
if let Some(default) = default {
|
||||||
|
builder = builder.default(default);
|
||||||
|
}
|
||||||
|
builder.interact().map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn multiselect<T: ToString>(
|
||||||
|
prompt: &str,
|
||||||
|
items: &[T],
|
||||||
|
defaults: Option<&[bool]>,
|
||||||
|
) -> Result<Vec<usize>> {
|
||||||
|
let theme = dialoguer::theme::ColorfulTheme::default();
|
||||||
|
let mut builder = dialoguer::MultiSelect::with_theme(&theme)
|
||||||
|
.with_prompt(prompt)
|
||||||
|
.items(items);
|
||||||
|
if let Some(defaults) = defaults {
|
||||||
|
builder = builder.defaults(defaults);
|
||||||
|
}
|
||||||
|
builder.interact().map_err(Into::into)
|
||||||
|
}
|
@ -5,23 +5,20 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
helpers::{
|
helpers::{
|
||||||
framework::{infer_from_package_json as infer_framework, Framework},
|
framework::{infer_from_package_json as infer_framework, Framework},
|
||||||
resolve_tauri_path, template,
|
prompts, resolve_tauri_path, template,
|
||||||
},
|
},
|
||||||
VersionMetadata,
|
VersionMetadata,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
collections::BTreeMap,
|
collections::BTreeMap,
|
||||||
env::current_dir,
|
env::current_dir,
|
||||||
fmt::Display,
|
|
||||||
fs::{read_to_string, remove_dir_all},
|
fs::{read_to_string, remove_dir_all},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
str::FromStr,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use dialoguer::Input;
|
|
||||||
use handlebars::{to_json, Handlebars};
|
use handlebars::{to_json, Handlebars};
|
||||||
use include_dir::{include_dir, Dir};
|
use include_dir::{include_dir, Dir};
|
||||||
use log::warn;
|
use log::warn;
|
||||||
@ -33,7 +30,7 @@ const TAURI_CONF_TEMPLATE: &str = include_str!("../templates/tauri.conf.json");
|
|||||||
#[clap(about = "Initialize a Tauri project in an existing directory")]
|
#[clap(about = "Initialize a Tauri project in an existing directory")]
|
||||||
pub struct Options {
|
pub struct Options {
|
||||||
/// Skip prompting for values
|
/// Skip prompting for values
|
||||||
#[clap(long)]
|
#[clap(long, env = "CI")]
|
||||||
ci: bool,
|
ci: bool,
|
||||||
/// Force init to overwrite the src-tauri folder
|
/// Force init to overwrite the src-tauri folder
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
@ -76,7 +73,6 @@ struct InitDefaults {
|
|||||||
|
|
||||||
impl Options {
|
impl Options {
|
||||||
fn load(mut self) -> Result<Self> {
|
fn load(mut self) -> Result<Self> {
|
||||||
self.ci = self.ci || std::env::var("CI").is_ok();
|
|
||||||
let package_json_path = PathBuf::from(&self.directory).join("package.json");
|
let package_json_path = PathBuf::from(&self.directory).join("package.json");
|
||||||
|
|
||||||
let init_defaults = if package_json_path.exists() {
|
let init_defaults = if package_json_path.exists() {
|
||||||
@ -92,7 +88,7 @@ impl Options {
|
|||||||
};
|
};
|
||||||
|
|
||||||
self.app_name = self.app_name.map(|s| Ok(Some(s))).unwrap_or_else(|| {
|
self.app_name = self.app_name.map(|s| Ok(Some(s))).unwrap_or_else(|| {
|
||||||
request_input(
|
prompts::input(
|
||||||
"What is your app name?",
|
"What is your app name?",
|
||||||
init_defaults.app_name.clone(),
|
init_defaults.app_name.clone(),
|
||||||
self.ci,
|
self.ci,
|
||||||
@ -101,7 +97,7 @@ impl Options {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
self.window_title = self.window_title.map(|s| Ok(Some(s))).unwrap_or_else(|| {
|
self.window_title = self.window_title.map(|s| Ok(Some(s))).unwrap_or_else(|| {
|
||||||
request_input(
|
prompts::input(
|
||||||
"What should the window title be?",
|
"What should the window title be?",
|
||||||
init_defaults.app_name.clone(),
|
init_defaults.app_name.clone(),
|
||||||
self.ci,
|
self.ci,
|
||||||
@ -109,7 +105,7 @@ impl Options {
|
|||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
self.frontend_dist = self.frontend_dist.map(|s| Ok(Some(s))).unwrap_or_else(|| request_input(
|
self.frontend_dist = self.frontend_dist.map(|s| Ok(Some(s))).unwrap_or_else(|| prompts::input(
|
||||||
r#"Where are your web assets (HTML/CSS/JS) located, relative to the "<current dir>/src-tauri/tauri.conf.json" file that will be created?"#,
|
r#"Where are your web assets (HTML/CSS/JS) located, relative to the "<current dir>/src-tauri/tauri.conf.json" file that will be created?"#,
|
||||||
init_defaults.framework.as_ref().map(|f| f.frontend_dist()),
|
init_defaults.framework.as_ref().map(|f| f.frontend_dist()),
|
||||||
self.ci,
|
self.ci,
|
||||||
@ -117,7 +113,7 @@ impl Options {
|
|||||||
))?;
|
))?;
|
||||||
|
|
||||||
self.dev_url = self.dev_url.map(|s| Ok(Some(s))).unwrap_or_else(|| {
|
self.dev_url = self.dev_url.map(|s| Ok(Some(s))).unwrap_or_else(|| {
|
||||||
request_input(
|
prompts::input(
|
||||||
"What is the url of your dev server?",
|
"What is the url of your dev server?",
|
||||||
init_defaults.framework.map(|f| f.dev_url()),
|
init_defaults.framework.map(|f| f.dev_url()),
|
||||||
self.ci,
|
self.ci,
|
||||||
@ -129,7 +125,7 @@ impl Options {
|
|||||||
.before_dev_command
|
.before_dev_command
|
||||||
.map(|s| Ok(Some(s)))
|
.map(|s| Ok(Some(s)))
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
request_input(
|
prompts::input(
|
||||||
"What is your frontend dev command?",
|
"What is your frontend dev command?",
|
||||||
Some("npm run dev".to_string()),
|
Some("npm run dev".to_string()),
|
||||||
self.ci,
|
self.ci,
|
||||||
@ -140,7 +136,7 @@ impl Options {
|
|||||||
.before_build_command
|
.before_build_command
|
||||||
.map(|s| Ok(Some(s)))
|
.map(|s| Ok(Some(s)))
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
request_input(
|
prompts::input(
|
||||||
"What is your frontend build command?",
|
"What is your frontend build command?",
|
||||||
Some("npm run build".to_string()),
|
Some("npm run build".to_string()),
|
||||||
self.ci,
|
self.ci,
|
||||||
@ -283,29 +279,3 @@ pub fn command(mut options: Options) -> Result<()> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn request_input<T>(
|
|
||||||
prompt: &str,
|
|
||||||
initial: Option<T>,
|
|
||||||
skip: bool,
|
|
||||||
allow_empty: bool,
|
|
||||||
) -> Result<Option<T>>
|
|
||||||
where
|
|
||||||
T: Clone + FromStr + Display + ToString,
|
|
||||||
T::Err: Display + std::fmt::Debug,
|
|
||||||
{
|
|
||||||
if skip {
|
|
||||||
Ok(initial)
|
|
||||||
} else {
|
|
||||||
let theme = dialoguer::theme::ColorfulTheme::default();
|
|
||||||
let mut builder = Input::with_theme(&theme)
|
|
||||||
.with_prompt(prompt)
|
|
||||||
.allow_empty(allow_empty);
|
|
||||||
|
|
||||||
if let Some(v) = initial {
|
|
||||||
builder = builder.with_initial_text(v.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.interact_text().map(Some).map_err(Into::into)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
pub use anyhow::Result;
|
pub use anyhow::Result;
|
||||||
|
|
||||||
|
mod acl;
|
||||||
mod add;
|
mod add;
|
||||||
mod build;
|
mod build;
|
||||||
mod completions;
|
mod completions;
|
||||||
@ -145,6 +146,8 @@ enum Commands {
|
|||||||
Icon(icon::Options),
|
Icon(icon::Options),
|
||||||
Signer(signer::Cli),
|
Signer(signer::Cli),
|
||||||
Completions(completions::Options),
|
Completions(completions::Options),
|
||||||
|
Permission(acl::permission::Cli),
|
||||||
|
Capability(acl::capability::Cli),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_error<I: CommandFactory>(err: clap::Error) -> clap::Error {
|
fn format_error<I: CommandFactory>(err: clap::Error) -> clap::Error {
|
||||||
@ -247,6 +250,8 @@ where
|
|||||||
Commands::Plugin(cli) => plugin::command(cli)?,
|
Commands::Plugin(cli) => plugin::command(cli)?,
|
||||||
Commands::Signer(cli) => signer::command(cli)?,
|
Commands::Signer(cli) => signer::command(cli)?,
|
||||||
Commands::Completions(options) => completions::command(options, cli_)?,
|
Commands::Completions(options) => completions::command(options, cli_)?,
|
||||||
|
Commands::Permission(options) => acl::permission::command(options)?,
|
||||||
|
Commands::Capability(options) => acl::capability::command(options)?,
|
||||||
Commands::Android(c) => mobile::android::command(c, cli.verbose)?,
|
Commands::Android(c) => mobile::android::command(c, cli.verbose)?,
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
Commands::Ios(c) => mobile::ios::command(c, cli.verbose)?,
|
Commands::Ios(c) => mobile::ios::command(c, cli.verbose)?,
|
||||||
|
@ -64,6 +64,9 @@ pub struct Options {
|
|||||||
/// Open Android Studio
|
/// Open Android Studio
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
pub open: bool,
|
pub open: bool,
|
||||||
|
/// Skip prompting for values
|
||||||
|
#[clap(long, env = "CI")]
|
||||||
|
pub ci: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Options> for BuildOptions {
|
impl From<Options> for BuildOptions {
|
||||||
@ -76,7 +79,7 @@ impl From<Options> for BuildOptions {
|
|||||||
bundles: None,
|
bundles: None,
|
||||||
config: options.config,
|
config: options.config,
|
||||||
args: Vec::new(),
|
args: Vec::new(),
|
||||||
ci: false,
|
ci: options.ci,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ pub struct Cli {
|
|||||||
#[clap(about = "Initialize Android target in the project")]
|
#[clap(about = "Initialize Android target in the project")]
|
||||||
pub struct InitOptions {
|
pub struct InitOptions {
|
||||||
/// Skip prompting for values
|
/// Skip prompting for values
|
||||||
#[clap(long)]
|
#[clap(long, env = "CI")]
|
||||||
ci: bool,
|
ci: bool,
|
||||||
/// Skips installing rust toolchains via rustup
|
/// Skips installing rust toolchains via rustup
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
|
@ -38,14 +38,8 @@ pub fn command(
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let wrapper = TextWrapper::default();
|
let wrapper = TextWrapper::default();
|
||||||
|
|
||||||
exec(
|
exec(target, &wrapper, ci, reinstall_deps, skip_targets_install)
|
||||||
target,
|
.map_err(|e| anyhow::anyhow!("{:#}", e))?;
|
||||||
&wrapper,
|
|
||||||
ci || var_os("CI").is_some(),
|
|
||||||
reinstall_deps,
|
|
||||||
skip_targets_install,
|
|
||||||
)
|
|
||||||
.map_err(|e| anyhow::anyhow!("{:#}", e))?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,6 +60,9 @@ pub struct Options {
|
|||||||
/// Open Xcode
|
/// Open Xcode
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
pub open: bool,
|
pub open: bool,
|
||||||
|
/// Skip prompting for values
|
||||||
|
#[clap(long, env = "CI")]
|
||||||
|
pub ci: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Options> for BuildOptions {
|
impl From<Options> for BuildOptions {
|
||||||
@ -72,7 +75,7 @@ impl From<Options> for BuildOptions {
|
|||||||
bundles: None,
|
bundles: None,
|
||||||
config: options.config,
|
config: options.config,
|
||||||
args: Vec::new(),
|
args: Vec::new(),
|
||||||
ci: false,
|
ci: options.ci,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,7 +63,7 @@ pub struct Cli {
|
|||||||
#[clap(about = "Initialize iOS target in the project")]
|
#[clap(about = "Initialize iOS target in the project")]
|
||||||
pub struct InitOptions {
|
pub struct InitOptions {
|
||||||
/// Skip prompting for values
|
/// Skip prompting for values
|
||||||
#[clap(long)]
|
#[clap(long, env = "CI")]
|
||||||
ci: bool,
|
ci: bool,
|
||||||
/// Reinstall dependencies
|
/// Reinstall dependencies
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
|
@ -2,7 +2,10 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
use crate::{helpers::template, Result};
|
use crate::{
|
||||||
|
helpers::{prompts::input, template},
|
||||||
|
Result,
|
||||||
|
};
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use handlebars::Handlebars;
|
use handlebars::Handlebars;
|
||||||
|
|
||||||
@ -56,7 +59,7 @@ pub fn command(cli: Cli) -> Result<()> {
|
|||||||
return Err(anyhow::anyhow!("android folder already exists"));
|
return Err(anyhow::anyhow!("android folder already exists"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let plugin_id = super::init::request_input(
|
let plugin_id = input(
|
||||||
"What should be the Android Package ID for your plugin?",
|
"What should be the Android Package ID for your plugin?",
|
||||||
Some(format!("com.plugin.{}", plugin_name)),
|
Some(format!("com.plugin.{}", plugin_name)),
|
||||||
false,
|
false,
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
use crate::helpers::prompts::input;
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
use crate::{
|
use crate::{
|
||||||
helpers::{resolve_tauri_path, template},
|
helpers::{resolve_tauri_path, template},
|
||||||
@ -9,7 +10,6 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use dialoguer::Input;
|
|
||||||
use handlebars::{to_json, Handlebars};
|
use handlebars::{to_json, Handlebars};
|
||||||
use heck::{ToKebabCase, ToPascalCase, ToSnakeCase};
|
use heck::{ToKebabCase, ToPascalCase, ToSnakeCase};
|
||||||
use include_dir::{include_dir, Dir};
|
use include_dir::{include_dir, Dir};
|
||||||
@ -18,10 +18,8 @@ use std::{
|
|||||||
collections::BTreeMap,
|
collections::BTreeMap,
|
||||||
env::current_dir,
|
env::current_dir,
|
||||||
ffi::OsStr,
|
ffi::OsStr,
|
||||||
fmt::Display,
|
|
||||||
fs::{create_dir_all, remove_dir_all, File, OpenOptions},
|
fs::{create_dir_all, remove_dir_all, File, OpenOptions},
|
||||||
path::{Component, Path, PathBuf},
|
path::{Component, Path, PathBuf},
|
||||||
str::FromStr,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const TEMPLATE_DIR: Dir<'_> = include_dir!("templates/plugin");
|
pub const TEMPLATE_DIR: Dir<'_> = include_dir!("templates/plugin");
|
||||||
@ -145,7 +143,7 @@ pub fn command(mut options: Options) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let plugin_id = if options.android || options.mobile {
|
let plugin_id = if options.android || options.mobile {
|
||||||
let plugin_id = request_input(
|
let plugin_id = input(
|
||||||
"What should be the Android Package ID for your plugin?",
|
"What should be the Android Package ID for your plugin?",
|
||||||
Some(format!("com.plugin.{}", plugin_name)),
|
Some(format!("com.plugin.{}", plugin_name)),
|
||||||
false,
|
false,
|
||||||
@ -218,6 +216,10 @@ pub fn command(mut options: Options) -> Result<()> {
|
|||||||
)
|
)
|
||||||
.with_context(|| "failed to render plugin Android template")?;
|
.with_context(|| "failed to render plugin Android template")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::fs::create_dir(template_target_path.join("permissions"))
|
||||||
|
.with_context(|| "failed to create `permissions` directory")?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,29 +280,3 @@ pub fn generate_android_out_file(
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn request_input<T>(
|
|
||||||
prompt: &str,
|
|
||||||
initial: Option<T>,
|
|
||||||
skip: bool,
|
|
||||||
allow_empty: bool,
|
|
||||||
) -> Result<Option<T>>
|
|
||||||
where
|
|
||||||
T: Clone + FromStr + Display + ToString,
|
|
||||||
T::Err: Display + std::fmt::Debug,
|
|
||||||
{
|
|
||||||
if skip {
|
|
||||||
Ok(initial)
|
|
||||||
} else {
|
|
||||||
let theme = dialoguer::theme::ColorfulTheme::default();
|
|
||||||
let mut builder = Input::with_theme(&theme)
|
|
||||||
.with_prompt(prompt)
|
|
||||||
.allow_empty(allow_empty);
|
|
||||||
|
|
||||||
if let Some(v) = initial {
|
|
||||||
builder = builder.with_initial_text(v.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.interact_text().map(Some).map_err(Into::into)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -23,13 +23,11 @@ pub struct Options {
|
|||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
force: bool,
|
force: bool,
|
||||||
/// Skip prompting for values
|
/// Skip prompting for values
|
||||||
#[clap(long)]
|
#[clap(long, env = "CI")]
|
||||||
ci: bool,
|
ci: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn command(mut options: Options) -> Result<()> {
|
pub fn command(mut options: Options) -> Result<()> {
|
||||||
options.ci = options.ci || std::env::var("CI").is_ok();
|
|
||||||
|
|
||||||
if options.ci && options.password.is_none() {
|
if options.ci && options.password.is_none() {
|
||||||
log::warn!("Generating new private key without password. For security reasons, we recommend setting a password instead.");
|
log::warn!("Generating new private key without password. For security reasons, we recommend setting a password instead.");
|
||||||
options.password.replace("".into());
|
options.password.replace("".into());
|
||||||
|
Loading…
Reference in New Issue
Block a user