refactor(core): use attohttpc by default (#1861)

This commit is contained in:
Lucas Fernandes Nogueira 2021-05-19 01:06:08 -03:00 committed by GitHub
parent f237435093
commit 17c7c4396f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 309 additions and 124 deletions

View File

@ -0,0 +1,5 @@
---
"tauri": patch
---
Use `attohttpc` on the HTTP API by default for bundle size optimization. `reqwest` is implemented behind the `reqwest-client` feature flag.

View File

@ -0,0 +1,5 @@
---
"cli.rs": patch
---
Properly keep all `tauri` features that are not managed by the CLI.

View File

@ -27,6 +27,9 @@ targets = [
"x86_64-apple-darwin"
]
[package.metadata.cargo-udeps.ignore]
normal = ["attohttpc"] # we ignore attohttpc because we can't remove it based on `not(feature = "reqwest-client")`
[dependencies]
serde_json = { version = "1.0", features = [ "raw_value" ] }
serde = { version = "1.0", features = [ "derive" ] }
@ -41,7 +44,6 @@ tauri-macros = { version = "1.0.0-beta.1", path = "../tauri-macros" }
tauri-utils = { version = "1.0.0-beta.0", path = "../tauri-utils" }
tauri-runtime-wry = { version = "0.1.1", path = "../tauri-runtime-wry", optional = true }
rand = "0.8"
reqwest = { version = "0.11", features = [ "json", "multipart" ] }
tempfile = "3"
semver = "0.11"
serde_repr = "0.1"
@ -53,7 +55,6 @@ tar = "0.4"
flate2 = "1.0"
rfd = "0.3.0"
tinyfiledialogs = "3.3"
bytes = { version = "1", features = [ "serde" ] }
http = "0.2"
clap = { version = "=3.0.0-beta.2", optional = true }
notify-rust = { version = "4.5.0", optional = true }
@ -65,6 +66,11 @@ minisign-verify = "0.1.8"
state = "0.4"
bincode = "1.3"
# HTTP
reqwest = { version = "0.11", features = [ "json", "multipart" ], optional = true }
bytes = { version = "1", features = [ "serde" ], optional = true }
attohttpc = { version = "0.17", features = [ "json", "form" ] }
[build-dependencies]
cfg_aliases = "0.1.1"
@ -85,9 +91,10 @@ wry = [ "tauri-runtime-wry" ]
cli = [ "clap" ]
custom-protocol = [ "tauri-macros/custom-protocol" ]
api-all = [ "notification-all", "global-shortcut-all", "updater" ]
updater = [ "reqwest/default-tls" ]
updater = [ ]
menu = [ "tauri-runtime/menu", "tauri-runtime-wry/menu" ]
system-tray = [ "tauri-runtime/system-tray", "tauri-runtime-wry/system-tray" ]
reqwest-client = [ "reqwest", "bytes" ]
fs-all = [ ]
fs-read-text-file = [ ]
fs-read-binary-file = [ ]

View File

@ -25,6 +25,11 @@ pub enum Error {
#[error("user cancelled the dialog")]
DialogCancelled,
/// The network error.
#[cfg(not(feature = "reqwest-client"))]
#[error("Network Error: {0}")]
Network(#[from] attohttpc::Error),
/// The network error.
#[cfg(feature = "reqwest-client")]
#[error("Network Error: {0}")]
Network(#[from] reqwest::Error),
/// HTTP method error.
@ -32,10 +37,10 @@ pub enum Error {
HttpMethod(#[from] http::method::InvalidMethod),
/// Invalid HTTO header.
#[error("{0}")]
HttpHeader(#[from] reqwest::header::InvalidHeaderName),
HttpHeader(#[from] http::header::InvalidHeaderName),
/// Failed to serialize header value as string.
#[error("failed to convert response header value to string")]
HttpHeaderToString(#[from] reqwest::header::ToStrError),
HttpHeaderToString(#[from] http::header::ToStrError),
/// HTTP form to must be an object.
#[error("http form must be an object")]
InvalidHttpForm,

View File

@ -2,8 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use bytes::Bytes;
use reqwest::{header::HeaderName, redirect::Policy, Method};
use http::{header::HeaderName, Method};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use serde_repr::{Deserialize_repr, Serialize_repr};
@ -11,7 +10,7 @@ use serde_repr::{Deserialize_repr, Serialize_repr};
use std::{collections::HashMap, path::PathBuf, time::Duration};
/// Client builder.
#[derive(Default, Deserialize)]
#[derive(Clone, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ClientBuilder {
/// Max number of redirections to follow
@ -38,12 +37,19 @@ impl ClientBuilder {
self
}
/// Builds the ClientOptions.
/// Builds the Client.
#[cfg(not(feature = "reqwest-client"))]
pub fn build(self) -> crate::api::Result<Client> {
Ok(Client(self))
}
/// Builds the Client.
#[cfg(feature = "reqwest-client")]
pub fn build(self) -> crate::api::Result<Client> {
let mut client_builder = reqwest::Client::builder();
if let Some(max_redirections) = self.max_redirections {
client_builder = client_builder.redirect(Policy::limited(max_redirections))
client_builder = client_builder.redirect(reqwest::redirect::Policy::limited(max_redirections))
}
if let Some(connect_timeout) = self.connect_timeout {
@ -56,16 +62,80 @@ impl ClientBuilder {
}
/// The HTTP client.
#[cfg(feature = "reqwest-client")]
#[derive(Clone)]
pub struct Client(reqwest::Client);
/// The HTTP client.
#[cfg(not(feature = "reqwest-client"))]
#[derive(Clone)]
pub struct Client(ClientBuilder);
#[cfg(not(feature = "reqwest-client"))]
impl Client {
/// Executes an HTTP request
///
/// The response will be transformed to String,
/// If reading the response as binary, the byte array will be serialized using serde_json
/// If reading the response as binary, the byte array will be serialized using serde_json.
pub async fn send(&self, request: HttpRequestBuilder) -> crate::api::Result<Response> {
let method = Method::from_bytes(request.method.to_uppercase().as_bytes())?;
let mut request_builder = attohttpc::RequestBuilder::try_new(method, &request.url)?;
if let Some(query) = request.query {
request_builder = request_builder.params(&query);
}
if let Some(headers) = request.headers {
for (header, header_value) in headers.iter() {
request_builder =
request_builder.header(HeaderName::from_bytes(header.as_bytes())?, header_value);
}
}
if let Some(timeout) = request.timeout {
request_builder = request_builder.timeout(Duration::from_secs(timeout));
}
let response = if let Some(body) = request.body {
match body {
Body::Bytes(data) => request_builder.body(attohttpc::body::Bytes(data)).send()?,
Body::Text(text) => request_builder.body(attohttpc::body::Bytes(text)).send()?,
Body::Json(json) => request_builder.json(&json)?.send()?,
Body::Form(form_body) => {
let mut form = Vec::new();
for (name, part) in form_body.0 {
match part {
FormPart::Bytes(bytes) => form.push((name, serde_json::to_string(&bytes)?)),
FormPart::File(file_path) => form.push((name, serde_json::to_string(&file_path)?)),
FormPart::Text(text) => form.push((name, text)),
}
}
request_builder.form(&form)?.send()?
}
}
} else {
request_builder.send()?
};
let response = response.error_for_status()?;
Ok(Response(
request.response_type.unwrap_or(ResponseType::Json),
response,
request.url,
))
}
}
#[cfg(feature = "reqwest-client")]
impl Client {
/// Executes an HTTP request
///
/// The response will be transformed to String,
/// If reading the response as binary, the byte array will be serialized using serde_json.
pub async fn send(&self, request: HttpRequestBuilder) -> crate::api::Result<Response> {
let method = Method::from_bytes(request.method.to_uppercase().as_bytes())?;
let mut request_builder = self.0.request(method, &request.url);
if let Some(query) = request.query {
@ -85,8 +155,18 @@ impl Client {
let response = if let Some(body) = request.body {
match body {
Body::Bytes(data) => request_builder.body(Bytes::from(data)).send().await?,
Body::Text(text) => request_builder.body(Bytes::from(text)).send().await?,
Body::Bytes(data) => {
request_builder
.body(bytes::Bytes::from(data))
.send()
.await?
}
Body::Text(text) => {
request_builder
.body(bytes::Bytes::from(text))
.send()
.await?
}
Body::Json(json) => request_builder.json(&json).send().await?,
Body::Form(form_body) => {
let mut form = Vec::new();
@ -249,24 +329,50 @@ impl HttpRequestBuilder {
}
/// The HTTP response.
#[cfg(feature = "reqwest-client")]
pub struct Response(ResponseType, reqwest::Response);
/// The HTTP response.
#[cfg(not(feature = "reqwest-client"))]
pub struct Response(ResponseType, attohttpc::Response, String);
impl Response {
/// Reads the response as raw bytes.
pub async fn bytes(self) -> crate::api::Result<RawResponse> {
let status = self.1.status().as_u16();
#[cfg(feature = "reqwest-client")]
let data = self.1.bytes().await?.to_vec();
#[cfg(not(feature = "reqwest-client"))]
let data = self.1.bytes()?;
Ok(RawResponse { status, data })
}
/// Reads the response and returns its info.
pub async fn read(self) -> crate::api::Result<ResponseData> {
#[cfg(feature = "reqwest-client")]
let url = self.1.url().to_string();
#[cfg(not(feature = "reqwest-client"))]
let url = self.2;
let mut headers = HashMap::new();
for (name, value) in self.1.headers() {
headers.insert(name.as_str().to_string(), value.to_str()?.to_string());
}
let status = self.1.status().as_u16();
#[cfg(feature = "reqwest-client")]
let data = match self.0 {
ResponseType::Json => self.1.json().await?,
ResponseType::Text => Value::String(self.1.text().await?),
ResponseType::Binary => Value::String(serde_json::to_string(&self.1.bytes().await?)?),
};
#[cfg(not(feature = "reqwest-client"))]
let data = match self.0 {
ResponseType::Json => self.1.json()?,
ResponseType::Text => Value::String(self.1.text()?),
ResponseType::Binary => Value::String(serde_json::to_string(&self.1.bytes()?)?),
};
Ok(ResponseData {
url,
status,
@ -276,12 +382,26 @@ impl Response {
}
}
/// A response with raw bytes.
#[non_exhaustive]
pub struct RawResponse {
/// Response status code.
pub status: u16,
/// Response bytes.
pub data: Vec<u8>,
}
/// The response type.
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct ResponseData {
url: String,
status: u16,
headers: HashMap<String, String>,
data: Value,
/// Response URL. Useful if it followed redirects.
pub url: String,
/// Response status code.
pub status: u16,
/// Response headers.
pub headers: HashMap<String, String>,
/// Response data.
pub data: Value,
}

View File

@ -5,16 +5,17 @@
use super::error::{Error, Result};
use crate::api::{file::Extract, version};
use base64::decode;
use http::StatusCode;
use minisign_verify::{PublicKey, Signature};
use reqwest::{self, header, StatusCode};
use std::{
collections::HashMap,
env,
ffi::OsStr,
fs::{read_dir, remove_file, File, OpenOptions},
io::{prelude::*, BufReader, Read},
path::{Path, PathBuf},
str::from_utf8,
time::{Duration, SystemTime, UNIX_EPOCH},
time::{SystemTime, UNIX_EPOCH},
};
#[cfg(not(target_os = "macos"))]
@ -23,6 +24,8 @@ use std::process::Command;
#[cfg(target_os = "macos")]
use crate::api::file::Move;
use crate::api::http::{ClientBuilder, HttpRequestBuilder};
#[cfg(target_os = "windows")]
use std::process::exit;
@ -271,31 +274,33 @@ impl<'a> UpdateBuilder<'a> {
);
// we want JSON only
let mut headers = header::HeaderMap::new();
headers.insert(header::ACCEPT, "application/json".parse().unwrap());
let mut headers = HashMap::new();
headers.insert("Accept".into(), "application/json".into());
let resp = reqwest::Client::new()
.get(&fixed_link)
.headers(headers)
// wait 20sec for the firewall
.timeout(Duration::from_secs(20))
.send()
let resp = ClientBuilder::new()
.build()?
.send(
HttpRequestBuilder::new("GET", &fixed_link)
.headers(headers)
// wait 20sec for the firewall
.timeout(20),
)
.await;
// If we got a success, we stop the loop
// and we set our remote_release variable
if let Ok(ref res) = resp {
if let Ok(res) = resp {
let res = res.read().await?;
// got status code 2XX
if res.status().is_success() {
if StatusCode::from_u16(res.status).unwrap().is_success() {
// if we got 204
if StatusCode::NO_CONTENT == res.status() {
if StatusCode::NO_CONTENT.as_u16() == res.status {
// return with `UpToDate` error
// we should catch on the client
return Err(Error::UpToDate);
};
let json = resp?.json::<serde_json::Value>().await?;
// Convert the remote result to our local struct
let built_release = RemoteRelease::from_release(&json, &target);
let built_release = RemoteRelease::from_release(&res.data, &target);
// make sure all went well and the remote data is compatible
// with what we need locally
match built_release {
@ -411,35 +416,32 @@ impl Update {
let mut tmp_archive = File::create(&tmp_archive_path)?;
// set our headers
let mut headers = header::HeaderMap::new();
headers.insert(header::ACCEPT, "application/octet-stream".parse().unwrap());
// make sure we have a valid agent
if !headers.contains_key(header::USER_AGENT) {
headers.insert(
header::USER_AGENT,
"tauri/updater".parse().expect("invalid user-agent"),
);
}
let mut headers = HashMap::new();
headers.insert("Accept".into(), "application/octet-stream".into());
headers.insert("User-Agent".into(), "tauri/updater".into());
// Create our request
let resp = reqwest::Client::new()
.get(&url)
// wait 20sec for the firewall
.timeout(Duration::from_secs(20))
.headers(headers)
.send()
let resp = ClientBuilder::new()
.build()?
.send(
HttpRequestBuilder::new("GET", &url)
.headers(headers)
// wait 20sec for the firewall
.timeout(20),
)
.await?
.bytes()
.await?;
// make sure it's success
if !resp.status().is_success() {
if !StatusCode::from_u16(resp.status).unwrap().is_success() {
return Err(Error::Network(format!(
"Download request failed with status: {}",
resp.status()
resp.status
)));
}
tmp_archive.write_all(&resp.bytes().await?)?;
tmp_archive.write_all(&resp.data)?;
// Validate signature ONLY if pubkey is available in tauri.conf.json
if let Some(pub_key) = pub_key {

View File

@ -11,9 +11,6 @@ pub enum Error {
/// IO Errors.
#[error("`{0}`")]
Io(#[from] std::io::Error),
/// Reqwest Errors.
#[error("Request error: {0}")]
Reqwest(#[from] reqwest::Error),
/// Semver Errors.
#[error("Unable to compare version: {0}")]
Semver(#[from] semver::SemVerError),

View File

@ -11,7 +11,7 @@ tauri-build = { path = "../../../core/tauri-build" }
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = [ "derive" ] }
tauri = { path = "../../../core/tauri", features = ["api-all", "cli", "updater", "system-tray", "menu"] }
tauri = { path = "../../../core/tauri", features = ["api-all", "cli", "menu", "system-tray", "updater"] }
[features]
default = [ "custom-protocol" ]

View File

@ -300,7 +300,7 @@ pub struct SecurityConfig {
pub csp: Option<String>,
}
trait Allowlist {
pub trait Allowlist {
fn to_features(&self) -> Vec<&str>;
}
@ -314,31 +314,31 @@ macro_rules! check_feature {
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct FsAllowlistConfig {
pub struct FsAllowlistConfig {
#[serde(default)]
all: bool,
pub all: bool,
#[serde(default)]
read_text_file: bool,
pub read_text_file: bool,
#[serde(default)]
read_binary_file: bool,
pub read_binary_file: bool,
#[serde(default)]
write_file: bool,
pub write_file: bool,
#[serde(default)]
write_binary_file: bool,
pub write_binary_file: bool,
#[serde(default)]
read_dir: bool,
pub read_dir: bool,
#[serde(default)]
copy_file: bool,
pub copy_file: bool,
#[serde(default)]
create_dir: bool,
pub create_dir: bool,
#[serde(default)]
remove_dir: bool,
pub remove_dir: bool,
#[serde(default)]
remove_file: bool,
pub remove_file: bool,
#[serde(default)]
rename_file: bool,
pub rename_file: bool,
#[serde(default)]
path: bool,
pub path: bool,
}
impl Allowlist for FsAllowlistConfig {
@ -365,11 +365,11 @@ impl Allowlist for FsAllowlistConfig {
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct WindowAllowlistConfig {
pub struct WindowAllowlistConfig {
#[serde(default)]
all: bool,
pub all: bool,
#[serde(default)]
create: bool,
pub create: bool,
}
impl Allowlist for WindowAllowlistConfig {
@ -386,13 +386,13 @@ impl Allowlist for WindowAllowlistConfig {
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct ShellAllowlistConfig {
pub struct ShellAllowlistConfig {
#[serde(default)]
all: bool,
pub all: bool,
#[serde(default)]
execute: bool,
pub execute: bool,
#[serde(default)]
open: bool,
pub open: bool,
}
impl Allowlist for ShellAllowlistConfig {
@ -410,13 +410,13 @@ impl Allowlist for ShellAllowlistConfig {
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct DialogAllowlistConfig {
pub struct DialogAllowlistConfig {
#[serde(default)]
all: bool,
pub all: bool,
#[serde(default)]
open: bool,
pub open: bool,
#[serde(default)]
save: bool,
pub save: bool,
}
impl Allowlist for DialogAllowlistConfig {
@ -434,11 +434,11 @@ impl Allowlist for DialogAllowlistConfig {
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct HttpAllowlistConfig {
pub struct HttpAllowlistConfig {
#[serde(default)]
all: bool,
pub all: bool,
#[serde(default)]
request: bool,
pub request: bool,
}
impl Allowlist for HttpAllowlistConfig {
@ -455,9 +455,9 @@ impl Allowlist for HttpAllowlistConfig {
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct NotificationAllowlistConfig {
pub struct NotificationAllowlistConfig {
#[serde(default)]
all: bool,
pub all: bool,
}
impl Allowlist for NotificationAllowlistConfig {
@ -472,9 +472,9 @@ impl Allowlist for NotificationAllowlistConfig {
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct GlobalShortcutAllowlistConfig {
pub struct GlobalShortcutAllowlistConfig {
#[serde(default)]
all: bool,
pub all: bool,
}
impl Allowlist for GlobalShortcutAllowlistConfig {
@ -489,23 +489,23 @@ impl Allowlist for GlobalShortcutAllowlistConfig {
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct AllowlistConfig {
pub struct AllowlistConfig {
#[serde(default)]
all: bool,
pub all: bool,
#[serde(default)]
fs: FsAllowlistConfig,
pub fs: FsAllowlistConfig,
#[serde(default)]
window: WindowAllowlistConfig,
pub window: WindowAllowlistConfig,
#[serde(default)]
shell: ShellAllowlistConfig,
pub shell: ShellAllowlistConfig,
#[serde(default)]
dialog: DialogAllowlistConfig,
pub dialog: DialogAllowlistConfig,
#[serde(default)]
http: HttpAllowlistConfig,
pub http: HttpAllowlistConfig,
#[serde(default)]
notification: NotificationAllowlistConfig,
pub notification: NotificationAllowlistConfig,
#[serde(default)]
global_shortcut: GlobalShortcutAllowlistConfig,
pub global_shortcut: GlobalShortcutAllowlistConfig,
}
impl Allowlist for AllowlistConfig {

View File

@ -383,7 +383,7 @@ fn tauri_config_to_bundle_settings(
// provides `libwebkit2gtk-4.0.so.37` and all `4.0` versions have the -37 package name
depends.push("libwebkit2gtk-4.0-37".to_string());
depends.push("libgtk-3-0".to_string());
if manifest.features.contains(&"menu".into()) || system_tray_config.is_some() {
if manifest.features.contains("menu") || system_tray_config.is_some() {
depends.push("libgtksourceview-3.0-1".to_string());
}
}

View File

@ -101,3 +101,44 @@ pub fn reload(merge_config: Option<&str>) -> crate::Result<()> {
get_internal(merge_config, true)?;
Ok(())
}
pub fn all_allowlist_features() -> Vec<&'static str> {
AllowlistConfig {
all: true,
fs: FsAllowlistConfig {
all: true,
read_text_file: true,
read_binary_file: true,
write_file: true,
write_binary_file: true,
read_dir: true,
copy_file: true,
create_dir: true,
remove_dir: true,
remove_file: true,
rename_file: true,
path: true,
},
window: WindowAllowlistConfig {
all: true,
create: true,
},
shell: ShellAllowlistConfig {
all: true,
execute: true,
open: true,
},
dialog: DialogAllowlistConfig {
all: true,
open: true,
save: true,
},
http: HttpAllowlistConfig {
all: true,
request: true,
},
notification: NotificationAllowlistConfig { all: true },
global_shortcut: GlobalShortcutAllowlistConfig { all: true },
}
.to_features()
}

View File

@ -2,19 +2,23 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use super::{app_paths::tauri_dir, config::ConfigHandle};
use super::{
app_paths::tauri_dir,
config::{all_allowlist_features, ConfigHandle},
};
use anyhow::Context;
use toml_edit::{Array, Document, InlineTable, Item, Value};
use std::{
collections::HashSet,
fs::File,
io::{Read, Write},
path::Path,
};
pub struct Manifest {
pub features: Vec<String>,
pub features: HashSet<String>,
}
fn read_manifest(manifest_path: &Path) -> crate::Result<Document> {
@ -31,14 +35,14 @@ fn read_manifest(manifest_path: &Path) -> crate::Result<Document> {
Ok(manifest)
}
fn features_to_vec(features: &Array) -> Vec<String> {
let mut string_features = Vec::new();
for feat in features.iter() {
if let Value::String(feature) = feat {
string_features.push(feature.value().to_string());
}
fn toml_array(features: &HashSet<String>) -> Array {
let mut f = Array::default();
let mut features: Vec<String> = features.iter().map(|f| f.to_string()).collect();
features.sort();
for feature in features {
f.push(feature.as_str()).unwrap();
}
string_features
f
}
pub fn rewrite_manifest(config: ConfigHandle) -> crate::Result<Manifest> {
@ -56,32 +60,35 @@ pub fn rewrite_manifest(config: ConfigHandle) -> crate::Result<Manifest> {
let config = config_guard.as_ref().unwrap();
let allowlist_features = config.tauri.features();
let mut features = Array::default();
let mut features = HashSet::new();
for feature in allowlist_features {
features.push(feature).unwrap();
features.insert(feature.to_string());
}
if config.tauri.cli.is_some() {
features.push("cli").unwrap();
features.insert("cli".to_string());
}
if config.tauri.updater.active {
features.push("updater").unwrap();
features.insert("updater".to_string());
}
if config.tauri.system_tray.is_some() {
features.push("system-tray").unwrap();
features.insert("system-tray".to_string());
}
let mut cli_managed_features = all_allowlist_features();
cli_managed_features.extend(vec!["cli", "updater", "system-tray"]);
if let Some(tauri) = tauri_entry.as_table_mut() {
let manifest_features = tauri.entry("features");
if let Item::Value(Value::Array(f)) = &manifest_features {
for feat in f.iter() {
if let Value::String(feature) = feat {
if feature.value() == "menu" {
features.push("menu").unwrap();
if !cli_managed_features.contains(&feature.value().as_str()) {
features.insert(feature.value().to_string());
}
}
}
}
*manifest_features = Item::Value(Value::Array(features.clone()));
*manifest_features = Item::Value(Value::Array(toml_array(&features)));
} else if let Some(tauri) = tauri_entry.as_value_mut() {
match tauri {
Value::InlineTable(table) => {
@ -89,13 +96,13 @@ pub fn rewrite_manifest(config: ConfigHandle) -> crate::Result<Manifest> {
if let Value::Array(f) = &manifest_features {
for feat in f.iter() {
if let Value::String(feature) = feat {
if feature.value() == "menu" {
features.push("menu").unwrap();
if !cli_managed_features.contains(&feature.value().as_str()) {
features.insert(feature.value().to_string());
}
}
}
}
*manifest_features = Value::Array(features.clone());
*manifest_features = Value::Array(toml_array(&features));
}
Value::String(version) => {
let mut def = InlineTable::default();
@ -103,7 +110,7 @@ pub fn rewrite_manifest(config: ConfigHandle) -> crate::Result<Manifest> {
"version",
version.to_string().replace("\"", "").replace(" ", ""),
);
def.get_or_insert("features", Value::Array(features.clone()));
def.get_or_insert("features", Value::Array(toml_array(&features)));
*tauri = Value::InlineTable(def);
}
_ => {
@ -113,9 +120,7 @@ pub fn rewrite_manifest(config: ConfigHandle) -> crate::Result<Manifest> {
}
}
} else {
return Ok(Manifest {
features: features_to_vec(&features),
});
return Ok(Manifest { features });
}
let mut manifest_file =
@ -132,9 +137,7 @@ pub fn rewrite_manifest(config: ConfigHandle) -> crate::Result<Manifest> {
)?;
manifest_file.flush()?;
Ok(Manifest {
features: features_to_vec(&features),
})
Ok(Manifest { features })
}
pub fn get_workspace_members() -> crate::Result<Vec<String>> {