Merge remote-tracking branch 'origin/dev' into feat/open-harmony

This commit is contained in:
Lucas Nogueira
2025-09-01 14:39:50 -03:00
60 changed files with 1150 additions and 452 deletions

6
.changes/CHANGES.md Normal file
View File

@@ -0,0 +1,6 @@
---
'tauri-cli': 'minor:feat'
'tauri-bundler': 'minor:feat'
---
Add a `--no-sign` flag to the `tauri build` and `tauri bundle` commands to skip the code signing step, improving the developer experience for local testing and development without requiring code signing keys.

6
.changes/data-dir-js.md Normal file
View File

@@ -0,0 +1,6 @@
---
"tauri-utils": "minor:enhance"
"@tauri-apps/api": "minor:enhance"
---
Added a config to set a data_directory relative to the app-specific data dir in JavaScript and `tauri.conf.json`.

View File

@@ -0,0 +1,6 @@
---
"@tauri-apps/cli": patch:enhance
"tauri-cli": patch:enhance
---
Set a default log level filter when running `tauri add log`.

View File

@@ -0,0 +1,6 @@
---
"@tauri-apps/cli": minor:enhance
"tauri-cli": minor:enhance
---
Prompt to install the iOS platform if it isn't installed yet.

50
Cargo.lock generated
View File

@@ -1057,8 +1057,8 @@ dependencies = [
[[package]]
name = "cargo-mobile2"
version = "0.20.4"
source = "git+https://github.com/tauri-apps/cargo-mobile2?branch=feat/ohos#b26dda2b04c1306fb0156599d3ef0f289419e6ee"
version = "0.20.7"
source = "git+https://github.com/tauri-apps/cargo-mobile2?branch=feat/ohos#7e25240a471c7e17b7a77e8ef215061d19866dcc"
dependencies = [
"colored",
"core-foundation 0.10.0",
@@ -1321,7 +1321,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
dependencies = [
"lazy_static",
"windows-sys 0.59.0",
"windows-sys 0.48.0",
]
[[package]]
@@ -2388,7 +2388,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [
"libc",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -4158,9 +4158,9 @@ dependencies = [
[[package]]
name = "jsonschema"
version = "0.32.1"
version = "0.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24690c68dfcdde5980d676b0f1820981841016b1f29eecb4c42ad48ab4118681"
checksum = "d46662859bc5f60a145b75f4632fbadc84e829e45df6c5de74cfc8e05acb96b5"
dependencies = [
"ahash 0.8.11",
"base64 0.22.1",
@@ -4357,7 +4357,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
dependencies = [
"cfg-if",
"windows-targets 0.52.6",
"windows-targets 0.48.5",
]
[[package]]
@@ -5457,9 +5457,9 @@ checksum = "e95bb83d997b0d7f1d04e9b2ba8874a1bd8e89dc5e88d0852a4390f8b44b0703"
[[package]]
name = "ohos-web-binding"
version = "0.1.0"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "390cbdbe97bddcb5dcd46d52654799525ef42962cf6f360c57b4bb72597ebd61"
checksum = "b1ed1f2ba96b92179128f6b18ea5a0dd879210e483f7dcab51200219cbfa4a58"
dependencies = [
"bitflags 2.7.0",
"ohos-web-sys",
@@ -6666,7 +6666,7 @@ dependencies = [
"once_cell",
"socket2",
"tracing",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -6985,9 +6985,9 @@ dependencies = [
[[package]]
name = "referencing"
version = "0.32.1"
version = "0.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a3d769362109497b240e66462606bc28af68116436c8669bac17069533b908e"
checksum = "9e9c261f7ce75418b3beadfb3f0eb1299fe8eb9640deba45ffa2cb783098697d"
dependencies = [
"ahash 0.8.11",
"fluent-uri",
@@ -7385,7 +7385,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys 0.4.15",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -7398,7 +7398,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys 0.9.4",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -8766,7 +8766,7 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]]
name = "tauri"
version = "2.8.2"
version = "2.8.5"
dependencies = [
"anyhow",
"bytes",
@@ -8829,7 +8829,7 @@ dependencies = [
[[package]]
name = "tauri-build"
version = "2.4.0"
version = "2.4.1"
dependencies = [
"anyhow",
"cargo_toml",
@@ -8852,7 +8852,7 @@ dependencies = [
[[package]]
name = "tauri-bundler"
version = "2.6.0"
version = "2.6.1"
dependencies = [
"anyhow",
"ar",
@@ -8898,7 +8898,7 @@ dependencies = [
[[package]]
name = "tauri-cli"
version = "2.8.0"
version = "2.8.4"
dependencies = [
"anyhow",
"ar",
@@ -9101,9 +9101,9 @@ dependencies = [
[[package]]
name = "tauri-plugin-log"
version = "2.4.0"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d2b582d860eb214f28323f4ce4f2797ae3b78f197e27b11677f976f9f52aedb"
checksum = "a59139183e0907cec1499dddee4e085f5a801dc659efa0848ee224f461371426"
dependencies = [
"android_logger",
"byte-unit",
@@ -9158,7 +9158,7 @@ dependencies = [
[[package]]
name = "tauri-runtime-wry"
version = "2.8.0"
version = "2.8.1"
dependencies = [
"gtk",
"http 1.3.1",
@@ -9281,7 +9281,7 @@ dependencies = [
"getrandom 0.2.15",
"once_cell",
"rustix 0.38.43",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -10597,7 +10597,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.59.0",
"windows-sys 0.48.0",
]
[[package]]
@@ -11229,8 +11229,8 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
[[package]]
name = "wry"
version = "0.53.1"
source = "git+https://github.com/richerfu/wry#136e9a9bf3052be539889608f06e80116375ecfd"
version = "0.53.3"
source = "git+https://github.com/richerfu/wry#3b3762817ec6d9b84db594cf11bb6dc3b95d3244"
dependencies = [
"base64 0.22.1",
"block2 0.6.0",

View File

@@ -10,6 +10,8 @@
html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png",
html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png"
)]
// file is used by multiple binaries
#![allow(dead_code)]
use std::{fs::File, io::BufReader};
mod utils;

View File

@@ -106,10 +106,9 @@ fn run_max_mem_benchmark() -> Result<HashMap<String, u64>> {
let proc_result = proc.wait_with_output()?;
println!("{proc_result:?}");
results.insert(
name.to_string(),
utils::parse_max_mem(benchmark_file).unwrap(),
);
if let Some(max_mem) = utils::parse_max_mem(benchmark_file)? {
results.insert(name.to_string(), max_mem);
}
}
Ok(results)
@@ -229,7 +228,7 @@ fn run_exec_time(target_dir: &Path) -> Result<HashMap<String, HashMap<String, f6
);
}
utils::run(&command.iter().map(|s| s.as_ref()).collect::<Vec<_>>());
utils::run(&command.iter().map(|s| s.as_ref()).collect::<Vec<_>>())?;
let mut results = HashMap::<String, HashMap<String, f64>>::new();
let hyperfine_results = utils::read_json(benchmark_file)?;
@@ -264,7 +263,7 @@ fn main() -> Result<()> {
utils::download_file(
"https://github.com/lemarier/tauri-test/releases/download/v2.0.0/json_3mb.json",
json_3mb,
);
)?;
}
println!("Starting tauri benchmark");
@@ -278,7 +277,7 @@ fn main() -> Result<()> {
let now = time::OffsetDateTime::now_utc();
let mut new_data = utils::BenchResult {
created_at: now.format(&format).unwrap(),
sha1: utils::run_collect(&["git", "rev-parse", "HEAD"])
sha1: utils::run_collect(&["git", "rev-parse", "HEAD"])?
.0
.trim()
.to_string(),

View File

@@ -2,7 +2,16 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use anyhow::Result;
//! Utility functions for benchmarking tasks in the Tauri project.
//!
//! This module provides helpers for:
//! - Paths to project directories and targets
//! - Running and collecting process outputs
//! - Parsing memory profiler (`mprof`) and syscall profiler (`strace`) outputs
//! - JSON read/write utilities
//! - File download utilities (via `curl` or file copy)
use anyhow::{bail, Context, Result};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::{
@@ -13,6 +22,7 @@ use std::{
process::{Command, Output, Stdio},
};
/// Holds the results of a benchmark run.
#[derive(Default, Clone, Serialize, Deserialize, Debug)]
pub struct BenchResult {
pub created_at: String,
@@ -25,7 +35,7 @@ pub struct BenchResult {
pub cargo_deps: HashMap<String, usize>,
}
#[allow(dead_code)]
/// Represents a single line of parsed `strace` output.
#[derive(Debug, Clone, Serialize)]
pub struct StraceOutput {
pub percent_time: f64,
@@ -35,6 +45,7 @@ pub struct StraceOutput {
pub errors: u64,
}
/// Get the compilation target triple for the current platform.
pub fn get_target() -> &'static str {
#[cfg(target_os = "macos")]
return if cfg!(target_arch = "aarch64") {
@@ -42,18 +53,22 @@ pub fn get_target() -> &'static str {
} else {
"x86_64-apple-darwin"
};
#[cfg(target_os = "ios")]
return if cfg!(target_arch = "aarch64") {
"aarch64-apple-ios"
} else {
"x86_64-apple-ios"
};
#[cfg(target_os = "linux")]
return "x86_64-unknown-linux-gnu";
#[cfg(target_os = "windows")]
unimplemented!();
unimplemented!("Windows target not implemented yet");
}
/// Get the `target/release` directory path for benchmarks.
pub fn target_dir() -> PathBuf {
bench_root_path()
.join("..")
@@ -62,83 +77,90 @@ pub fn target_dir() -> PathBuf {
.join("release")
}
/// Get the root path of the current benchmark crate.
pub fn bench_root_path() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
}
#[allow(dead_code)]
/// Get the home directory of the current user.
pub fn home_path() -> PathBuf {
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "linux"))]
return PathBuf::from(env!("HOME"));
{
PathBuf::from(std::env::var("HOME").unwrap_or_default())
}
#[cfg(target_os = "windows")]
return PathBuf::from(env!("HOMEPATH"));
{
PathBuf::from(std::env::var("USERPROFILE").unwrap_or_default())
}
}
#[allow(dead_code)]
/// Get the root path of the Tauri repository.
pub fn tauri_root_path() -> PathBuf {
bench_root_path().parent().unwrap().to_path_buf()
bench_root_path().parent().map(|p| p.to_path_buf()).unwrap()
}
#[allow(dead_code)]
pub fn run_collect(cmd: &[&str]) -> (String, String) {
let mut process_builder = Command::new(cmd[0]);
process_builder
/// Run a command and collect its stdout and stderr as strings.
/// Returns an error if the command fails or exits with a non-zero status.
pub fn run_collect(cmd: &[&str]) -> Result<(String, String)> {
let output: Output = Command::new(cmd[0])
.args(&cmd[1..])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
let prog = process_builder.spawn().expect("failed to spawn script");
let Output {
stdout,
stderr,
status,
} = prog.wait_with_output().expect("failed to wait on child");
let stdout = String::from_utf8_lossy(&stdout).to_string();
let stderr = String::from_utf8_lossy(&stderr).to_string();
if !status.success() {
eprintln!("stdout: <<<{stdout}>>>");
eprintln!("stderr: <<<{stderr}>>>");
panic!("Unexpected exit code: {:?}", status.code());
.stderr(Stdio::piped())
.output()
.with_context(|| format!("failed to execute command: {cmd:?}"))?;
if !output.status.success() {
bail!(
"Command {:?} exited with {:?}\nstdout:\n{}\nstderr:\n{}",
cmd,
output.status.code(),
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
(stdout, stderr)
Ok((
String::from_utf8_lossy(&output.stdout).to_string(),
String::from_utf8_lossy(&output.stderr).to_string(),
))
}
#[allow(dead_code)]
pub fn parse_max_mem(file_path: &str) -> Option<u64> {
let file = fs::File::open(file_path).unwrap();
/// Parse a memory profiler (`mprof`) output file and return the maximum
/// memory usage in bytes. Returns `None` if no values are found.
pub fn parse_max_mem(file_path: &str) -> Result<Option<u64>> {
let file = fs::File::open(file_path)
.with_context(|| format!("failed to open mprof output file {file_path}"))?;
let output = BufReader::new(file);
let mut highest: u64 = 0;
// MEM 203.437500 1621617192.4123
for line in output.lines().map_while(Result::ok) {
// split line by space
let split = line.split(' ').collect::<Vec<_>>();
let split: Vec<&str> = line.split(' ').collect();
if split.len() == 3 {
// mprof generate result in MB
let current_bytes = str::parse::<f64>(split[1]).unwrap() as u64 * 1024 * 1024;
if current_bytes > highest {
highest = current_bytes;
if let Ok(mb) = split[1].parse::<f64>() {
let current_bytes = (mb * 1024.0 * 1024.0) as u64;
highest = highest.max(current_bytes);
}
}
}
fs::remove_file(file_path).unwrap();
// Best-effort cleanup
let _ = fs::remove_file(file_path);
if highest > 0 {
return Some(highest);
}
None
Ok(if highest > 0 { Some(highest) } else { None })
}
#[allow(dead_code)]
/// Parse the output of `strace -c` and return a summary of syscalls.
pub fn parse_strace_output(output: &str) -> HashMap<String, StraceOutput> {
let mut summary = HashMap::new();
let mut lines = output
.lines()
.filter(|line| !line.is_empty() && !line.contains("detached ..."));
let count = lines.clone().count();
let count = lines.clone().count();
if count < 4 {
return summary;
}
@@ -148,88 +170,90 @@ pub fn parse_strace_output(output: &str) -> HashMap<String, StraceOutput> {
let data_lines = lines.skip(2);
for line in data_lines {
let syscall_fields = line.split_whitespace().collect::<Vec<_>>();
let syscall_fields: Vec<&str> = line.split_whitespace().collect();
let len = syscall_fields.len();
let syscall_name = syscall_fields.last().unwrap();
if (5..=6).contains(&len) {
summary.insert(
syscall_name.to_string(),
StraceOutput {
percent_time: str::parse::<f64>(syscall_fields[0]).unwrap(),
seconds: str::parse::<f64>(syscall_fields[1]).unwrap(),
usecs_per_call: Some(str::parse::<u64>(syscall_fields[2]).unwrap()),
calls: str::parse::<u64>(syscall_fields[3]).unwrap(),
errors: if syscall_fields.len() < 6 {
if let Some(&syscall_name) = syscall_fields.last() {
if (5..=6).contains(&len) {
let output = StraceOutput {
percent_time: syscall_fields[0].parse().unwrap_or(0.0),
seconds: syscall_fields[1].parse().unwrap_or(0.0),
usecs_per_call: syscall_fields[2].parse().ok(),
calls: syscall_fields[3].parse().unwrap_or(0),
errors: if len < 6 {
0
} else {
str::parse::<u64>(syscall_fields[4]).unwrap()
syscall_fields[4].parse().unwrap_or(0)
},
},
);
};
summary.insert(syscall_name.to_string(), output);
}
}
}
let total_fields = total_line.split_whitespace().collect::<Vec<_>>();
summary.insert(
"total".to_string(),
match total_fields.len() {
// Old format, has no usecs/call
5 => StraceOutput {
percent_time: str::parse::<f64>(total_fields[0]).unwrap(),
seconds: str::parse::<f64>(total_fields[1]).unwrap(),
usecs_per_call: None,
calls: str::parse::<u64>(total_fields[2]).unwrap(),
errors: str::parse::<u64>(total_fields[3]).unwrap(),
},
6 => StraceOutput {
percent_time: str::parse::<f64>(total_fields[0]).unwrap(),
seconds: str::parse::<f64>(total_fields[1]).unwrap(),
usecs_per_call: Some(str::parse::<u64>(total_fields[2]).unwrap()),
calls: str::parse::<u64>(total_fields[3]).unwrap(),
errors: str::parse::<u64>(total_fields[4]).unwrap(),
},
_ => panic!("Unexpected total field count: {}", total_fields.len()),
let total_fields: Vec<&str> = total_line.split_whitespace().collect();
let total = match total_fields.len() {
5 => StraceOutput {
percent_time: total_fields[0].parse().unwrap_or(0.0),
seconds: total_fields[1].parse().unwrap_or(0.0),
usecs_per_call: None,
calls: total_fields[2].parse().unwrap_or(0),
errors: total_fields[3].parse().unwrap_or(0),
},
);
6 => StraceOutput {
percent_time: total_fields[0].parse().unwrap_or(0.0),
seconds: total_fields[1].parse().unwrap_or(0.0),
usecs_per_call: total_fields[2].parse().ok(),
calls: total_fields[3].parse().unwrap_or(0),
errors: total_fields[4].parse().unwrap_or(0),
},
_ => {
panic!("Unexpected total field count: {}", total_fields.len());
}
};
summary.insert("total".to_string(), total);
summary
}
#[allow(dead_code)]
pub fn run(cmd: &[&str]) {
let mut process_builder = Command::new(cmd[0]);
process_builder.args(&cmd[1..]).stdin(Stdio::piped());
let mut prog = process_builder.spawn().expect("failed to spawn script");
let status = prog.wait().expect("failed to wait on child");
/// Run a command and wait for completion.
/// Returns an error if the command fails.
pub fn run(cmd: &[&str]) -> Result<()> {
let status = Command::new(cmd[0])
.args(&cmd[1..])
.stdin(Stdio::piped())
.status()
.with_context(|| format!("failed to execute command: {cmd:?}"))?;
if !status.success() {
panic!("Unexpected exit code: {:?}", status.code());
bail!("Command {:?} exited with {:?}", cmd, status.code());
}
Ok(())
}
#[allow(dead_code)]
/// Read a JSON file into a [`serde_json::Value`].
pub fn read_json(filename: &str) -> Result<Value> {
let f = fs::File::open(filename)?;
let f =
fs::File::open(filename).with_context(|| format!("failed to open JSON file {filename}"))?;
Ok(serde_json::from_reader(f)?)
}
#[allow(dead_code)]
/// Write a [`serde_json::Value`] into a JSON file.
pub fn write_json(filename: &str, value: &Value) -> Result<()> {
let f = fs::File::create(filename)?;
let f =
fs::File::create(filename).with_context(|| format!("failed to create JSON file {filename}"))?;
serde_json::to_writer(f, value)?;
Ok(())
}
#[allow(dead_code)]
pub fn download_file(url: &str, filename: PathBuf) {
/// Download a file from either a local path or an HTTP/HTTPS URL.
/// Falls back to copying the file if the URL does not start with http/https.
pub fn download_file(url: &str, filename: PathBuf) -> Result<()> {
if !url.starts_with("http:") && !url.starts_with("https:") {
fs::copy(url, filename).unwrap();
return;
fs::copy(url, &filename).with_context(|| format!("failed to copy from {url}"))?;
return Ok(());
}
// Downloading with curl this saves us from adding
// a Rust HTTP client dependency.
println!("Downloading {url}");
let status = Command::new("curl")
.arg("-L")
@@ -238,8 +262,14 @@ pub fn download_file(url: &str, filename: PathBuf) {
.arg(&filename)
.arg(url)
.status()
.unwrap();
.with_context(|| format!("failed to execute curl for {url}"))?;
assert!(status.success());
assert!(filename.exists());
if !status.success() {
bail!("curl failed with exit code {:?}", status.code());
}
if !filename.exists() {
bail!("expected file {:?} to exist after download", filename);
}
Ok(())
}

View File

@@ -1,5 +1,11 @@
# Changelog
## \[2.4.1]
### Enhancements
- [`c23bec62d`](https://www.github.com/tauri-apps/tauri/commit/c23bec62d6d5724798869681aa1534423aae28e2) ([#14083](https://www.github.com/tauri-apps/tauri/pull/14083) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Tauri now ignores `macOS.minimumSystemVersion` in `tauri dev` to prevent forced rebuilds of macOS specific dependencies when using something like `rust-analyzer` at the same time as `tauri dev`.
## \[2.4.0]
### Dependencies

View File

@@ -1,6 +1,6 @@
[package]
name = "tauri-build"
version = "2.4.0"
version = "2.4.1"
description = "build time code to pair with https://crates.io/crates/tauri"
exclude = ["CHANGELOG.md", "/target"]
readme = "README.md"

View File

@@ -577,8 +577,10 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
}
}
if let Some(version) = &config.bundle.macos.minimum_system_version {
println!("cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET={version}");
if !is_dev() {
if let Some(version) = &config.bundle.macos.minimum_system_version {
println!("cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET={version}");
}
}
}

View File

@@ -1,5 +1,11 @@
# Changelog
## \[2.6.1]
### Bug Fixes
- [`f3df96fb3`](https://www.github.com/tauri-apps/tauri/commit/f3df96fb38e2f27ce6bf232fe87f35bcfec50ce4) ([#14065](https://www.github.com/tauri-apps/tauri/pull/14065) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix binary patching updater type fails on 32 bit Windows builds
## \[2.6.0]
### New Features

View File

@@ -1,6 +1,6 @@
[package]
name = "tauri-bundler"
version = "2.6.0"
version = "2.6.1"
authors = [
"George Burton <burtonageo@gmail.com>",
"Tauri Programme within The Commons Conservancy",

View File

@@ -85,41 +85,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<Bundle>> {
}
// Sign windows binaries before the bundling step in case neither wix and nsis bundles are enabled
if matches!(target_os, TargetPlatform::Windows) {
if settings.can_sign() {
for bin in settings.binaries() {
if bin.main() {
// we will sign the main binary after patching per "package type"
continue;
}
let bin_path = settings.binary_path(bin);
windows::sign::try_sign(&bin_path, settings)?;
}
// Sign the sidecar binaries
for bin in settings.external_binaries() {
let path = bin?;
let skip = std::env::var("TAURI_SKIP_SIDECAR_SIGNATURE_CHECK").is_ok_and(|v| v == "true");
if skip {
continue;
}
#[cfg(windows)]
if windows::sign::verify(&path)? {
log::info!(
"sidecar at \"{}\" already signed. Skipping...",
path.display()
);
continue;
}
windows::sign::try_sign(&path, settings)?;
}
} else {
#[cfg(not(target_os = "windows"))]
log::warn!("Signing, by default, is only supported on Windows hosts, but you can specify a custom signing command in `bundler > windows > sign_command`, for now, skipping signing the installer...");
}
}
sign_binaries_if_needed(settings, target_os)?;
let main_binary = settings
.binaries()
@@ -134,8 +100,9 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<Bundle>> {
// - codesigning tools should handle calculating+updating this, we just need to ensure
// (re)signing is performed after every `patch_binary()` operation
// - signing an already-signed binary can result in multiple signatures, causing verification errors
let main_binary_reset_required =
matches!(target_os, TargetPlatform::Windows) && settings.can_sign() && package_types.len() > 1;
let main_binary_reset_required = matches!(target_os, TargetPlatform::Windows)
&& settings.windows().can_sign()
&& package_types.len() > 1;
let mut unsigned_main_binary_copy = tempfile::tempfile()?;
if main_binary_reset_required {
let mut unsigned_main_binary = std::fs::File::open(&main_binary_path)?;
@@ -155,7 +122,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<Bundle>> {
}
// sign main binary for every package type after patch
if matches!(target_os, TargetPlatform::Windows) && settings.can_sign() {
if matches!(target_os, TargetPlatform::Windows) && settings.windows().can_sign() {
if main_binary_signed && main_binary_reset_required {
let mut signed_main_binary = std::fs::OpenOptions::new()
.write(true)
@@ -305,6 +272,51 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<Bundle>> {
Ok(bundles)
}
fn sign_binaries_if_needed(settings: &Settings, target_os: &TargetPlatform) -> crate::Result<()> {
if matches!(target_os, TargetPlatform::Windows) {
if settings.windows().can_sign() {
if settings.no_sign() {
log::info!("Skipping binary signing due to --no-sign flag.");
return Ok(());
}
for bin in settings.binaries() {
if bin.main() {
// we will sign the main binary after patching per "package type"
continue;
}
let bin_path = settings.binary_path(bin);
windows::sign::try_sign(&bin_path, settings)?;
}
// Sign the sidecar binaries
for bin in settings.external_binaries() {
let path = bin?;
let skip = std::env::var("TAURI_SKIP_SIDECAR_SIGNATURE_CHECK").is_ok_and(|v| v == "true");
if skip {
continue;
}
#[cfg(windows)]
if windows::sign::verify(&path)? {
log::info!(
"sidecar at \"{}\" already signed. Skipping...",
path.display()
);
continue;
}
windows::sign::try_sign(&path, settings)?;
}
} else {
#[cfg(not(target_os = "windows"))]
log::warn!("Signing, by default, is only supported on Windows hosts, but you can specify a custom signing command in `bundler > windows > sign_command`, for now, skipping signing the installer...");
}
}
Ok(())
}
/// Check to see if there are icons in the settings struct
pub fn check_icons(settings: &Settings) -> crate::Result<bool> {
// make a peekable iterator of the icon_files

View File

@@ -103,7 +103,11 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
copy_custom_files_to_bundle(&bundle_directory, settings)?;
if let Some(keychain) = super::sign::keychain(settings.macos().signing_identity.as_deref())? {
if settings.no_sign() {
log::warn!("Skipping signing due to --no-sign flag.",);
} else if let Some(keychain) =
super::sign::keychain(settings.macos().signing_identity.as_deref())?
{
// Sign frameworks and sidecar binaries first, per apple, signing must be done inside out
// https://developer.apple.com/forums/thread/701514
sign_paths.push(SignTarget {

View File

@@ -195,7 +195,7 @@ pub fn bundle_project(settings: &Settings, bundles: &[Bundle]) -> crate::Result<
// Sign DMG if needed
// skipping self-signing DMGs https://github.com/tauri-apps/tauri/issues/12288
let identity = settings.macos().signing_identity.as_deref();
if identity != Some("-") {
if !settings.no_sign() && identity != Some("-") {
if let Some(keychain) = super::sign::keychain(identity)? {
super::sign::sign(
&keychain,

View File

@@ -572,6 +572,12 @@ pub struct WindowsSettings {
pub sign_command: Option<CustomSignCommandSettings>,
}
impl WindowsSettings {
pub(crate) fn can_sign(&self) -> bool {
self.sign_command.is_some() || self.certificate_thumbprint.is_some()
}
}
#[allow(deprecated)]
mod _default {
use super::*;
@@ -778,6 +784,8 @@ pub struct Settings {
target_platform: TargetPlatform,
/// The target triple.
target: String,
/// Whether to disable code signing during the bundling process.
no_sign: bool,
}
/// A builder for [`Settings`].
@@ -791,6 +799,7 @@ pub struct SettingsBuilder {
binaries: Vec<BundleBinary>,
target: Option<String>,
local_tools_directory: Option<PathBuf>,
no_sign: bool,
}
impl SettingsBuilder {
@@ -860,6 +869,13 @@ impl SettingsBuilder {
self
}
/// Sets whether to skip code signing.
#[must_use]
pub fn no_sign(mut self, no_sign: bool) -> Self {
self.no_sign = no_sign;
self
}
/// Builds a Settings from the CLI args.
///
/// Package settings will be read from Cargo.toml.
@@ -894,6 +910,7 @@ impl SettingsBuilder {
},
target_platform,
target,
no_sign: self.no_sign,
})
}
}
@@ -1242,4 +1259,14 @@ impl Settings {
pub fn updater(&self) -> Option<&UpdaterSettings> {
self.bundle_settings.updater.as_ref()
}
/// Whether to skip signing.
pub fn no_sign(&self) -> bool {
self.no_sign
}
/// Set whether to skip signing.
pub fn set_no_sign(&mut self, no_sign: bool) {
self.no_sign = no_sign;
}
}

View File

@@ -470,7 +470,7 @@ pub fn build_wix_app_installer(
fs::create_dir_all(&output_path)?;
// when we're performing code signing, we'll sign some WiX DLLs, so we make a local copy
let wix_toolset_path = if settings.can_sign() {
let wix_toolset_path = if settings.windows().can_sign() {
let wix_path = output_path.join("wix");
crate::utils::fs_utils::copy_dir(wix_toolset_path, &wix_path)
.context("failed to copy wix directory")?;
@@ -771,7 +771,7 @@ pub fn build_wix_app_installer(
let mut extensions = Vec::new();
for cap in extension_regex.captures_iter(&fragment) {
let path = wix_toolset_path.join(format!("Wix{}.dll", &cap[1]));
if settings.can_sign() {
if settings.windows().can_sign() {
try_sign(&path, settings)?;
}
extensions.push(path);
@@ -785,7 +785,7 @@ pub fn build_wix_app_installer(
fragment_extensions.insert(wix_toolset_path.join("WixUtilExtension.dll"));
// sign default extensions
if settings.can_sign() {
if settings.windows().can_sign() {
for path in &fragment_extensions {
try_sign(path, settings)?;
}
@@ -879,7 +879,7 @@ pub fn build_wix_app_installer(
)?;
fs::rename(&msi_output_path, &msi_path)?;
if settings.can_sign() {
if settings.windows().can_sign() {
try_sign(&msi_path, settings)?;
}
@@ -988,7 +988,7 @@ fn generate_resource_data(settings: &Settings) -> crate::Result<ResourceMap> {
}
added_resources.push(resource_path.clone());
if settings.can_sign() && should_sign(&resource_path)? {
if settings.windows().can_sign() && should_sign(&resource_path)? {
try_sign(&resource_path, settings)?;
}
@@ -1076,7 +1076,7 @@ fn generate_resource_data(settings: &Settings) -> crate::Result<ResourceMap> {
.to_string_lossy()
.into_owned();
if !added_resources.iter().any(|r| r.ends_with(&relative_path)) {
if settings.can_sign() {
if settings.windows().can_sign() {
try_sign(resource_path, settings)?;
}

View File

@@ -192,7 +192,7 @@ fn build_nsis_app_installer(
// we make a copy of the NSIS directory if we're going to sign its DLLs
// because we don't want to change the DLL hashes so the cache can reuse it
let maybe_plugin_copy_path = if settings.can_sign() {
let maybe_plugin_copy_path = if settings.windows().can_sign() {
// find nsis path
#[cfg(target_os = "linux")]
let system_nsis_toolset_path = std::env::var_os("NSIS_PATH")
@@ -283,7 +283,7 @@ fn build_nsis_app_installer(
);
data.insert("copyright", to_json(settings.copyright_string()));
if settings.can_sign() {
if settings.windows().can_sign() {
let sign_cmd = format!("{:?}", sign_command("%1", &settings.sign_params())?);
data.insert("uninstaller_sign_cmd", to_json(sign_cmd));
}
@@ -600,7 +600,7 @@ fn build_nsis_app_installer(
));
fs::create_dir_all(nsis_installer_path.parent().unwrap())?;
if settings.can_sign() {
if settings.windows().can_sign() {
log::info!("Signing NSIS plugins");
for dll in NSIS_PLUGIN_FILES {
let path = additional_plugins_path.join(dll);
@@ -640,7 +640,7 @@ fn build_nsis_app_installer(
fs::rename(nsis_output_path, &nsis_installer_path)?;
if settings.can_sign() {
if settings.windows().can_sign() {
try_sign(&nsis_installer_path, settings)?;
} else {
#[cfg(not(target_os = "windows"))]
@@ -718,7 +718,7 @@ fn generate_resource_data(settings: &Settings) -> crate::Result<ResourcesMap> {
let loader_path =
dunce::simplified(&settings.project_out_directory().join("WebView2Loader.dll")).to_path_buf();
if loader_path.exists() {
if settings.can_sign() {
if settings.windows().can_sign() {
try_sign(&loader_path, settings)?;
}
added_resources.push(loader_path.clone());
@@ -743,7 +743,7 @@ fn generate_resource_data(settings: &Settings) -> crate::Result<ResourcesMap> {
}
added_resources.push(resource_path.clone());
if settings.can_sign() && should_sign(&resource_path)? {
if settings.windows().can_sign() && should_sign(&resource_path)? {
try_sign(&resource_path, settings)?;
}

View File

@@ -14,10 +14,6 @@ use std::sync::OnceLock;
use std::{path::Path, process::Command};
impl Settings {
pub(crate) fn can_sign(&self) -> bool {
self.windows().sign_command.is_some() || self.windows().certificate_thumbprint.is_some()
}
pub(crate) fn sign_params(&self) -> SignParams {
SignParams {
product_name: self.product_name().into(),
@@ -251,7 +247,14 @@ pub fn sign<P: AsRef<Path>>(path: P, params: &SignParams) -> crate::Result<()> {
}
pub fn try_sign<P: AsRef<Path>>(file_path: P, settings: &Settings) -> crate::Result<()> {
if settings.can_sign() {
if settings.no_sign() {
log::warn!(
"Skipping signing for {} due to --no-sign flag.",
tauri_utils::display_path(file_path.as_ref())
);
return Ok(());
}
if settings.windows().can_sign() {
log::info!(action = "Signing"; "{}", tauri_utils::display_path(file_path.as_ref()));
sign(file_path, &settings.sign_params())?;
}

View File

@@ -100,17 +100,16 @@ pub fn patch_binary(binary_path: &PathBuf, package_type: &crate::PackageType) ->
.ok_or(crate::Error::MissingBundleTypeVar)?;
let data_offset = tauri_bundle_section.pointer_to_raw_data as usize;
if data_offset + 8 > file_data.len() {
return Err(crate::Error::BinaryOffsetOutOfRange);
}
let ptr_bytes = &file_data[data_offset..data_offset + 8];
let ptr_value = u64::from_le_bytes(ptr_bytes.try_into().map_err(|_| {
crate::Error::BinaryParseError(
std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid pointer bytes").into(),
)
})?);
let pointer_size = if pe.is_64 { 8 } else { 4 };
let ptr_bytes = file_data
.get(data_offset..data_offset + pointer_size)
.ok_or(crate::Error::BinaryOffsetOutOfRange)?;
// `try_into` is safe to `unwrap` here because we have already checked the slice's size through `get`
let ptr_value = if pe.is_64 {
u64::from_le_bytes(ptr_bytes.try_into().unwrap())
} else {
u32::from_le_bytes(ptr_bytes.try_into().unwrap()).into()
};
let rdata_section = pe
.sections
@@ -133,12 +132,10 @@ pub fn patch_binary(binary_path: &PathBuf, package_type: &crate::PackageType) ->
let file_offset = rdata_section.pointer_to_raw_data as usize
+ (rva as usize).saturating_sub(rdata_section.virtual_address as usize);
if file_offset + 3 > file_data.len() {
return Err(crate::Error::BinaryOffsetOutOfRange);
}
// Overwrite the string at that offset
let string_bytes = &mut file_data[file_offset..file_offset + 3];
let string_bytes = file_data
.get_mut(file_offset..file_offset + 3)
.ok_or(crate::Error::BinaryOffsetOutOfRange)?;
match package_type {
crate::PackageType::Nsis => string_bytes.copy_from_slice(b"NSS"),
crate::PackageType::WindowsMsi => string_bytes.copy_from_slice(b"MSI"),

View File

@@ -1,5 +1,33 @@
# Changelog
## \[2.8.4]
### Enhancements
- [`f70b28529`](https://www.github.com/tauri-apps/tauri/commit/f70b28529d226a2dec2f41709d8934f8f5adab25) ([#14093](https://www.github.com/tauri-apps/tauri/pull/14093) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Ensure Rust targets for mobile are installed when running the dev and build commands (previously only checked on init).
- [`a9b342125`](https://www.github.com/tauri-apps/tauri/commit/a9b342125d5ac1bc9a4b2e8b5f73e8ca3cbcb8b2) ([#14114](https://www.github.com/tauri-apps/tauri/pull/14114) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix iOS dev and build targeting the simulator on Intel machines.
- [`61b9b681e`](https://www.github.com/tauri-apps/tauri/commit/61b9b681e88067a53b79d2318ae005dc25addcd6) ([#14111](https://www.github.com/tauri-apps/tauri/pull/14111) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Retain `RUST_*` environment variables when running the mobile commands.
- [`c23bec62d`](https://www.github.com/tauri-apps/tauri/commit/c23bec62d6d5724798869681aa1534423aae28e2) ([#14083](https://www.github.com/tauri-apps/tauri/pull/14083) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Tauri now ignores `macOS.minimumSystemVersion` in `tauri dev` to prevent forced rebuilds of macOS specific dependencies when using something like `rust-analyzer` at the same time as `tauri dev`.
### Bug Fixes
- [`c37a29833`](https://www.github.com/tauri-apps/tauri/commit/c37a298331d6d744b15d32d55a2db83c884a3d6a) ([#14112](https://www.github.com/tauri-apps/tauri/pull/14112) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix usage with Deno failing with `ReferenceError: require is not defined`.
- [`bcf000c0a`](https://www.github.com/tauri-apps/tauri/commit/bcf000c0a8607eedf488fb949b982f519abda43d) ([#14110](https://www.github.com/tauri-apps/tauri/pull/14110) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes running `ios` commands with `deno` crashing due to incorrect current working directory resolution.
- [`7db7142f9`](https://www.github.com/tauri-apps/tauri/commit/7db7142f9ff7dc2f5719602e199b77129ceb19d3) ([#14119](https://www.github.com/tauri-apps/tauri/pull/14119) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes empty device name when using an Android emulator causing the emulator to never be detected as running.
- [`956b4fd6f`](https://www.github.com/tauri-apps/tauri/commit/956b4fd6ffbb4312123b107ca96c87a001359b9d) ([#14106](https://www.github.com/tauri-apps/tauri/pull/14106) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Use the correct export method on Xcode < 15.4.
## \[2.8.3]
### Bug Fixes
- [`0ac89d3b6`](https://www.github.com/tauri-apps/tauri/commit/0ac89d3b6c8c4a4826a4c42726e4f4a8941b3fde) ([#14078](https://www.github.com/tauri-apps/tauri/pull/14078) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Updated `cargo-mobile2` to allow running on iOS simulators that have a higher version than the XCode SDK. This fixes compatiblity issues with Apple's recent "iOS 18.5 + iOS 18.6 Simulator" platform support component.
## \[2.8.1]
### Dependencies
- Upgraded to `tauri-bundler@2.6.1`
## \[2.8.0]
### New Features

View File

@@ -1,6 +1,6 @@
[package]
name = "tauri-cli"
version = "2.8.0"
version = "2.8.4"
authors = ["Tauri Programme within The Commons Conservancy"]
edition = "2021"
rust-version = "1.77.2"
@@ -36,7 +36,7 @@ name = "cargo-tauri"
path = "src/main.rs"
[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\", target_os = \"windows\", target_os = \"macos\"))".dependencies]
cargo-mobile2 = { version = "0.20.2", default-features = false }
cargo-mobile2 = { version = "0.20.6", default-features = false }
[dependencies]
jsonrpsee = { version = "0.24", features = ["server"] }
@@ -47,7 +47,7 @@ sublime_fuzzy = "0.7"
clap_complete = "4"
clap = { version = "4", features = ["derive", "env"] }
anyhow = "1"
tauri-bundler = { version = "2.6.0", default-features = false, path = "../tauri-bundler" }
tauri-bundler = { version = "2.6.1", default-features = false, path = "../tauri-bundler" }
colored = "2"
serde = { version = "1", features = ["derive"] }
serde_json = { version = "1", features = ["preserve_order"] }
@@ -66,7 +66,7 @@ tauri-utils = { version = "2.7.0", path = "../tauri-utils", features = [
"html-manipulation",
] }
toml = "0.9"
jsonschema = "0.32"
jsonschema = "0.33"
handlebars = "6"
include_dir = "0.7"
minisign = "=0.7.3"

View File

@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://schema.tauri.app/config/2.8.2",
"$id": "https://schema.tauri.app/config/2.8.5",
"title": "Config",
"description": "The Tauri configuration object.\n It is read from a file where you can define your frontend assets,\n configure the bundler and define a tray icon.\n\n The configuration file is generated by the\n [`tauri init`](https://v2.tauri.app/reference/cli/#init) command that lives in\n your Tauri application source directory (src-tauri).\n\n Once generated, you may modify it at will to customize your Tauri application.\n\n ## File Formats\n\n By default, the configuration is defined as a JSON file named `tauri.conf.json`.\n\n Tauri also supports JSON5 and TOML files via the `config-json5` and `config-toml` Cargo features, respectively.\n The JSON5 file name must be either `tauri.conf.json` or `tauri.conf.json5`.\n The TOML file name is `Tauri.toml`.\n\n ## Platform-Specific Configuration\n\n In addition to the default configuration file, Tauri can\n read a platform-specific configuration from `tauri.linux.conf.json`,\n `tauri.windows.conf.json`, `tauri.macos.conf.json`, `tauri.android.conf.json` and `tauri.ios.conf.json`\n (or `Tauri.linux.toml`, `Tauri.windows.toml`, `Tauri.macos.toml`, `Tauri.android.toml` and `Tauri.ios.toml` if the `Tauri.toml` format is used),\n which gets merged with the main configuration object.\n\n ## Configuration Structure\n\n The configuration is composed of the following objects:\n\n - [`app`](#appconfig): The Tauri configuration\n - [`build`](#buildconfig): The build configuration\n - [`bundle`](#bundleconfig): The bundle configurations\n - [`plugins`](#pluginconfig): The plugins configuration\n\n Example tauri.config.json file:\n\n ```json\n {\n \"productName\": \"tauri-app\",\n \"version\": \"0.1.0\",\n \"build\": {\n \"beforeBuildCommand\": \"\",\n \"beforeDevCommand\": \"\",\n \"devUrl\": \"http://localhost:3000\",\n \"frontendDist\": \"../dist\"\n },\n \"app\": {\n \"security\": {\n \"csp\": null\n },\n \"windows\": [\n {\n \"fullscreen\": false,\n \"height\": 600,\n \"resizable\": true,\n \"title\": \"Tauri App\",\n \"width\": 800\n }\n ]\n },\n \"bundle\": {},\n \"plugins\": {}\n }\n ```",
"type": "object",
@@ -164,7 +164,7 @@
"type": "object",
"properties": {
"windows": {
"description": "The app windows configuration.",
"description": "The app windows configuration.\n\n ## Example:\n\n To create a window at app startup\n\n ```json\n {\n \"app\": {\n \"windows\": [\n { \"width\": 800, \"height\": 600 }\n ]\n }\n }\n ```\n\n If not specified, the window's label (its identifier) defaults to \"main\",\n you can use this label to get the window through\n `app.get_webview_window` in Rust or `WebviewWindow.getByLabel` in JavaScript\n\n When working with multiple windows, each window will need an unique label\n\n ```json\n {\n \"app\": {\n \"windows\": [\n { \"label\": \"main\", \"width\": 800, \"height\": 600 },\n { \"label\": \"secondary\", \"width\": 800, \"height\": 600 }\n ]\n }\n }\n ```\n\n You can also set `create` to false and use this config through the Rust APIs\n\n ```json\n {\n \"app\": {\n \"windows\": [\n { \"create\": false, \"width\": 800, \"height\": 600 }\n ]\n }\n }\n ```\n\n and use it like this\n\n ```rust\n tauri::Builder::default()\n .setup(|app| {\n tauri::WebviewWindowBuilder::from_config(app.handle(), app.config().app.windows[0])?.build()?;\n Ok(())\n });\n ```",
"default": [],
"type": "array",
"items": {
@@ -230,7 +230,7 @@
"type": "string"
},
"create": {
"description": "Whether Tauri should create this window at app startup or not.\n\n When this is set to `false` you must manually grab the config object via `app.config().app.windows`\n and create it with [`WebviewWindowBuilder::from_config`](https://docs.rs/tauri/2/tauri/webview/struct.WebviewWindowBuilder.html#method.from_config).",
"description": "Whether Tauri should create this window at app startup or not.\n\n When this is set to `false` you must manually grab the config object via `app.config().app.windows`\n and create it with [`WebviewWindowBuilder::from_config`](https://docs.rs/tauri/2/tauri/webview/struct.WebviewWindowBuilder.html#method.from_config).\n\n ## Example:\n\n ```rust\n tauri::Builder::default()\n .setup(|app| {\n tauri::WebviewWindowBuilder::from_config(app.handle(), app.config().app.windows[0])?.build()?;\n Ok(())\n });\n ```",
"default": true,
"type": "boolean"
},
@@ -495,7 +495,7 @@
]
},
"incognito": {
"description": "Whether or not the webview should be launched in incognito mode.\n\n ## Platform-specific:\n\n - **Android**: Unsupported.",
"description": "Whether or not the webview should be launched in incognito mode.\n\n ## Platform-specific:\n\n - **Android**: Unsupported.",
"default": false,
"type": "boolean"
},
@@ -572,6 +572,27 @@
"description": "Allows disabling the input accessory view on iOS.\n\n The accessory view is the view that appears above the keyboard when a text input element is focused.\n It usually displays a view with \"Done\", \"Next\" buttons.",
"default": false,
"type": "boolean"
},
"dataDirectory": {
"description": "Set a custom path for the webview's data directory (localStorage, cache, etc.) **relative to [`appDataDir()`]/${label}**.\n\n To set absolute paths, use [`WebviewWindowBuilder::data_directory`](https://docs.rs/tauri/2/tauri/webview/struct.WebviewWindowBuilder.html#method.data_directory)\n\n #### Platform-specific:\n\n - **Windows**: WebViews with different values for settings like `additionalBrowserArgs`, `browserExtensionsEnabled` or `scrollBarStyle` must have different data directories.\n - **macOS / iOS**: Unsupported, use `dataStoreIdentifier` instead.\n - **Android**: Unsupported.",
"type": [
"string",
"null"
]
},
"dataStoreIdentifier": {
"description": "Initialize the WebView with a custom data store identifier. This can be seen as a replacement for `dataDirectory` which is unavailable in WKWebView.\n See https://developer.apple.com/documentation/webkit/wkwebsitedatastore/init(foridentifier:)?language=objc\n\n The array must contain 16 u8 numbers.\n\n #### Platform-specific:\n\n - **iOS**: Supported since version 17.0+.\n - **macOS**: Supported since version 14.0+.\n - **Windows / Linux / Android**: Unsupported.",
"type": [
"array",
"null"
],
"items": {
"type": "integer",
"format": "uint8",
"minimum": 0.0
},
"maxItems": 16,
"minItems": 16
}
},
"additionalProperties": false
@@ -2078,7 +2099,7 @@
}
},
"resources": {
"description": "App resources to bundle.\n Each resource is a path to a file or directory.\n Glob patterns are supported.",
"description": "App resources to bundle.\n Each resource is a path to a file or directory.\n Glob patterns are supported.\n\n ## Examples\n\n To include a list of files:\n\n ```json\n {\n \"bundle\": {\n \"resources\": [\n \"./path/to/some-file.txt\",\n \"/absolute/path/to/textfile.txt\",\n \"../relative/path/to/jsonfile.json\",\n \"some-folder/\",\n \"resources/**/*.md\"\n ]\n }\n }\n ```\n\n The bundled files will be in `$RESOURCES/` with the original directory structure preserved,\n for example: `./path/to/some-file.txt` -> `$RESOURCE/path/to/some-file.txt`\n\n To fine control where the files will get copied to, use a map instead\n\n ```json\n {\n \"bundle\": {\n \"resources\": {\n \"/absolute/path/to/textfile.txt\": \"resources/textfile.txt\",\n \"relative/path/to/jsonfile.json\": \"resources/jsonfile.json\",\n \"resources/\": \"\",\n \"docs/**/*md\": \"website-docs/\"\n }\n }\n }\n ```\n\n Note that when using glob pattern in this case, the original directory structure is not preserved,\n everything gets copied to the target directory directly\n\n See more: <https://v2.tauri.app/develop/resources/>",
"anyOf": [
{
"$ref": "#/definitions/BundleResources"
@@ -2928,7 +2949,7 @@
]
},
"installerHooks": {
"description": "A path to a `.nsh` file that contains special NSIS macros to be hooked into the\n main installer.nsi script.\n\n Supported hooks are:\n - `NSIS_HOOK_PREINSTALL`: This hook runs before copying files, setting registry key values and creating shortcuts.\n - `NSIS_HOOK_POSTINSTALL`: This hook runs after the installer has finished copying all files, setting the registry keys and created shortcuts.\n - `NSIS_HOOK_PREUNINSTALL`: This hook runs before removing any files, registry keys and shortcuts.\n - `NSIS_HOOK_POSTUNINSTALL`: This hook runs after files, registry keys and shortcuts have been removed.\n\n\n ### Example\n\n ```nsh\n !macro NSIS_HOOK_PREINSTALL\n MessageBox MB_OK \"PreInstall\"\n !macroend\n\n !macro NSIS_HOOK_POSTINSTALL\n MessageBox MB_OK \"PostInstall\"\n !macroend\n\n !macro NSIS_HOOK_PREUNINSTALL\n MessageBox MB_OK \"PreUnInstall\"\n !macroend\n\n !macro NSIS_HOOK_POSTUNINSTALL\n MessageBox MB_OK \"PostUninstall\"\n !macroend\n\n ```",
"description": "A path to a `.nsh` file that contains special NSIS macros to be hooked into the\n main installer.nsi script.\n\n Supported hooks are:\n\n - `NSIS_HOOK_PREINSTALL`: This hook runs before copying files, setting registry key values and creating shortcuts.\n - `NSIS_HOOK_POSTINSTALL`: This hook runs after the installer has finished copying all files, setting the registry keys and created shortcuts.\n - `NSIS_HOOK_PREUNINSTALL`: This hook runs before removing any files, registry keys and shortcuts.\n - `NSIS_HOOK_POSTUNINSTALL`: This hook runs after files, registry keys and shortcuts have been removed.\n\n ### Example\n\n ```nsh\n !macro NSIS_HOOK_PREINSTALL\n MessageBox MB_OK \"PreInstall\"\n !macroend\n\n !macro NSIS_HOOK_POSTINSTALL\n MessageBox MB_OK \"PostInstall\"\n !macroend\n\n !macro NSIS_HOOK_PREUNINSTALL\n MessageBox MB_OK \"PreUnInstall\"\n !macroend\n\n !macro NSIS_HOOK_POSTUNINSTALL\n MessageBox MB_OK \"PostUninstall\"\n !macroend\n ```",
"type": [
"string",
"null"
@@ -3490,7 +3511,7 @@
]
},
"minimumSystemVersion": {
"description": "A version string indicating the minimum macOS X version that the bundled application supports. Defaults to `10.13`.\n\n Setting it to `null` completely removes the `LSMinimumSystemVersion` field on the bundle's `Info.plist`\n and the `MACOSX_DEPLOYMENT_TARGET` environment variable.\n\n An empty string is considered an invalid value so the default value is used.",
"description": "A version string indicating the minimum macOS X version that the bundled application supports. Defaults to `10.13`.\n\n Setting it to `null` completely removes the `LSMinimumSystemVersion` field on the bundle's `Info.plist`\n and the `MACOSX_DEPLOYMENT_TARGET` environment variable.\n\n Ignored in `tauri dev`.\n\n An empty string is considered an invalid value so the default value is used.",
"default": "10.13",
"type": [
"string",

View File

@@ -1,9 +1,9 @@
{
"cli.js": {
"version": "2.8.1",
"version": "2.8.4",
"node": ">= 10.0.0"
},
"tauri": "2.8.2",
"tauri-build": "2.4.0",
"tauri": "2.8.5",
"tauri-build": "2.4.1",
"tauri-plugin": "2.4.0"
}

View File

@@ -132,6 +132,8 @@ pub fn run(options: Options) -> Result<()> {
"Builder::new(todo!()).build()"
} else if plugin == "single-instance" {
"init(|app, args, cwd| {})"
} else if plugin == "log" {
"Builder::new().level(tauri_plugin_log::log::LevelFilter::Info).build()"
} else if metadata.builder {
"Builder::new().build()"
} else {

View File

@@ -76,11 +76,18 @@ pub struct Options {
/// Only use this when you are sure the mismatch is incorrectly detected as version mismatched Tauri packages can lead to unknown behavior.
#[clap(long)]
pub ignore_version_mismatches: bool,
/// Skip code signing when bundling the app
#[clap(long)]
pub no_sign: bool,
}
pub fn command(mut options: Options, verbosity: u8) -> Result<()> {
crate::helpers::app_paths::resolve();
if options.no_sign {
log::warn!("--no-sign flag detected: Signing will be skipped.");
}
let ci = options.ci;
let target = options
@@ -104,6 +111,10 @@ pub fn command(mut options: Options, verbosity: u8) -> Result<()> {
let config_guard = config.lock().unwrap();
let config_ = config_guard.as_ref().unwrap();
if let Some(minimum_system_version) = &config_.bundle.macos.minimum_system_version {
std::env::set_var("MACOSX_DEPLOYMENT_TARGET", minimum_system_version);
}
let app_settings = interface.app_settings();
let interface_options = options.clone().into();

View File

@@ -92,6 +92,14 @@ pub struct Options {
/// On subsequent runs, it's recommended to disable this setting again.
#[clap(long)]
pub skip_stapling: bool,
/// Skip code signing during the build or bundling process.
///
/// Useful for local development and CI environments
/// where signing certificates or environment variables
/// are not available or not needed.
#[clap(long)]
pub no_sign: bool,
}
impl From<crate::build::Options> for Options {
@@ -104,6 +112,7 @@ impl From<crate::build::Options> for Options {
ci: value.ci,
config: value.config,
skip_stapling: value.skip_stapling,
no_sign: value.no_sign,
}
}
}
@@ -136,6 +145,10 @@ pub fn command(options: Options, verbosity: u8) -> crate::Result<()> {
let config_guard = config.lock().unwrap();
let config_ = config_guard.as_ref().unwrap();
if let Some(minimum_system_version) = &config_.bundle.macos.minimum_system_version {
std::env::set_var("MACOSX_DEPLOYMENT_TARGET", minimum_system_version);
}
let app_settings = interface.app_settings();
let interface_options = options.clone().into();
@@ -193,6 +206,7 @@ pub fn bundle<A: AppSettings>(
let mut settings = app_settings
.get_bundler_settings(options.clone().into(), config, out_dir, package_types)
.with_context(|| "failed to build bundler settings")?;
settings.set_no_sign(options.no_sign);
settings.set_log_level(match verbosity {
0 => log::Level::Error,

View File

@@ -175,6 +175,22 @@ fn is_xcode_command_line_tools_installed() -> bool {
.map(|o| o.status.success())
.unwrap_or(false)
}
#[cfg(target_os = "macos")]
pub fn xcode_version() -> Option<String> {
Command::new("xcodebuild")
.arg("-version")
.output()
.ok()
.map(|o| String::from_utf8_lossy(&o.stdout).into_owned())
.and_then(|s| {
s.split('\n')
.filter_map(|line| line.strip_prefix("Xcode "))
.next()
.map(ToString::to_string)
})
}
fn de_and_session() -> String {
#[cfg(any(
target_os = "linux",
@@ -319,5 +335,11 @@ pub fn items() -> Vec<SectionItem> {
}.into()
},
),
#[cfg(target_os = "macos")]
SectionItem::new().action(|| {
xcode_version().map(|v| (format!("Xcode: {v}"), Status::Success)).unwrap_or_else(|| {
(format!("Xcode: {}", "not installed!".red()), Status::Error)
}).into()
}),
]
}

View File

@@ -15,7 +15,7 @@ use std::fmt::{self, Display, Formatter};
mod app;
mod env_nodejs;
mod env_rust;
mod env_system;
pub mod env_system;
#[cfg(target_os = "macos")]
mod ios;
mod packages_nodejs;

View File

@@ -157,8 +157,6 @@ impl Interface for Rust {
"IPHONEOS_DEPLOYMENT_TARGET",
&config.bundle.ios.minimum_system_version,
);
} else if let Some(minimum_system_version) = &config.bundle.macos.minimum_system_version {
std::env::set_var("MACOSX_DEPLOYMENT_TARGET", minimum_system_version);
}
let app_settings = RustAppSettings::new(config, manifest, target)?;

View File

@@ -132,11 +132,21 @@ pub fn command(options: Options) -> Result<()> {
let mut validated_lib = false;
let installed_targets =
crate::interface::rust::installation::installed_targets().unwrap_or_default();
call_for_targets_with_fallback(
options.targets.unwrap_or_default().iter(),
&detect_target_ok,
&env,
|target: &Target| {
if !installed_targets.contains(&target.triple().into()) {
log::info!("Installing target {}", target.triple());
target
.install()
.context("failed to install target with rustup")?;
}
target.build(
&config,
&metadata,

View File

@@ -99,6 +99,7 @@ impl From<Options> for BuildOptions {
ci: options.ci,
skip_stapling: false,
ignore_version_mismatches: options.ignore_version_mismatches,
no_sign: false,
}
}
}
@@ -167,6 +168,15 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
crate::build::setup(&interface, &mut build_options, tauri_config.clone(), true)?;
let installed_targets =
crate::interface::rust::installation::installed_targets().unwrap_or_default();
if !installed_targets.contains(&first_target.triple().into()) {
log::info!("Installing target {}", first_target.triple());
first_target
.install()
.context("failed to install target with rustup")?;
}
// run an initial build to initialize plugins
first_target.build(&config, &metadata, &env, noise_level, true, profile)?;

View File

@@ -252,12 +252,22 @@ fn run_dev(
configure_cargo(&mut env, config)?;
let installed_targets =
crate::interface::rust::installation::installed_targets().unwrap_or_default();
// run an initial build to initialize plugins
let target_triple = dev_options.target.as_ref().unwrap();
let target = Target::all()
.values()
.find(|t| t.triple == target_triple)
.unwrap_or_else(|| Target::all().values().next().unwrap());
if !installed_targets.contains(&target.triple().into()) {
log::info!("Installing target {}", target.triple());
target
.install()
.context("failed to install target with rustup")?;
}
target.build(
config,
metadata,

View File

@@ -45,8 +45,9 @@ pub fn gen(
.collect::<Vec<&Target>>();
if !missing_targets.is_empty() {
println!("Installing Android Rust toolchains...");
log::info!("Installing Android Rust targets...");
for target in missing_targets {
log::info!("Installing target {}", target.triple());
target
.install()
.context("failed to install target with rustup")?;

View File

@@ -15,7 +15,7 @@ use crate::{
flock,
},
interface::{AppInterface, Interface, Options as InterfaceOptions},
mobile::{write_options, CliOptions},
mobile::{ios::ensure_ios_runtime_installed, write_options, CliOptions},
ConfigValue, Result,
};
use clap::{ArgAction, Parser, ValueEnum};
@@ -102,6 +102,17 @@ pub enum ExportMethod {
Debugging,
}
impl ExportMethod {
/// Xcode 15.4 deprecated these names (in this case we should use the Display impl).
pub fn pre_xcode_15_4_name(&self) -> String {
match self {
Self::AppStoreConnect => "app-store".to_string(),
Self::ReleaseTesting => "ad-hoc".to_string(),
Self::Debugging => "development".to_string(),
}
}
}
impl std::fmt::Display for ExportMethod {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
@@ -139,6 +150,7 @@ impl From<Options> for BuildOptions {
ci: options.ci,
skip_stapling: false,
ignore_version_mismatches: options.ignore_version_mismatches,
no_sign: false,
}
}
}
@@ -213,9 +225,33 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
let mut env = env()?;
if !options.open {
ensure_ios_runtime_installed()?;
}
let mut export_options_plist = plist::Dictionary::new();
if let Some(method) = options.export_method {
export_options_plist.insert("method".to_string(), method.to_string().into());
let xcode_version =
crate::info::env_system::xcode_version().context("failed to determine Xcode version")?;
let mut iter = xcode_version.split('.');
let major = iter.next().context(format!(
"failed to parse Xcode version `{xcode_version}` as semver"
))?;
let minor = iter.next().context(format!(
"failed to parse Xcode version `{xcode_version}` as semver"
))?;
let major = major.parse::<u64>().context(format!(
"failed to parse Xcode version `{xcode_version}` as semver: major is not a number"
))?;
let minor = minor.parse::<u64>().context(format!(
"failed to parse Xcode version `{xcode_version}` as semver: minor is not a number"
))?;
if major < 15 || (major == 15 && minor < 4) {
export_options_plist.insert("method".to_string(), method.pre_xcode_15_4_name().into());
} else {
export_options_plist.insert("method".to_string(), method.to_string().into());
}
}
let (keychain, provisioning_profile) = super::signing_from_env()?;

View File

@@ -15,7 +15,8 @@ use crate::{
},
interface::{AppInterface, Interface, MobileOptions, Options as InterfaceOptions},
mobile::{
use_network_address_for_dev_url, write_options, CliOptions, DevChild, DevHost, DevProcess,
ios::ensure_ios_runtime_installed, use_network_address_for_dev_url, write_options, CliOptions,
DevChild, DevHost, DevProcess,
},
ConfigValue, Result,
};
@@ -166,6 +167,10 @@ fn run_command(options: Options, noise_level: NoiseLevel) -> Result<()> {
}
};
if device.is_some() {
ensure_ios_runtime_installed()?;
}
let mut dev_options: DevOptions = options.clone().into();
let target_triple = device
.as_ref()

View File

@@ -19,6 +19,7 @@ use cargo_mobile2::{
util::{prompt, relativize_path},
};
use clap::{Parser, Subcommand};
use serde::Deserialize;
use sublime_fuzzy::best_match;
use tauri_utils::resources::ResourcePaths;
@@ -325,12 +326,23 @@ fn connected_device_prompt<'a>(env: &'_ Env, target: Option<&str>) -> Result<Dev
device,
device.target().triple,
);
Ok(device)
} else {
Err(anyhow::anyhow!("No connected iOS devices detected"))
}
}
#[derive(Default, Deserialize)]
struct InstalledRuntimesList {
runtimes: Vec<InstalledRuntime>,
}
#[derive(Deserialize)]
struct InstalledRuntime {
name: String,
}
fn simulator_prompt(env: &'_ Env, target: Option<&str>) -> Result<device::Simulator> {
let simulator_list = device::list_simulators(env).map_err(|cause| {
anyhow::anyhow!("Failed to detect connected iOS Simulator devices: {cause}")
@@ -367,6 +379,19 @@ fn simulator_prompt(env: &'_ Env, target: Option<&str>) -> Result<device::Simula
};
Ok(device)
} else {
log::warn!("No available iOS Simulator detected");
let install_ios = crate::helpers::prompts::confirm(
"Would you like to install the latest iOS runtime?",
Some(false),
)
.unwrap_or_default();
if install_ios {
duct::cmd("xcodebuild", ["-downloadPlatform", "iOS"])
.stdout_file(os_pipe::dup_stdout().unwrap())
.stderr_file(os_pipe::dup_stderr().unwrap())
.run()?;
return simulator_prompt(env, target);
}
Err(anyhow::anyhow!("No available iOS Simulator detected"))
}
}
@@ -382,6 +407,34 @@ fn device_prompt<'a>(env: &'_ Env, target: Option<&str>) -> Result<Device<'a>> {
}
}
fn ensure_ios_runtime_installed() -> Result<()> {
let installed_platforms_json =
duct::cmd("xcrun", ["simctl", "list", "runtimes", "--json"]).read()?;
let installed_platforms: InstalledRuntimesList =
serde_json::from_str(&installed_platforms_json).unwrap_or_default();
if !installed_platforms
.runtimes
.iter()
.any(|r| r.name.starts_with("iOS"))
{
log::warn!("iOS platform not installed");
let install_ios = crate::helpers::prompts::confirm(
"Would you like to install the latest iOS runtime?",
Some(false),
)
.unwrap_or_default();
if install_ios {
duct::cmd("xcodebuild", ["-downloadPlatform", "iOS"])
.stdout_file(os_pipe::dup_stdout().unwrap())
.stderr_file(os_pipe::dup_stderr().unwrap())
.run()?;
} else {
anyhow::bail!("iOS platform not installed");
}
}
Ok(())
}
fn detect_target_ok<'a>(env: &Env) -> Option<&'a Target<'a>> {
device_prompt(env, None).map(|device| device.target()).ok()
}

View File

@@ -50,8 +50,9 @@ pub fn gen(
.collect::<Vec<&Target>>();
if !missing_targets.is_empty() {
println!("Installing iOS Rust toolchains...");
log::info!("Installing iOS Rust targets...");
for target in missing_targets {
log::info!("Installing target {}", target.triple());
target
.install()
.context("failed to install target with rustup")?;

View File

@@ -11,7 +11,7 @@ use crate::{
};
use anyhow::Context;
use cargo_mobile2::{apple::target::Target, opts::Profile};
use cargo_mobile2::{apple::target::Target, opts::Profile, target::TargetTrait};
use clap::{ArgAction, Parser};
use object::{Object, ObjectSymbol};
@@ -65,12 +65,16 @@ pub fn command(options: Options) -> Result<()> {
}
}
// `xcode-script` is ran from the `gen/apple` folder when not using NPM/yarn/pnpm.
let process_path = std::env::current_exe().unwrap_or_default();
// `xcode-script` is ran from the `gen/apple` folder when not using NPM/yarn/pnpm/deno.
// so we must change working directory to the src-tauri folder to resolve the tauri dir
// additionally, bun@<1.2 does not modify the current working directory, so it is also runs this script from `gen/apple`
// bun@>1.2 now actually moves the CWD to the package root so we shouldn't modify CWD in that case
// see https://bun.sh/blog/bun-v1.2#bun-run-uses-the-correct-directory
if (var_os("npm_lifecycle_event").is_none() && var_os("PNPM_PACKAGE_NAME").is_none())
if (var_os("npm_lifecycle_event").is_none()
&& var_os("PNPM_PACKAGE_NAME").is_none()
&& process_path.file_stem().unwrap_or_default() != "deno")
|| var("npm_config_user_agent")
.is_ok_and(|agent| agent.starts_with("bun/1.0") || agent.starts_with("bun/1.1"))
{
@@ -209,6 +213,10 @@ pub fn command(options: Options) -> Result<()> {
} else {
options.arches
};
let installed_targets =
crate::interface::rust::installation::installed_targets().unwrap_or_default();
for arch in arches {
// Set target-specific flags
let (env_triple, rust_triple) = match arch.as_str() {
@@ -251,6 +259,14 @@ pub fn command(options: Options) -> Result<()> {
)
})?
};
if !installed_targets.contains(&rust_triple.into()) {
log::info!("Installing target {}", target.triple());
target
.install()
.context("failed to install target with rustup")?;
}
target.compile_lib(
&config,
&metadata,

View File

@@ -353,6 +353,7 @@ fn env_vars() -> HashMap<String, OsString> {
&& k != "TAURI_SIGNING_PRIVATE_KEY_PASSWORD")
|| k.starts_with("WRY")
|| k.starts_with("CARGO_")
|| k.starts_with("RUST_")
|| k == "TMPDIR"
|| k == "PATH"
{

View File

@@ -1,5 +1,11 @@
# Changelog
## \[2.8.1]
### Bug Fixes
- [`03e7c1193`](https://www.github.com/tauri-apps/tauri/commit/03e7c1193208716170f120a1d4a39cea0bc21064) ([#14080](https://www.github.com/tauri-apps/tauri/pull/14080) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Ignore initial navigation to `about:blank` so `on_new_window` does not give a warning on first navigation on macOS.
## \[2.8.0]
### New Features

View File

@@ -1,6 +1,6 @@
[package]
name = "tauri-runtime-wry"
version = "2.8.0"
version = "2.8.1"
description = "Wry bindings to the Tauri runtime"
exclude = ["CHANGELOG.md", "/target"]
readme = "README.md"
@@ -17,7 +17,7 @@ rustc-args = ["--cfg", "docsrs"]
rustdoc-args = ["--cfg", "docsrs"]
[dependencies]
wry = { version = "0.53.1", default-features = false, features = [
wry = { version = "0.53.2", default-features = false, features = [
"drag-drop",
"protocol",
"os-webview",

View File

@@ -4654,13 +4654,16 @@ You may have it installed on another user account, but it is not available for t
let mut webview_builder = WebViewBuilder::new_with_web_context(&mut web_context.inner)
.with_id(&label)
.with_focused(webview_attributes.focus)
.with_url(&url)
.with_transparent(webview_attributes.transparent)
.with_accept_first_mouse(webview_attributes.accept_first_mouse)
.with_incognito(webview_attributes.incognito)
.with_clipboard(webview_attributes.clipboard)
.with_hotkeys_zoom(webview_attributes.zoom_hotkeys_enabled);
if url != "about:blank" {
webview_builder = webview_builder.with_url(&url);
}
#[cfg(target_os = "macos")]
if let Some(webview_configuration) = webview_attributes.webview_configuration {
webview_builder = webview_builder.with_webview_configuration(webview_configuration);

View File

@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://schema.tauri.app/config/2.8.2",
"$id": "https://schema.tauri.app/config/2.8.5",
"title": "Config",
"description": "The Tauri configuration object.\n It is read from a file where you can define your frontend assets,\n configure the bundler and define a tray icon.\n\n The configuration file is generated by the\n [`tauri init`](https://v2.tauri.app/reference/cli/#init) command that lives in\n your Tauri application source directory (src-tauri).\n\n Once generated, you may modify it at will to customize your Tauri application.\n\n ## File Formats\n\n By default, the configuration is defined as a JSON file named `tauri.conf.json`.\n\n Tauri also supports JSON5 and TOML files via the `config-json5` and `config-toml` Cargo features, respectively.\n The JSON5 file name must be either `tauri.conf.json` or `tauri.conf.json5`.\n The TOML file name is `Tauri.toml`.\n\n ## Platform-Specific Configuration\n\n In addition to the default configuration file, Tauri can\n read a platform-specific configuration from `tauri.linux.conf.json`,\n `tauri.windows.conf.json`, `tauri.macos.conf.json`, `tauri.android.conf.json` and `tauri.ios.conf.json`\n (or `Tauri.linux.toml`, `Tauri.windows.toml`, `Tauri.macos.toml`, `Tauri.android.toml` and `Tauri.ios.toml` if the `Tauri.toml` format is used),\n which gets merged with the main configuration object.\n\n ## Configuration Structure\n\n The configuration is composed of the following objects:\n\n - [`app`](#appconfig): The Tauri configuration\n - [`build`](#buildconfig): The build configuration\n - [`bundle`](#bundleconfig): The bundle configurations\n - [`plugins`](#pluginconfig): The plugins configuration\n\n Example tauri.config.json file:\n\n ```json\n {\n \"productName\": \"tauri-app\",\n \"version\": \"0.1.0\",\n \"build\": {\n \"beforeBuildCommand\": \"\",\n \"beforeDevCommand\": \"\",\n \"devUrl\": \"http://localhost:3000\",\n \"frontendDist\": \"../dist\"\n },\n \"app\": {\n \"security\": {\n \"csp\": null\n },\n \"windows\": [\n {\n \"fullscreen\": false,\n \"height\": 600,\n \"resizable\": true,\n \"title\": \"Tauri App\",\n \"width\": 800\n }\n ]\n },\n \"bundle\": {},\n \"plugins\": {}\n }\n ```",
"type": "object",
@@ -164,7 +164,7 @@
"type": "object",
"properties": {
"windows": {
"description": "The app windows configuration.",
"description": "The app windows configuration.\n\n ## Example:\n\n To create a window at app startup\n\n ```json\n {\n \"app\": {\n \"windows\": [\n { \"width\": 800, \"height\": 600 }\n ]\n }\n }\n ```\n\n If not specified, the window's label (its identifier) defaults to \"main\",\n you can use this label to get the window through\n `app.get_webview_window` in Rust or `WebviewWindow.getByLabel` in JavaScript\n\n When working with multiple windows, each window will need an unique label\n\n ```json\n {\n \"app\": {\n \"windows\": [\n { \"label\": \"main\", \"width\": 800, \"height\": 600 },\n { \"label\": \"secondary\", \"width\": 800, \"height\": 600 }\n ]\n }\n }\n ```\n\n You can also set `create` to false and use this config through the Rust APIs\n\n ```json\n {\n \"app\": {\n \"windows\": [\n { \"create\": false, \"width\": 800, \"height\": 600 }\n ]\n }\n }\n ```\n\n and use it like this\n\n ```rust\n tauri::Builder::default()\n .setup(|app| {\n tauri::WebviewWindowBuilder::from_config(app.handle(), app.config().app.windows[0])?.build()?;\n Ok(())\n });\n ```",
"default": [],
"type": "array",
"items": {
@@ -230,7 +230,7 @@
"type": "string"
},
"create": {
"description": "Whether Tauri should create this window at app startup or not.\n\n When this is set to `false` you must manually grab the config object via `app.config().app.windows`\n and create it with [`WebviewWindowBuilder::from_config`](https://docs.rs/tauri/2/tauri/webview/struct.WebviewWindowBuilder.html#method.from_config).",
"description": "Whether Tauri should create this window at app startup or not.\n\n When this is set to `false` you must manually grab the config object via `app.config().app.windows`\n and create it with [`WebviewWindowBuilder::from_config`](https://docs.rs/tauri/2/tauri/webview/struct.WebviewWindowBuilder.html#method.from_config).\n\n ## Example:\n\n ```rust\n tauri::Builder::default()\n .setup(|app| {\n tauri::WebviewWindowBuilder::from_config(app.handle(), app.config().app.windows[0])?.build()?;\n Ok(())\n });\n ```",
"default": true,
"type": "boolean"
},
@@ -495,7 +495,7 @@
]
},
"incognito": {
"description": "Whether or not the webview should be launched in incognito mode.\n\n ## Platform-specific:\n\n - **Android**: Unsupported.",
"description": "Whether or not the webview should be launched in incognito mode.\n\n ## Platform-specific:\n\n - **Android**: Unsupported.",
"default": false,
"type": "boolean"
},
@@ -572,6 +572,27 @@
"description": "Allows disabling the input accessory view on iOS.\n\n The accessory view is the view that appears above the keyboard when a text input element is focused.\n It usually displays a view with \"Done\", \"Next\" buttons.",
"default": false,
"type": "boolean"
},
"dataDirectory": {
"description": "Set a custom path for the webview's data directory (localStorage, cache, etc.) **relative to [`appDataDir()`]/${label}**.\n\n To set absolute paths, use [`WebviewWindowBuilder::data_directory`](https://docs.rs/tauri/2/tauri/webview/struct.WebviewWindowBuilder.html#method.data_directory)\n\n #### Platform-specific:\n\n - **Windows**: WebViews with different values for settings like `additionalBrowserArgs`, `browserExtensionsEnabled` or `scrollBarStyle` must have different data directories.\n - **macOS / iOS**: Unsupported, use `dataStoreIdentifier` instead.\n - **Android**: Unsupported.",
"type": [
"string",
"null"
]
},
"dataStoreIdentifier": {
"description": "Initialize the WebView with a custom data store identifier. This can be seen as a replacement for `dataDirectory` which is unavailable in WKWebView.\n See https://developer.apple.com/documentation/webkit/wkwebsitedatastore/init(foridentifier:)?language=objc\n\n The array must contain 16 u8 numbers.\n\n #### Platform-specific:\n\n - **iOS**: Supported since version 17.0+.\n - **macOS**: Supported since version 14.0+.\n - **Windows / Linux / Android**: Unsupported.",
"type": [
"array",
"null"
],
"items": {
"type": "integer",
"format": "uint8",
"minimum": 0.0
},
"maxItems": 16,
"minItems": 16
}
},
"additionalProperties": false
@@ -2078,7 +2099,7 @@
}
},
"resources": {
"description": "App resources to bundle.\n Each resource is a path to a file or directory.\n Glob patterns are supported.",
"description": "App resources to bundle.\n Each resource is a path to a file or directory.\n Glob patterns are supported.\n\n ## Examples\n\n To include a list of files:\n\n ```json\n {\n \"bundle\": {\n \"resources\": [\n \"./path/to/some-file.txt\",\n \"/absolute/path/to/textfile.txt\",\n \"../relative/path/to/jsonfile.json\",\n \"some-folder/\",\n \"resources/**/*.md\"\n ]\n }\n }\n ```\n\n The bundled files will be in `$RESOURCES/` with the original directory structure preserved,\n for example: `./path/to/some-file.txt` -> `$RESOURCE/path/to/some-file.txt`\n\n To fine control where the files will get copied to, use a map instead\n\n ```json\n {\n \"bundle\": {\n \"resources\": {\n \"/absolute/path/to/textfile.txt\": \"resources/textfile.txt\",\n \"relative/path/to/jsonfile.json\": \"resources/jsonfile.json\",\n \"resources/\": \"\",\n \"docs/**/*md\": \"website-docs/\"\n }\n }\n }\n ```\n\n Note that when using glob pattern in this case, the original directory structure is not preserved,\n everything gets copied to the target directory directly\n\n See more: <https://v2.tauri.app/develop/resources/>",
"anyOf": [
{
"$ref": "#/definitions/BundleResources"
@@ -2928,7 +2949,7 @@
]
},
"installerHooks": {
"description": "A path to a `.nsh` file that contains special NSIS macros to be hooked into the\n main installer.nsi script.\n\n Supported hooks are:\n - `NSIS_HOOK_PREINSTALL`: This hook runs before copying files, setting registry key values and creating shortcuts.\n - `NSIS_HOOK_POSTINSTALL`: This hook runs after the installer has finished copying all files, setting the registry keys and created shortcuts.\n - `NSIS_HOOK_PREUNINSTALL`: This hook runs before removing any files, registry keys and shortcuts.\n - `NSIS_HOOK_POSTUNINSTALL`: This hook runs after files, registry keys and shortcuts have been removed.\n\n\n ### Example\n\n ```nsh\n !macro NSIS_HOOK_PREINSTALL\n MessageBox MB_OK \"PreInstall\"\n !macroend\n\n !macro NSIS_HOOK_POSTINSTALL\n MessageBox MB_OK \"PostInstall\"\n !macroend\n\n !macro NSIS_HOOK_PREUNINSTALL\n MessageBox MB_OK \"PreUnInstall\"\n !macroend\n\n !macro NSIS_HOOK_POSTUNINSTALL\n MessageBox MB_OK \"PostUninstall\"\n !macroend\n\n ```",
"description": "A path to a `.nsh` file that contains special NSIS macros to be hooked into the\n main installer.nsi script.\n\n Supported hooks are:\n\n - `NSIS_HOOK_PREINSTALL`: This hook runs before copying files, setting registry key values and creating shortcuts.\n - `NSIS_HOOK_POSTINSTALL`: This hook runs after the installer has finished copying all files, setting the registry keys and created shortcuts.\n - `NSIS_HOOK_PREUNINSTALL`: This hook runs before removing any files, registry keys and shortcuts.\n - `NSIS_HOOK_POSTUNINSTALL`: This hook runs after files, registry keys and shortcuts have been removed.\n\n ### Example\n\n ```nsh\n !macro NSIS_HOOK_PREINSTALL\n MessageBox MB_OK \"PreInstall\"\n !macroend\n\n !macro NSIS_HOOK_POSTINSTALL\n MessageBox MB_OK \"PostInstall\"\n !macroend\n\n !macro NSIS_HOOK_PREUNINSTALL\n MessageBox MB_OK \"PreUnInstall\"\n !macroend\n\n !macro NSIS_HOOK_POSTUNINSTALL\n MessageBox MB_OK \"PostUninstall\"\n !macroend\n ```",
"type": [
"string",
"null"
@@ -3490,7 +3511,7 @@
]
},
"minimumSystemVersion": {
"description": "A version string indicating the minimum macOS X version that the bundled application supports. Defaults to `10.13`.\n\n Setting it to `null` completely removes the `LSMinimumSystemVersion` field on the bundle's `Info.plist`\n and the `MACOSX_DEPLOYMENT_TARGET` environment variable.\n\n An empty string is considered an invalid value so the default value is used.",
"description": "A version string indicating the minimum macOS X version that the bundled application supports. Defaults to `10.13`.\n\n Setting it to `null` completely removes the `LSMinimumSystemVersion` field on the bundle's `Info.plist`\n and the `MACOSX_DEPLOYMENT_TARGET` environment variable.\n\n Ignored in `tauri dev`.\n\n An empty string is considered an invalid value so the default value is used.",
"default": "10.13",
"type": [
"string",

View File

@@ -640,6 +640,8 @@ pub struct MacConfig {
/// Setting it to `null` completely removes the `LSMinimumSystemVersion` field on the bundle's `Info.plist`
/// and the `MACOSX_DEPLOYMENT_TARGET` environment variable.
///
/// Ignored in `tauri dev`.
///
/// An empty string is considered an invalid value so the default value is used.
#[serde(
deserialize_with = "de_macos_minimum_system_version",
@@ -906,12 +908,12 @@ pub struct NsisConfig {
/// main installer.nsi script.
///
/// Supported hooks are:
///
/// - `NSIS_HOOK_PREINSTALL`: This hook runs before copying files, setting registry key values and creating shortcuts.
/// - `NSIS_HOOK_POSTINSTALL`: This hook runs after the installer has finished copying all files, setting the registry keys and created shortcuts.
/// - `NSIS_HOOK_PREUNINSTALL`: This hook runs before removing any files, registry keys and shortcuts.
/// - `NSIS_HOOK_POSTUNINSTALL`: This hook runs after files, registry keys and shortcuts have been removed.
///
///
/// ### Example
///
/// ```nsh
@@ -930,7 +932,6 @@ pub struct NsisConfig {
/// !macro NSIS_HOOK_POSTUNINSTALL
/// MessageBox MB_OK "PostUninstall"
/// !macroend
///
/// ```
#[serde(alias = "installer-hooks")]
pub installer_hooks: Option<PathBuf>,
@@ -1284,6 +1285,47 @@ pub struct BundleConfig {
/// App resources to bundle.
/// Each resource is a path to a file or directory.
/// Glob patterns are supported.
///
/// ## Examples
///
/// To include a list of files:
///
/// ```json
/// {
/// "bundle": {
/// "resources": [
/// "./path/to/some-file.txt",
/// "/absolute/path/to/textfile.txt",
/// "../relative/path/to/jsonfile.json",
/// "some-folder/",
/// "resources/**/*.md"
/// ]
/// }
/// }
/// ```
///
/// The bundled files will be in `$RESOURCES/` with the original directory structure preserved,
/// for example: `./path/to/some-file.txt` -> `$RESOURCE/path/to/some-file.txt`
///
/// To fine control where the files will get copied to, use a map instead
///
/// ```json
/// {
/// "bundle": {
/// "resources": {
/// "/absolute/path/to/textfile.txt": "resources/textfile.txt",
/// "relative/path/to/jsonfile.json": "resources/jsonfile.json",
/// "resources/": "",
/// "docs/**/*md": "website-docs/"
/// }
/// }
/// }
/// ```
///
/// Note that when using glob pattern in this case, the original directory structure is not preserved,
/// everything gets copied to the target directory directly
///
/// See more: <https://v2.tauri.app/develop/resources/>
pub resources: Option<BundleResources>,
/// A copyright string associated with your application.
pub copyright: Option<String>,
@@ -1559,6 +1601,16 @@ pub struct WindowConfig {
///
/// When this is set to `false` you must manually grab the config object via `app.config().app.windows`
/// and create it with [`WebviewWindowBuilder::from_config`](https://docs.rs/tauri/2/tauri/webview/struct.WebviewWindowBuilder.html#method.from_config).
///
/// ## Example:
///
/// ```rust
/// tauri::Builder::default()
/// .setup(|app| {
/// tauri::WebviewWindowBuilder::from_config(app.handle(), app.config().app.windows[0])?.build()?;
/// Ok(())
/// });
/// ```
#[serde(default = "default_true")]
pub create: bool,
/// The window webview URL.
@@ -1731,9 +1783,9 @@ pub struct WindowConfig {
pub window_effects: Option<WindowEffectsConfig>,
/// Whether or not the webview should be launched in incognito mode.
///
/// ## Platform-specific:
/// ## Platform-specific:
///
/// - **Android**: Unsupported.
/// - **Android**: Unsupported.
#[serde(default)]
pub incognito: bool,
/// Sets the window associated with this label to be the parent of the window to be created.
@@ -1842,6 +1894,31 @@ pub struct WindowConfig {
alias = "disable_input_accessory_view"
)]
pub disable_input_accessory_view: bool,
///
/// Set a custom path for the webview's data directory (localStorage, cache, etc.) **relative to [`appDataDir()`]/${label}**.
///
/// To set absolute paths, use [`WebviewWindowBuilder::data_directory`](https://docs.rs/tauri/2/tauri/webview/struct.WebviewWindowBuilder.html#method.data_directory)
///
/// #### Platform-specific:
///
/// - **Windows**: WebViews with different values for settings like `additionalBrowserArgs`, `browserExtensionsEnabled` or `scrollBarStyle` must have different data directories.
/// - **macOS / iOS**: Unsupported, use `dataStoreIdentifier` instead.
/// - **Android**: Unsupported.
#[serde(default, alias = "data-directory")]
pub data_directory: Option<PathBuf>,
///
/// Initialize the WebView with a custom data store identifier. This can be seen as a replacement for `dataDirectory` which is unavailable in WKWebView.
/// See https://developer.apple.com/documentation/webkit/wkwebsitedatastore/init(foridentifier:)?language=objc
///
/// The array must contain 16 u8 numbers.
///
/// #### Platform-specific:
///
/// - **iOS**: Supported since version 17.0+.
/// - **macOS**: Supported since version 14.0+.
/// - **Windows / Linux / Android**: Unsupported.
#[serde(default, alias = "data-store-identifier")]
pub data_store_identifier: Option<[u8; 16]>,
}
impl Default for WindowConfig {
@@ -1901,6 +1978,8 @@ impl Default for WindowConfig {
javascript_disabled: false,
allow_link_preview: true,
disable_input_accessory_view: false,
data_directory: None,
data_store_identifier: None,
}
}
}
@@ -2557,6 +2636,59 @@ impl Default for PatternKind {
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct AppConfig {
/// The app windows configuration.
///
/// ## Example:
///
/// To create a window at app startup
///
/// ```json
/// {
/// "app": {
/// "windows": [
/// { "width": 800, "height": 600 }
/// ]
/// }
/// }
/// ```
///
/// If not specified, the window's label (its identifier) defaults to "main",
/// you can use this label to get the window through
/// `app.get_webview_window` in Rust or `WebviewWindow.getByLabel` in JavaScript
///
/// When working with multiple windows, each window will need an unique label
///
/// ```json
/// {
/// "app": {
/// "windows": [
/// { "label": "main", "width": 800, "height": 600 },
/// { "label": "secondary", "width": 800, "height": 600 }
/// ]
/// }
/// }
/// ```
///
/// You can also set `create` to false and use this config through the Rust APIs
///
/// ```json
/// {
/// "app": {
/// "windows": [
/// { "create": false, "width": 800, "height": 600 }
/// ]
/// }
/// }
/// ```
///
/// and use it like this
///
/// ```rust
/// tauri::Builder::default()
/// .setup(|app| {
/// tauri::WebviewWindowBuilder::from_config(app.handle(), app.config().app.windows[0])?.build()?;
/// Ok(())
/// });
/// ```
#[serde(default)]
pub windows: Vec<WindowConfig>,
/// Security configuration.
@@ -3354,6 +3486,8 @@ mod build {
let javascript_disabled = self.javascript_disabled;
let allow_link_preview = self.allow_link_preview;
let disable_input_accessory_view = self.disable_input_accessory_view;
let data_directory = opt_lit(self.data_directory.as_ref().map(path_buf_lit).as_ref());
let data_store_identifier = opt_vec_lit(self.data_store_identifier, identity);
literal_struct!(
tokens,
@@ -3411,7 +3545,9 @@ mod build {
background_throttling,
javascript_disabled,
allow_link_preview,
disable_input_accessory_view
disable_input_accessory_view,
data_directory,
data_store_identifier
);
}
}

View File

@@ -137,7 +137,11 @@ pub struct ResourcePathsIter<'a> {
allow_walk: bool,
current_path: Option<PathBuf>,
/// The key of map when `pattern_iter` is a [`PatternIter::Map`],
/// used for determining [`Resource::target`]
current_pattern: Option<String>,
/// The value of the map when `pattern_iter` is a [`PatternIter::Map`],
/// used for determining [`Resource::target`]
current_dest: Option<PathBuf>,
walk_iter: Option<walkdir::IntoIter>,
@@ -176,28 +180,27 @@ impl ResourcePathsIter<'_> {
Ok(Resource {
path: path.to_path_buf(),
target: self
.current_dest
.as_ref()
.map(|current_dest| {
// if processing a directory, preserve directory structure under current_dest
if self.walk_iter.is_some() {
let current_pattern = self.current_pattern.as_ref().unwrap();
current_dest.join(path.strip_prefix(current_pattern).unwrap_or(path))
} else if current_dest.components().count() == 0 {
// if current_dest is empty while processing a file pattern or glob
// we preserve the file name as it is
PathBuf::from(path.file_name().unwrap())
} else if self.glob_iter.is_some() {
// if processing a glob and current_dest is not empty
// we put all globbed paths under current_dest
// preserving the file name as it is
current_dest.join(path.file_name().unwrap())
} else {
current_dest.clone()
}
})
.unwrap_or_else(|| resource_relpath(path)),
target: if let Some(current_dest) = &self.current_dest {
// if processing a directory, preserve directory structure under current_dest
if self.walk_iter.is_some() {
let current_pattern = self.current_pattern.as_ref().unwrap();
current_dest.join(path.strip_prefix(current_pattern).unwrap_or(path))
} else if current_dest.components().count() == 0 {
// if current_dest is empty while processing a file pattern or glob
// we preserve the file name as it is
PathBuf::from(path.file_name().unwrap())
} else if self.glob_iter.is_some() {
// if processing a glob and current_dest is not empty
// we put all globbed paths under current_dest
// preserving the file name as it is
current_dest.join(path.file_name().unwrap())
} else {
current_dest.clone()
}
} else {
// If `pattern_iter` is a [`PatternIter::Slice`]
resource_relpath(path)
},
})
}
@@ -240,14 +243,12 @@ impl ResourcePathsIter<'_> {
let pattern = match &mut self.pattern_iter {
PatternIter::Slice(iter) => iter.next()?,
PatternIter::Map(iter) => match iter.next() {
Some((pattern, dest)) => {
self.current_pattern = Some(pattern.clone());
self.current_dest = Some(resource_relpath(Path::new(dest)));
pattern
}
None => return None,
},
PatternIter::Map(iter) => {
let (pattern, dest) = iter.next()?;
self.current_pattern = Some(pattern.clone());
self.current_dest = Some(resource_relpath(Path::new(dest)));
pattern
}
};
if pattern.contains('*') {
@@ -339,39 +340,47 @@ mod tests {
std::env::set_current_dir(&temp).unwrap();
let paths = [
Path::new("src-tauri/tauri.conf.json"),
Path::new("src-tauri/some-other-json.json"),
Path::new("src-tauri/Cargo.toml"),
Path::new("src-tauri/Tauri.toml"),
Path::new("src-tauri/build.rs"),
Path::new("src/assets/javascript.svg"),
Path::new("src/assets/tauri.svg"),
Path::new("src/assets/rust.svg"),
Path::new("src/assets/lang/en.json"),
Path::new("src/assets/lang/ar.json"),
Path::new("src/sounds/lang/es.wav"),
Path::new("src/sounds/lang/fr.wav"),
Path::new("src/textures/ground/earth.tex"),
Path::new("src/textures/ground/sand.tex"),
Path::new("src/textures/water.tex"),
Path::new("src/textures/fire.tex"),
Path::new("src/tiles/sky/grey.tile"),
Path::new("src/tiles/sky/yellow.tile"),
Path::new("src/tiles/grass.tile"),
Path::new("src/tiles/stones.tile"),
Path::new("src/index.html"),
Path::new("src/style.css"),
Path::new("src/script.js"),
Path::new("src/dir/another-dir/file1.txt"),
Path::new("src/dir/another-dir2/file2.txt"),
"src-tauri/tauri.conf.json",
"src-tauri/some-other-json.json",
"src-tauri/Cargo.toml",
"src-tauri/Tauri.toml",
"src-tauri/build.rs",
"src/assets/javascript.svg",
"src/assets/tauri.svg",
"src/assets/rust.svg",
"src/assets/lang/en.json",
"src/assets/lang/ar.json",
"src/sounds/lang/es.wav",
"src/sounds/lang/fr.wav",
"src/textures/ground/earth.tex",
"src/textures/ground/sand.tex",
"src/textures/water.tex",
"src/textures/fire.tex",
"src/tiles/sky/grey.tile",
"src/tiles/sky/yellow.tile",
"src/tiles/grass.tile",
"src/tiles/stones.tile",
"src/index.html",
"src/style.css",
"src/script.js",
"src/dir/another-dir/file1.txt",
"src/dir/another-dir2/file2.txt",
];
for path in paths {
let path = Path::new(path);
fs::create_dir_all(path.parent().unwrap()).unwrap();
fs::write(path, "").unwrap();
}
}
fn resources_map(literal: &[(&str, &str)]) -> HashMap<String, String> {
literal
.iter()
.map(|(from, to)| (from.to_string(), to.to_string()))
.collect()
}
#[test]
#[serial_test::serial(resources)]
fn resource_paths_iter_slice_allow_walk() {
@@ -386,6 +395,8 @@ mod tests {
"../src/assets".into(),
"../src/index.html".into(),
"../src/sounds".into(),
// Should be the same as `../src/textures/` or `../src/textures`
"../src/textures/**/*".into(),
"*.toml".into(),
"*.conf.json".into(),
],
@@ -396,7 +407,9 @@ mod tests {
.collect::<Vec<_>>();
let expected = expected_resources(&[
// From `../src/script.js`
("../src/script.js", "_up_/src/script.js"),
// From `../src/assets`
(
"../src/assets/javascript.svg",
"_up_/src/assets/javascript.svg",
@@ -405,11 +418,26 @@ mod tests {
("../src/assets/rust.svg", "_up_/src/assets/rust.svg"),
("../src/assets/lang/en.json", "_up_/src/assets/lang/en.json"),
("../src/assets/lang/ar.json", "_up_/src/assets/lang/ar.json"),
// From `../src/index.html`
("../src/index.html", "_up_/src/index.html"),
// From `../src/sounds`
("../src/sounds/lang/es.wav", "_up_/src/sounds/lang/es.wav"),
("../src/sounds/lang/fr.wav", "_up_/src/sounds/lang/fr.wav"),
// From `../src/textures/**/*`
(
"../src/textures/ground/earth.tex",
"_up_/src/textures/earth.tex",
),
(
"../src/textures/ground/sand.tex",
"_up_/src/textures/sand.tex",
),
("../src/textures/water.tex", "_up_/src/textures/water.tex"),
("../src/textures/fire.tex", "_up_/src/textures/fire.tex"),
// From `*.toml`
("Cargo.toml", "Cargo.toml"),
("Tauri.toml", "Tauri.toml"),
// From `*.conf.json`
("tauri.conf.json", "tauri.conf.json"),
]);
@@ -469,17 +497,17 @@ mod tests {
let _ = std::env::set_current_dir(dir);
let resources = ResourcePaths::from_map(
&std::collections::HashMap::from_iter([
("../src/script.js".into(), "main.js".into()),
("../src/assets".into(), "".into()),
("../src/index.html".into(), "frontend/index.html".into()),
("../src/sounds".into(), "voices".into()),
("../src/textures/*".into(), "textures".into()),
("../src/tiles/**/*".into(), "tiles".into()),
("*.toml".into(), "".into()),
("*.conf.json".into(), "json".into()),
("../non-existent-file".into(), "asd".into()), // invalid case
("../non/*".into(), "asd".into()), // invalid case
&resources_map(&[
("../src/script.js", "main.js"),
("../src/assets", ""),
("../src/index.html", "frontend/index.html"),
("../src/sounds", "voices"),
("../src/textures/*", "textures"),
("../src/tiles/**/*", "tiles"),
("*.toml", ""),
("*.conf.json", "json"),
("../non-existent-file", "asd"), // invalid case
("../non/*", "asd"), // invalid case
]),
true,
)
@@ -525,13 +553,13 @@ mod tests {
let _ = std::env::set_current_dir(dir);
let resources = ResourcePaths::from_map(
&std::collections::HashMap::from_iter([
("../src/script.js".into(), "main.js".into()),
("../src/assets".into(), "".into()),
("../src/index.html".into(), "frontend/index.html".into()),
("../src/sounds".into(), "voices".into()),
("*.toml".into(), "".into()),
("*.conf.json".into(), "json".into()),
&resources_map(&[
("../src/script.js", "main.js"),
("../src/assets", ""),
("../src/index.html", "frontend/index.html"),
("../src/sounds", "voices"),
("*.toml", ""),
("*.conf.json", "json"),
]),
false,
)
@@ -564,15 +592,15 @@ mod tests {
let _ = std::env::set_current_dir(dir);
let resources = ResourcePaths::from_map(
&std::collections::HashMap::from_iter([
("../non-existent-file".into(), "file".into()),
("../non-existent-dir".into(), "dir".into()),
&resources_map(&[
("../non-existent-file", "file"),
("../non-existent-dir", "dir"),
// exists but not allowed to walk
("../src".into(), "dir2".into()),
("../src", "dir2"),
// doesn't exist but it is a glob and will return an error
("../non-existent-glob-dir/*".into(), "glob".into()),
("../non-existent-glob-dir/*", "glob"),
// exists but only contains directories and will not produce any values
("../src/dir/*".into(), "dir3".into()),
("../src/dir/*", "dir3"),
]),
false,
)

View File

@@ -1,5 +1,31 @@
# Changelog
## \[2.8.5]
### Enhancements
- [`07e134f70`](https://www.github.com/tauri-apps/tauri/commit/07e134f70e3a65424641f1b384a26bf059fd9c56) ([#14107](https://www.github.com/tauri-apps/tauri/pull/14107) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Improve error message for request errors on iOS when local network permission has been denied and the app tries to reach the development server.
### Dependencies
- Upgraded to `tauri-build@2.4.1`
## \[2.8.4]
### Bug Fixes
- [`03e7c1193`](https://www.github.com/tauri-apps/tauri/commit/03e7c1193208716170f120a1d4a39cea0bc21064) ([#14080](https://www.github.com/tauri-apps/tauri/pull/14080) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Ignore initial navigation to `about:blank` so `on_new_window` does not give a warning on first navigation on macOS.
### Dependencies
- Upgraded to `tauri-runtime-wry@2.8.1`
## \[2.8.3]
### Bug Fixes
- [`534998406`](https://www.github.com/tauri-apps/tauri/commit/534998406433a1be52caa9792d120763ab8339ac) ([#14054](https://www.github.com/tauri-apps/tauri/pull/14054) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused the runtime WebView2 detection to fail for FixedRuntime installations.
## \[2.8.2]
### Bug Fixes

View File

@@ -1,6 +1,6 @@
[package]
name = "tauri"
version = "2.8.2"
version = "2.8.5"
description = "Make tiny, secure apps for all desktop platforms with Tauri"
exclude = ["/test", "/.scripts", "CHANGELOG.md", "/target"]
readme = "README.md"
@@ -61,7 +61,7 @@ tauri-macros = { version = "2.4.0", path = "../tauri-macros" }
tauri-utils = { version = "2.7.0", features = [
"resources",
], path = "../tauri-utils" }
tauri-runtime-wry = { version = "2.8.0", path = "../tauri-runtime-wry", default-features = false, optional = true }
tauri-runtime-wry = { version = "2.8.1", path = "../tauri-runtime-wry", default-features = false, optional = true }
getrandom = "0.3"
serde_repr = "0.1"
http = "1"
@@ -168,7 +168,7 @@ objc2-ui-kit = { version = "0.3.0", default-features = false, features = [
[build-dependencies]
glob = "0.3"
heck = "0.5"
tauri-build = { path = "../tauri-build/", default-features = false, version = "2.4.0" }
tauri-build = { path = "../tauri-build/", default-features = false, version = "2.4.1" }
tauri-utils = { path = "../tauri-utils/", version = "2.7.0", features = [
"build",
] }

View File

@@ -1632,7 +1632,7 @@ impl<R: Runtime> Builder<R> {
use tauri::Manager;
tauri::Builder::default()
.setup(|app| {
let main_window = app.get_window("main").unwrap();
let main_window = app.get_webview_window("main").unwrap();
main_window.set_title("Tauri!")?;
Ok(())
});
@@ -2178,6 +2178,26 @@ tauri::Builder::default()
.expect("OpenHarmony app instance not initialized"),
};
// The env var must be set before the Runtime is created so that GetAvailableBrowserVersionString picks it up.
#[cfg(windows)]
{
if let crate::utils::config::WebviewInstallMode::FixedRuntime { path } =
&manager.config.bundle.windows.webview_install_mode
{
if let Some(exe_dir) = crate::utils::platform::current_exe()
.ok()
.and_then(|p| p.parent().map(|p| p.to_path_buf()))
{
std::env::set_var("WEBVIEW2_BROWSER_EXECUTABLE_FOLDER", exe_dir.join(path));
} else {
#[cfg(debug_assertions)]
eprintln!(
"failed to resolve resource directory; fallback to the installed Webview2 runtime."
);
}
}
}
#[cfg(any(windows, target_os = "linux"))]
let mut runtime = if self.runtime_any_thread {
R::new_any_thread(runtime_args)?
@@ -2255,25 +2275,6 @@ tauri::Builder::default()
app.manage(ChannelDataIpcQueue::default());
app.handle.plugin(crate::ipc::channel::plugin())?;
#[cfg(windows)]
{
if let crate::utils::config::WebviewInstallMode::FixedRuntime { path } =
&app.manager.config().bundle.windows.webview_install_mode
{
if let Ok(resource_dir) = app.path().resource_dir() {
std::env::set_var(
"WEBVIEW2_BROWSER_EXECUTABLE_FOLDER",
resource_dir.join(path),
);
} else {
#[cfg(debug_assertions)]
eprintln!(
"failed to resolve resource directory; fallback to the installed Webview2 runtime."
);
}
}
}
let handle = app.handle();
// initialize default tray icon if defined

View File

@@ -174,6 +174,7 @@ impl<R: Runtime> PathResolver<R> {
/// - **Linux:** Resolves to `$HOME`.
/// - **macOS:** Resolves to `$HOME`.
/// - **Windows:** Resolves to `{FOLDERID_Profile}`.
/// - **iOS**: Cannot be written to directly, use one of the app paths instead.
pub fn home_dir(&self) -> Result<PathBuf> {
self.call_resolve("getHomeDir")
}

View File

@@ -149,6 +149,7 @@ impl<R: Runtime> PathResolver<R> {
/// - **Linux:** Resolves to `$HOME`.
/// - **macOS:** Resolves to `$HOME`.
/// - **Windows:** Resolves to `{FOLDERID_Profile}`.
/// - **iOS**: Cannot be written to directly, use one of the app paths instead.
pub fn home_dir(&self) -> Result<PathBuf> {
dirs::home_dir().ok_or(Error::UnknownPath)
}

View File

@@ -124,7 +124,7 @@ pub enum BaseDirectory {
/// Resolves to [`crate::path::PathResolver::video_dir`].
Video = 10,
/// The Resource directory.
/// Resolves to the resource directory of this app.
/// Resolves to [`crate::path::PathResolver::resource_dir`].
Resource = 11,
/// A temporary directory.
/// Resolves to [`std::env::temp_dir`].

View File

@@ -187,8 +187,20 @@ fn get_response<R: Runtime>(
.body(response.body.to_vec().into())?
}
Err(e) => {
log::error!("Failed to request {}: {}", url.as_str(), e);
return Err(Box::new(e));
let error_message = format!(
"Failed to request {}: {}{}",
url.as_str(),
e,
if let Some(s) = e.status() {
format!("status code: {}", s.as_u16())
} else if cfg!(target_os = "ios") {
", did you grant local network permissions? That is required to reach the development server. Please grant the permission via the prompt or in `Settings > Privacy & Security > Local Network` and restart the app. See https://support.apple.com/en-us/102229 for more information.".to_string()
} else {
"".to_string()
}
);
log::error!("{error_message}");
return Err(error_message.into());
}
}
};

View File

@@ -42,6 +42,7 @@ use crate::{
InvokeError, InvokeMessage, InvokeResolver, Origin, OwnedInvokeResponder, ScopeObject,
},
manager::AppManager,
path::SafePathBuf,
sealed::{ManagerBase, RuntimeOrDispatch},
AppHandle, Emitter, Event, EventId, EventLoopMessage, EventName, Listener, Manager,
ResourceTable, Runtime, Window,
@@ -390,9 +391,47 @@ async fn create_window(app: tauri::AppHandle) {
///
/// [the Webview2 issue]: https://github.com/tauri-apps/wry/issues/583
pub fn from_config(config: &WindowConfig) -> Self {
let mut config = config.to_owned();
if let Some(data_directory) = &config.data_directory {
let resolve_data_dir_res = dirs::data_local_dir()
.or({
#[cfg(feature = "tracing")]
tracing::error!("failed to resolve data directory");
None
})
.and_then(|local_dir| {
SafePathBuf::new(data_directory.clone())
.inspect_err(|_err| {
#[cfg(feature = "tracing")]
tracing::error!(
"data_directory `{}` is not a safe path, ignoring config. Validation error was: {_err}",
data_directory.display()
);
})
.map(|p| (local_dir, p))
.ok()
})
.and_then(|(local_dir, data_directory)| {
if data_directory.as_ref().is_relative() {
Some(local_dir.join(&config.label).join(data_directory.as_ref()))
} else {
#[cfg(feature = "tracing")]
tracing::error!(
"data_directory `{}` is not a relative path, ignoring config.",
data_directory.display()
);
None
}
});
if let Some(resolved_data_directory) = resolve_data_dir_res {
config.data_directory = Some(resolved_data_directory);
}
}
Self {
label: config.label.clone(),
webview_attributes: WebviewAttributes::from(config),
webview_attributes: WebviewAttributes::from(&config),
web_resource_request_handler: None,
navigation_handler: None,
new_window_handler: None,

View File

@@ -55,7 +55,7 @@
"eslint-plugin-security": "3.0.1",
"fast-glob": "3.3.3",
"globals": "^16.2.0",
"rollup": "4.46.4",
"rollup": "4.50.0",
"tslib": "^2.8.1",
"typescript": "^5.8.3",
"typescript-eslint": "^8.34.1"

View File

@@ -854,6 +854,33 @@ interface WebviewOptions {
* It usually displays a view with "Done", "Next" buttons.
*/
disableInputAccessoryView?: boolean
/**
* Set a custom path for the webview's data directory (localStorage, cache, etc.) **relative to [`appDataDir()`]/${label}**.
* For security reasons, paths outside of that location can only be configured on the Rust side.
*
* #### Platform-specific:
*
* - **Windows**: WebViews with different values for settings like `additionalBrowserArgs`, `browserExtensionsEnabled` or `scrollBarStyle` must have different data directories.
* - **macOS / iOS**: Unsupported, use `dataStoreIdentifier` instead.
* - **Android**: Unsupported.
*
* @since 2.9.0
*/
dataDirectory?: string
/**
* Initialize the WebView with a custom data store identifier. This can be seen as a replacement for `dataDirectory` which is unavailable in WKWebView.
* See https://developer.apple.com/documentation/webkit/wkwebsitedatastore/init(foridentifier:)?language=objc
*
* The array must contain 16 u8 numbers.
*
* #### Platform-specific:
*
* - **macOS / iOS**: Available on macOS >= 14 and iOS >= 17
* - **Windows / Linux / Android**: Unsupported.
*
* @since 2.9.0
*/
dataStoreIdentifier?: number[]
}
export { Webview, getCurrentWebview, getAllWebviews }

View File

@@ -1,5 +1,41 @@
# Changelog
## \[2.8.4]
### Enhancements
- [`f70b28529`](https://www.github.com/tauri-apps/tauri/commit/f70b28529d226a2dec2f41709d8934f8f5adab25) ([#14093](https://www.github.com/tauri-apps/tauri/pull/14093) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Ensure Rust targets for mobile are installed when running the dev and build commands (previously only checked on init).
- [`a9b342125`](https://www.github.com/tauri-apps/tauri/commit/a9b342125d5ac1bc9a4b2e8b5f73e8ca3cbcb8b2) ([#14114](https://www.github.com/tauri-apps/tauri/pull/14114) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix iOS dev and build targeting the simulator on Intel machines.
- [`61b9b681e`](https://www.github.com/tauri-apps/tauri/commit/61b9b681e88067a53b79d2318ae005dc25addcd6) ([#14111](https://www.github.com/tauri-apps/tauri/pull/14111) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Retain `RUST_*` environment variables when running the mobile commands.
- [`c23bec62d`](https://www.github.com/tauri-apps/tauri/commit/c23bec62d6d5724798869681aa1534423aae28e2) ([#14083](https://www.github.com/tauri-apps/tauri/pull/14083) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Tauri now ignores `macOS.minimumSystemVersion` in `tauri dev` to prevent forced rebuilds of macOS specific dependencies when using something like `rust-analyzer` at the same time as `tauri dev`.
### Bug Fixes
- [`c37a29833`](https://www.github.com/tauri-apps/tauri/commit/c37a298331d6d744b15d32d55a2db83c884a3d6a) ([#14112](https://www.github.com/tauri-apps/tauri/pull/14112) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix usage with Deno failing with `ReferenceError: require is not defined`.
- [`bcf000c0a`](https://www.github.com/tauri-apps/tauri/commit/bcf000c0a8607eedf488fb949b982f519abda43d) ([#14110](https://www.github.com/tauri-apps/tauri/pull/14110) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes running `ios` commands with `deno` crashing due to incorrect current working directory resolution.
- [`7db7142f9`](https://www.github.com/tauri-apps/tauri/commit/7db7142f9ff7dc2f5719602e199b77129ceb19d3) ([#14119](https://www.github.com/tauri-apps/tauri/pull/14119) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes empty device name when using an Android emulator causing the emulator to never be detected as running.
- [`956b4fd6f`](https://www.github.com/tauri-apps/tauri/commit/956b4fd6ffbb4312123b107ca96c87a001359b9d) ([#14106](https://www.github.com/tauri-apps/tauri/pull/14106) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Use the correct export method on Xcode < 15.4.
### Dependencies
- Upgraded to `tauri-cli@2.8.4`
## \[2.8.3]
### Bug Fixes
- [`0ac89d3b6`](https://www.github.com/tauri-apps/tauri/commit/0ac89d3b6c8c4a4826a4c42726e4f4a8941b3fde) ([#14078](https://www.github.com/tauri-apps/tauri/pull/14078) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Updated `cargo-mobile2` to allow running on iOS simulators that have a higher version than the XCode SDK. This fixes compatiblity issues with Apple's recent "iOS 18.5 + iOS 18.6 Simulator" platform support component.
### Dependencies
- Upgraded to `tauri-cli@2.8.3`
## \[2.8.2]
### Dependencies
- Upgraded to `tauri-cli@2.8.1`
## \[2.8.1]
### Bug Fixes

View File

@@ -1,7 +1,8 @@
{
"name": "@tauri-apps/cli",
"version": "2.8.1",
"version": "2.8.4",
"description": "Command line interface for building Tauri apps",
"type": "commonjs",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/tauri"

199
pnpm-lock.yaml generated
View File

@@ -63,10 +63,10 @@ importers:
version: 9.29.0
'@rollup/plugin-terser':
specifier: 0.4.4
version: 0.4.4(rollup@4.46.4)
version: 0.4.4(rollup@4.50.0)
'@rollup/plugin-typescript':
specifier: 12.1.4
version: 12.1.4(rollup@4.46.4)(tslib@2.8.1)(typescript@5.8.3)
version: 12.1.4(rollup@4.50.0)(tslib@2.8.1)(typescript@5.8.3)
'@types/eslint':
specifier: ^9.6.1
version: 9.6.1
@@ -89,8 +89,8 @@ importers:
specifier: ^16.2.0
version: 16.2.0
rollup:
specifier: 4.46.4
version: 4.46.4
specifier: 4.50.0
version: 4.50.0
tslib:
specifier: ^2.8.1
version: 2.8.1
@@ -1103,103 +1103,108 @@ packages:
rollup:
optional: true
'@rollup/rollup-android-arm-eabi@4.46.4':
resolution: {integrity: sha512-B2wfzCJ+ps/OBzRjeds7DlJumCU3rXMxJJS1vzURyj7+KBHGONm7c9q1TfdBl4vCuNMkDvARn3PBl2wZzuR5mw==}
'@rollup/rollup-android-arm-eabi@4.50.0':
resolution: {integrity: sha512-lVgpeQyy4fWN5QYebtW4buT/4kn4p4IJ+kDNB4uYNT5b8c8DLJDg6titg20NIg7E8RWwdWZORW6vUFfrLyG3KQ==}
cpu: [arm]
os: [android]
'@rollup/rollup-android-arm64@4.46.4':
resolution: {integrity: sha512-FGJYXvYdn8Bs6lAlBZYT5n+4x0ciEp4cmttsvKAZc/c8/JiPaQK8u0c/86vKX8lA7OY/+37lIQSe0YoAImvBAA==}
'@rollup/rollup-android-arm64@4.50.0':
resolution: {integrity: sha512-2O73dR4Dc9bp+wSYhviP6sDziurB5/HCym7xILKifWdE9UsOe2FtNcM+I4xZjKrfLJnq5UR8k9riB87gauiQtw==}
cpu: [arm64]
os: [android]
'@rollup/rollup-darwin-arm64@4.46.4':
resolution: {integrity: sha512-/9qwE/BM7ATw/W/OFEMTm3dmywbJyLQb4f4v5nmOjgYxPIGpw7HaxRi6LnD4Pjn/q7k55FGeHe1/OD02w63apA==}
'@rollup/rollup-darwin-arm64@4.50.0':
resolution: {integrity: sha512-vwSXQN8T4sKf1RHr1F0s98Pf8UPz7pS6P3LG9NSmuw0TVh7EmaE+5Ny7hJOZ0M2yuTctEsHHRTMi2wuHkdS6Hg==}
cpu: [arm64]
os: [darwin]
'@rollup/rollup-darwin-x64@4.46.4':
resolution: {integrity: sha512-QkWfNbeRuzFnv2d0aPlrzcA3Ebq2mE8kX/5Pl7VdRShbPBjSnom7dbT8E3Jmhxo2RL784hyqGvR5KHavCJQciw==}
'@rollup/rollup-darwin-x64@4.50.0':
resolution: {integrity: sha512-cQp/WG8HE7BCGyFVuzUg0FNmupxC+EPZEwWu2FCGGw5WDT1o2/YlENbm5e9SMvfDFR6FRhVCBePLqj0o8MN7Vw==}
cpu: [x64]
os: [darwin]
'@rollup/rollup-freebsd-arm64@4.46.4':
resolution: {integrity: sha512-+ToyOMYnSfV8D+ckxO6NthPln/PDNp1P6INcNypfZ7muLmEvPKXqduUiD8DlJpMMT8LxHcE5W0dK9kXfJke9Zw==}
'@rollup/rollup-freebsd-arm64@4.50.0':
resolution: {integrity: sha512-UR1uTJFU/p801DvvBbtDD7z9mQL8J80xB0bR7DqW7UGQHRm/OaKzp4is7sQSdbt2pjjSS72eAtRh43hNduTnnQ==}
cpu: [arm64]
os: [freebsd]
'@rollup/rollup-freebsd-x64@4.46.4':
resolution: {integrity: sha512-cGT6ey/W+sje6zywbLiqmkfkO210FgRz7tepWAzzEVgQU8Hn91JJmQWNqs55IuglG8sJdzk7XfNgmGRtcYlo1w==}
'@rollup/rollup-freebsd-x64@4.50.0':
resolution: {integrity: sha512-G/DKyS6PK0dD0+VEzH/6n/hWDNPDZSMBmqsElWnCRGrYOb2jC0VSupp7UAHHQ4+QILwkxSMaYIbQ72dktp8pKA==}
cpu: [x64]
os: [freebsd]
'@rollup/rollup-linux-arm-gnueabihf@4.46.4':
resolution: {integrity: sha512-9fhTJyOb275w5RofPSl8lpr4jFowd+H4oQKJ9XTYzD1JWgxdZKE8bA6d4npuiMemkecQOcigX01FNZNCYnQBdA==}
'@rollup/rollup-linux-arm-gnueabihf@4.50.0':
resolution: {integrity: sha512-u72Mzc6jyJwKjJbZZcIYmd9bumJu7KNmHYdue43vT1rXPm2rITwmPWF0mmPzLm9/vJWxIRbao/jrQmxTO0Sm9w==}
cpu: [arm]
os: [linux]
'@rollup/rollup-linux-arm-musleabihf@4.46.4':
resolution: {integrity: sha512-+6kCIM5Zjvz2HwPl/udgVs07tPMIp1VU2Y0c72ezjOvSvEfAIWsUgpcSDvnC7g9NrjYR6X9bZT92mZZ90TfvXw==}
'@rollup/rollup-linux-arm-musleabihf@4.50.0':
resolution: {integrity: sha512-S4UefYdV0tnynDJV1mdkNawp0E5Qm2MtSs330IyHgaccOFrwqsvgigUD29uT+B/70PDY1eQ3t40+xf6wIvXJyg==}
cpu: [arm]
os: [linux]
'@rollup/rollup-linux-arm64-gnu@4.46.4':
resolution: {integrity: sha512-SWuXdnsayCZL4lXoo6jn0yyAj7TTjWE4NwDVt9s7cmu6poMhtiras5c8h6Ih6Y0Zk6Z+8t/mLumvpdSPTWub2Q==}
'@rollup/rollup-linux-arm64-gnu@4.50.0':
resolution: {integrity: sha512-1EhkSvUQXJsIhk4msxP5nNAUWoB4MFDHhtc4gAYvnqoHlaL9V3F37pNHabndawsfy/Tp7BPiy/aSa6XBYbaD1g==}
cpu: [arm64]
os: [linux]
'@rollup/rollup-linux-arm64-musl@4.46.4':
resolution: {integrity: sha512-vDknMDqtMhrrroa5kyX6tuC0aRZZlQ+ipDfbXd2YGz5HeV2t8HOl/FDAd2ynhs7Ki5VooWiiZcCtxiZ4IjqZwQ==}
'@rollup/rollup-linux-arm64-musl@4.50.0':
resolution: {integrity: sha512-EtBDIZuDtVg75xIPIK1l5vCXNNCIRM0OBPUG+tbApDuJAy9mKago6QxX+tfMzbCI6tXEhMuZuN1+CU8iDW+0UQ==}
cpu: [arm64]
os: [linux]
'@rollup/rollup-linux-loongarch64-gnu@4.46.4':
resolution: {integrity: sha512-mCBkjRZWhvjtl/x+Bd4fQkWZT8canStKDxGrHlBiTnZmJnWygGcvBylzLVCZXka4dco5ymkWhZlLwKCGFF4ivw==}
'@rollup/rollup-linux-loongarch64-gnu@4.50.0':
resolution: {integrity: sha512-BGYSwJdMP0hT5CCmljuSNx7+k+0upweM2M4YGfFBjnFSZMHOLYR0gEEj/dxyYJ6Zc6AiSeaBY8dWOa11GF/ppQ==}
cpu: [loong64]
os: [linux]
'@rollup/rollup-linux-ppc64-gnu@4.46.4':
resolution: {integrity: sha512-YMdz2phOTFF+Z66dQfGf0gmeDSi5DJzY5bpZyeg9CPBkV9QDzJ1yFRlmi/j7WWRf3hYIWrOaJj5jsfwgc8GTHQ==}
'@rollup/rollup-linux-ppc64-gnu@4.50.0':
resolution: {integrity: sha512-I1gSMzkVe1KzAxKAroCJL30hA4DqSi+wGc5gviD0y3IL/VkvcnAqwBf4RHXHyvH66YVHxpKO8ojrgc4SrWAnLg==}
cpu: [ppc64]
os: [linux]
'@rollup/rollup-linux-riscv64-gnu@4.46.4':
resolution: {integrity: sha512-r0WKLSfFAK8ucG024v2yiLSJMedoWvk8yWqfNICX28NHDGeu3F/wBf8KG6mclghx4FsLePxJr/9N8rIj1PtCnw==}
'@rollup/rollup-linux-riscv64-gnu@4.50.0':
resolution: {integrity: sha512-bSbWlY3jZo7molh4tc5dKfeSxkqnf48UsLqYbUhnkdnfgZjgufLS/NTA8PcP/dnvct5CCdNkABJ56CbclMRYCA==}
cpu: [riscv64]
os: [linux]
'@rollup/rollup-linux-riscv64-musl@4.46.4':
resolution: {integrity: sha512-IaizpPP2UQU3MNyPH1u0Xxbm73D+4OupL0bjo4Hm0496e2wg3zuvoAIhubkD1NGy9fXILEExPQy87mweujEatA==}
'@rollup/rollup-linux-riscv64-musl@4.50.0':
resolution: {integrity: sha512-LSXSGumSURzEQLT2e4sFqFOv3LWZsEF8FK7AAv9zHZNDdMnUPYH3t8ZlaeYYZyTXnsob3htwTKeWtBIkPV27iQ==}
cpu: [riscv64]
os: [linux]
'@rollup/rollup-linux-s390x-gnu@4.46.4':
resolution: {integrity: sha512-aCM29orANR0a8wk896p6UEgIfupReupnmISz6SUwMIwTGaTI8MuKdE0OD2LvEg8ondDyZdMvnaN3bW4nFbATPA==}
'@rollup/rollup-linux-s390x-gnu@4.50.0':
resolution: {integrity: sha512-CxRKyakfDrsLXiCyucVfVWVoaPA4oFSpPpDwlMcDFQvrv3XY6KEzMtMZrA+e/goC8xxp2WSOxHQubP8fPmmjOQ==}
cpu: [s390x]
os: [linux]
'@rollup/rollup-linux-x64-gnu@4.46.4':
resolution: {integrity: sha512-0Xj1vZE3cbr/wda8d/m+UeuSL+TDpuozzdD4QaSzu/xSOMK0Su5RhIkF7KVHFQsobemUNHPLEcYllL7ZTCP/Cg==}
'@rollup/rollup-linux-x64-gnu@4.50.0':
resolution: {integrity: sha512-8PrJJA7/VU8ToHVEPu14FzuSAqVKyo5gg/J8xUerMbyNkWkO9j2ExBho/68RnJsMGNJq4zH114iAttgm7BZVkA==}
cpu: [x64]
os: [linux]
'@rollup/rollup-linux-x64-musl@4.46.4':
resolution: {integrity: sha512-kM/orjpolfA5yxsx84kI6bnK47AAZuWxglGKcNmokw2yy9i5eHY5UAjcX45jemTJnfHAWo3/hOoRqEeeTdL5hw==}
'@rollup/rollup-linux-x64-musl@4.50.0':
resolution: {integrity: sha512-SkE6YQp+CzpyOrbw7Oc4MgXFvTw2UIBElvAvLCo230pyxOLmYwRPwZ/L5lBe/VW/qT1ZgND9wJfOsdy0XptRvw==}
cpu: [x64]
os: [linux]
'@rollup/rollup-win32-arm64-msvc@4.46.4':
resolution: {integrity: sha512-cNLH4psMEsWKILW0isbpQA2OvjXLbKvnkcJFmqAptPQbtLrobiapBJVj6RoIvg6UXVp5w0wnIfd/Q56cNpF+Ew==}
'@rollup/rollup-openharmony-arm64@4.50.0':
resolution: {integrity: sha512-PZkNLPfvXeIOgJWA804zjSFH7fARBBCpCXxgkGDRjjAhRLOR8o0IGS01ykh5GYfod4c2yiiREuDM8iZ+pVsT+Q==}
cpu: [arm64]
os: [openharmony]
'@rollup/rollup-win32-arm64-msvc@4.50.0':
resolution: {integrity: sha512-q7cIIdFvWQoaCbLDUyUc8YfR3Jh2xx3unO8Dn6/TTogKjfwrax9SyfmGGK6cQhKtjePI7jRfd7iRYcxYs93esg==}
cpu: [arm64]
os: [win32]
'@rollup/rollup-win32-ia32-msvc@4.46.4':
resolution: {integrity: sha512-OiEa5lRhiANpv4SfwYVgQ3opYWi/QmPDC5ve21m8G9pf6ZO+aX1g2EEF1/IFaM1xPSP7mK0msTRXlPs6mIagkg==}
'@rollup/rollup-win32-ia32-msvc@4.50.0':
resolution: {integrity: sha512-XzNOVg/YnDOmFdDKcxxK410PrcbcqZkBmz+0FicpW5jtjKQxcW1BZJEQOF0NJa6JO7CZhett8GEtRN/wYLYJuw==}
cpu: [ia32]
os: [win32]
'@rollup/rollup-win32-x64-msvc@4.46.4':
resolution: {integrity: sha512-IKL9mewGZ5UuuX4NQlwOmxPyqielvkAPUS2s1cl6yWjjQvyN3h5JTdVFGD5Jr5xMjRC8setOfGQDVgX8V+dkjg==}
'@rollup/rollup-win32-x64-msvc@4.50.0':
resolution: {integrity: sha512-xMmiWRR8sp72Zqwjgtf3QbZfF1wdh8X2ABu3EaozvZcyHJeU0r+XAnXdKgs4cCAp6ORoYoCygipYP1mjmbjrsg==}
cpu: [x64]
os: [win32]
@@ -2157,8 +2162,8 @@ packages:
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
rollup@4.46.4:
resolution: {integrity: sha512-YbxoxvoqNg9zAmw4+vzh1FkGAiZRK+LhnSrbSrSXMdZYsRPDWoshcSd/pldKRO6lWzv/e9TiJAVQyirYIeSIPQ==}
rollup@4.50.0:
resolution: {integrity: sha512-/Zl4D8zPifNmyGzJS+3kVoyXeDeT/GrsJM94sACNg9RtUE0hrHa1bNPtRSrfHTMH5HjRzce6K7rlTh3Khiw+pw==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
@@ -3312,89 +3317,92 @@ snapshots:
dependencies:
quansync: 0.2.10
'@rollup/plugin-terser@0.4.4(rollup@4.46.4)':
'@rollup/plugin-terser@0.4.4(rollup@4.50.0)':
dependencies:
serialize-javascript: 6.0.2
smob: 1.5.0
terser: 5.43.1
optionalDependencies:
rollup: 4.46.4
rollup: 4.50.0
'@rollup/plugin-typescript@12.1.4(rollup@4.46.4)(tslib@2.8.1)(typescript@5.8.3)':
'@rollup/plugin-typescript@12.1.4(rollup@4.50.0)(tslib@2.8.1)(typescript@5.8.3)':
dependencies:
'@rollup/pluginutils': 5.2.0(rollup@4.46.4)
'@rollup/pluginutils': 5.2.0(rollup@4.50.0)
resolve: 1.22.10
typescript: 5.8.3
optionalDependencies:
rollup: 4.46.4
rollup: 4.50.0
tslib: 2.8.1
'@rollup/pluginutils@5.2.0(rollup@4.46.4)':
'@rollup/pluginutils@5.2.0(rollup@4.50.0)':
dependencies:
'@types/estree': 1.0.8
estree-walker: 2.0.2
picomatch: 4.0.2
optionalDependencies:
rollup: 4.46.4
rollup: 4.50.0
'@rollup/rollup-android-arm-eabi@4.46.4':
'@rollup/rollup-android-arm-eabi@4.50.0':
optional: true
'@rollup/rollup-android-arm64@4.46.4':
'@rollup/rollup-android-arm64@4.50.0':
optional: true
'@rollup/rollup-darwin-arm64@4.46.4':
'@rollup/rollup-darwin-arm64@4.50.0':
optional: true
'@rollup/rollup-darwin-x64@4.46.4':
'@rollup/rollup-darwin-x64@4.50.0':
optional: true
'@rollup/rollup-freebsd-arm64@4.46.4':
'@rollup/rollup-freebsd-arm64@4.50.0':
optional: true
'@rollup/rollup-freebsd-x64@4.46.4':
'@rollup/rollup-freebsd-x64@4.50.0':
optional: true
'@rollup/rollup-linux-arm-gnueabihf@4.46.4':
'@rollup/rollup-linux-arm-gnueabihf@4.50.0':
optional: true
'@rollup/rollup-linux-arm-musleabihf@4.46.4':
'@rollup/rollup-linux-arm-musleabihf@4.50.0':
optional: true
'@rollup/rollup-linux-arm64-gnu@4.46.4':
'@rollup/rollup-linux-arm64-gnu@4.50.0':
optional: true
'@rollup/rollup-linux-arm64-musl@4.46.4':
'@rollup/rollup-linux-arm64-musl@4.50.0':
optional: true
'@rollup/rollup-linux-loongarch64-gnu@4.46.4':
'@rollup/rollup-linux-loongarch64-gnu@4.50.0':
optional: true
'@rollup/rollup-linux-ppc64-gnu@4.46.4':
'@rollup/rollup-linux-ppc64-gnu@4.50.0':
optional: true
'@rollup/rollup-linux-riscv64-gnu@4.46.4':
'@rollup/rollup-linux-riscv64-gnu@4.50.0':
optional: true
'@rollup/rollup-linux-riscv64-musl@4.46.4':
'@rollup/rollup-linux-riscv64-musl@4.50.0':
optional: true
'@rollup/rollup-linux-s390x-gnu@4.46.4':
'@rollup/rollup-linux-s390x-gnu@4.50.0':
optional: true
'@rollup/rollup-linux-x64-gnu@4.46.4':
'@rollup/rollup-linux-x64-gnu@4.50.0':
optional: true
'@rollup/rollup-linux-x64-musl@4.46.4':
'@rollup/rollup-linux-x64-musl@4.50.0':
optional: true
'@rollup/rollup-win32-arm64-msvc@4.46.4':
'@rollup/rollup-openharmony-arm64@4.50.0':
optional: true
'@rollup/rollup-win32-ia32-msvc@4.46.4':
'@rollup/rollup-win32-arm64-msvc@4.50.0':
optional: true
'@rollup/rollup-win32-x64-msvc@4.46.4':
'@rollup/rollup-win32-ia32-msvc@4.50.0':
optional: true
'@rollup/rollup-win32-x64-msvc@4.50.0':
optional: true
'@sveltejs/acorn-typescript@1.0.5(acorn@8.15.0)':
@@ -4448,30 +4456,31 @@ snapshots:
reusify@1.1.0: {}
rollup@4.46.4:
rollup@4.50.0:
dependencies:
'@types/estree': 1.0.8
optionalDependencies:
'@rollup/rollup-android-arm-eabi': 4.46.4
'@rollup/rollup-android-arm64': 4.46.4
'@rollup/rollup-darwin-arm64': 4.46.4
'@rollup/rollup-darwin-x64': 4.46.4
'@rollup/rollup-freebsd-arm64': 4.46.4
'@rollup/rollup-freebsd-x64': 4.46.4
'@rollup/rollup-linux-arm-gnueabihf': 4.46.4
'@rollup/rollup-linux-arm-musleabihf': 4.46.4
'@rollup/rollup-linux-arm64-gnu': 4.46.4
'@rollup/rollup-linux-arm64-musl': 4.46.4
'@rollup/rollup-linux-loongarch64-gnu': 4.46.4
'@rollup/rollup-linux-ppc64-gnu': 4.46.4
'@rollup/rollup-linux-riscv64-gnu': 4.46.4
'@rollup/rollup-linux-riscv64-musl': 4.46.4
'@rollup/rollup-linux-s390x-gnu': 4.46.4
'@rollup/rollup-linux-x64-gnu': 4.46.4
'@rollup/rollup-linux-x64-musl': 4.46.4
'@rollup/rollup-win32-arm64-msvc': 4.46.4
'@rollup/rollup-win32-ia32-msvc': 4.46.4
'@rollup/rollup-win32-x64-msvc': 4.46.4
'@rollup/rollup-android-arm-eabi': 4.50.0
'@rollup/rollup-android-arm64': 4.50.0
'@rollup/rollup-darwin-arm64': 4.50.0
'@rollup/rollup-darwin-x64': 4.50.0
'@rollup/rollup-freebsd-arm64': 4.50.0
'@rollup/rollup-freebsd-x64': 4.50.0
'@rollup/rollup-linux-arm-gnueabihf': 4.50.0
'@rollup/rollup-linux-arm-musleabihf': 4.50.0
'@rollup/rollup-linux-arm64-gnu': 4.50.0
'@rollup/rollup-linux-arm64-musl': 4.50.0
'@rollup/rollup-linux-loongarch64-gnu': 4.50.0
'@rollup/rollup-linux-ppc64-gnu': 4.50.0
'@rollup/rollup-linux-riscv64-gnu': 4.50.0
'@rollup/rollup-linux-riscv64-musl': 4.50.0
'@rollup/rollup-linux-s390x-gnu': 4.50.0
'@rollup/rollup-linux-x64-gnu': 4.50.0
'@rollup/rollup-linux-x64-musl': 4.50.0
'@rollup/rollup-openharmony-arm64': 4.50.0
'@rollup/rollup-win32-arm64-msvc': 4.50.0
'@rollup/rollup-win32-ia32-msvc': 4.50.0
'@rollup/rollup-win32-x64-msvc': 4.50.0
fsevents: 2.3.3
run-parallel@1.2.0:
@@ -4748,7 +4757,7 @@ snapshots:
fdir: 6.4.6(picomatch@4.0.2)
picomatch: 4.0.2
postcss: 8.5.6
rollup: 4.46.4
rollup: 4.50.0
tinyglobby: 0.2.14
optionalDependencies:
'@types/node': 22.15.32