diff --git a/.changes/refactor-refcell.md b/.changes/refactor-refcell.md new file mode 100644 index 000000000000..e8d4d1a3c8c7 --- /dev/null +++ b/.changes/refactor-refcell.md @@ -0,0 +1,8 @@ +--- +"tauri-runtime": patch:bug +"tauri-runtime-wry": patch:bug +--- + +Harden `tauri-runtime-wry` window storage with a `WindowsStore` newtype that routes all +access through fallible borrow methods, preventing recurrence of the panic class narrowly +fixed in #14862. diff --git a/Cargo.lock b/Cargo.lock index bd6659c89090..2b00a7eea49f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1321,7 +1321,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -5540,7 +5540,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.60.2", ] [[package]] @@ -5957,7 +5957,7 @@ dependencies = [ "aes-gcm", "aes-kw", "argon2", - "base64 0.22.1", + "base64 0.21.7", "bitfield", "block-padding", "blowfish", @@ -8784,7 +8784,7 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tauri" -version = "2.11.1" +version = "2.11.2" dependencies = [ "anyhow", "bytes", @@ -8845,7 +8845,7 @@ dependencies = [ [[package]] name = "tauri-build" -version = "2.6.1" +version = "2.6.2" dependencies = [ "anyhow", "cargo_toml", @@ -8866,7 +8866,7 @@ dependencies = [ [[package]] name = "tauri-bundler" -version = "2.9.1" +version = "2.9.2" dependencies = [ "anyhow", "ar", @@ -8912,7 +8912,7 @@ dependencies = [ [[package]] name = "tauri-cli" -version = "2.11.1" +version = "2.11.2" dependencies = [ "ar", "axum", @@ -9006,7 +9006,7 @@ dependencies = [ [[package]] name = "tauri-codegen" -version = "2.6.1" +version = "2.6.2" dependencies = [ "base64 0.22.1", "brotli", @@ -9094,7 +9094,7 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "2.6.1" +version = "2.6.2" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -9106,7 +9106,7 @@ dependencies = [ [[package]] name = "tauri-plugin" -version = "2.6.1" +version = "2.6.2" dependencies = [ "anyhow", "glob", @@ -9153,7 +9153,7 @@ dependencies = [ [[package]] name = "tauri-runtime" -version = "2.11.1" +version = "2.11.2" dependencies = [ "cookie", "dpi", @@ -9176,7 +9176,7 @@ dependencies = [ [[package]] name = "tauri-runtime-wry" -version = "2.11.1" +version = "2.11.2" dependencies = [ "gtk", "http 1.3.1", @@ -9226,7 +9226,7 @@ dependencies = [ [[package]] name = "tauri-utils" -version = "2.9.1" +version = "2.9.2" dependencies = [ "aes-gcm", "anyhow", @@ -10654,7 +10654,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/crates/tauri-build/CHANGELOG.md b/crates/tauri-build/CHANGELOG.md index df654d6eda9f..fb80c0cdf3ee 100644 --- a/crates/tauri-build/CHANGELOG.md +++ b/crates/tauri-build/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## \[2.6.2] + +### Dependencies + +- Upgraded to `tauri-utils@2.9.2` +- Upgraded to `tauri-codegen@2.6.2` + ## \[2.6.1] ### Dependencies diff --git a/crates/tauri-build/Cargo.toml b/crates/tauri-build/Cargo.toml index 78e9b1551522..8061a8f2c3a3 100644 --- a/crates/tauri-build/Cargo.toml +++ b/crates/tauri-build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tauri-build" -version = "2.6.1" +version = "2.6.2" description = "build time code to pair with https://crates.io/crates/tauri" exclude = ["CHANGELOG.md", "/target"] readme = "README.md" @@ -26,8 +26,8 @@ targets = [ [dependencies] anyhow = "1" quote = { version = "1", optional = true } -tauri-codegen = { version = "2.6.1", path = "../tauri-codegen", optional = true } -tauri-utils = { version = "2.9.1", path = "../tauri-utils", features = [ +tauri-codegen = { version = "2.6.2", path = "../tauri-codegen", optional = true } +tauri-utils = { version = "2.9.2", path = "../tauri-utils", features = [ "build-2", "resources", ] } diff --git a/crates/tauri-bundler/CHANGELOG.md b/crates/tauri-bundler/CHANGELOG.md index 31a0e7f95a1f..c86a769b304b 100644 --- a/crates/tauri-bundler/CHANGELOG.md +++ b/crates/tauri-bundler/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## \[2.9.2] + +### Dependencies + +- Upgraded to `tauri-utils@2.9.2` + ## \[2.9.1] ### Dependencies diff --git a/crates/tauri-bundler/Cargo.toml b/crates/tauri-bundler/Cargo.toml index 5dc1cdb117b3..a32aa0007858 100644 --- a/crates/tauri-bundler/Cargo.toml +++ b/crates/tauri-bundler/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tauri-bundler" -version = "2.9.1" +version = "2.9.2" authors = [ "George Burton ", "Tauri Programme within The Commons Conservancy", @@ -15,7 +15,7 @@ rust-version = "1.77.2" exclude = ["CHANGELOG.md", "/target", "rustfmt.toml"] [dependencies] -tauri-utils = { version = "2.9.1", path = "../tauri-utils", features = [ +tauri-utils = { version = "2.9.2", path = "../tauri-utils", features = [ "resources", ] } image = "0.25" diff --git a/crates/tauri-cli/CHANGELOG.md b/crates/tauri-cli/CHANGELOG.md index 66b7b7d8f4ea..03b4ca37d59c 100644 --- a/crates/tauri-cli/CHANGELOG.md +++ b/crates/tauri-cli/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## \[2.11.2] + +### Dependencies + +- Upgraded to `tauri-utils@2.9.2` +- Upgraded to `tauri-bundler@2.9.2` + ## \[2.11.1] ### Dependencies diff --git a/crates/tauri-cli/Cargo.toml b/crates/tauri-cli/Cargo.toml index 97d9409a730c..877394237347 100644 --- a/crates/tauri-cli/Cargo.toml +++ b/crates/tauri-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tauri-cli" -version = "2.11.1" +version = "2.11.2" authors = ["Tauri Programme within The Commons Conservancy"] edition = "2021" rust-version = "1.77.2" @@ -47,7 +47,7 @@ sublime_fuzzy = "0.7" clap_complete = "4" clap = { version = "4", features = ["derive", "env"] } thiserror = "2" -tauri-bundler = { version = "2.9.1", default-features = false, path = "../tauri-bundler" } +tauri-bundler = { version = "2.9.2", default-features = false, path = "../tauri-bundler" } colored = "2" serde = { version = "1", features = ["derive"] } serde_json = { version = "1", features = ["preserve_order"] } @@ -58,7 +58,7 @@ shared_child = "1" duct = "1.0" toml_edit = { version = "0.25", features = ["serde"] } json-patch = "3" -tauri-utils = { version = "2.9.1", path = "../tauri-utils", features = [ +tauri-utils = { version = "2.9.2", path = "../tauri-utils", features = [ "isolation", "schema", "config-json5", diff --git a/crates/tauri-cli/config.schema.json b/crates/tauri-cli/config.schema.json index dbbd5dff137e..898317a465a0 100644 --- a/crates/tauri-cli/config.schema.json +++ b/crates/tauri-cli/config.schema.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://schema.tauri.app/config/2.11.1", + "$id": "https://schema.tauri.app/config/2.11.2", "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", diff --git a/crates/tauri-cli/metadata-v2.json b/crates/tauri-cli/metadata-v2.json index 091d891cd366..9484a7df7caa 100644 --- a/crates/tauri-cli/metadata-v2.json +++ b/crates/tauri-cli/metadata-v2.json @@ -1,9 +1,9 @@ { "cli.js": { - "version": "2.11.1", + "version": "2.11.2", "node": ">= 10.0.0" }, - "tauri": "2.11.1", - "tauri-build": "2.6.1", - "tauri-plugin": "2.6.1" + "tauri": "2.11.2", + "tauri-build": "2.6.2", + "tauri-plugin": "2.6.2" } diff --git a/crates/tauri-codegen/CHANGELOG.md b/crates/tauri-codegen/CHANGELOG.md index fa8c1c97352d..a445a96af25a 100644 --- a/crates/tauri-codegen/CHANGELOG.md +++ b/crates/tauri-codegen/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## \[2.6.2] + +### Dependencies + +- Upgraded to `tauri-utils@2.9.2` + ## \[2.6.1] ### Dependencies diff --git a/crates/tauri-codegen/Cargo.toml b/crates/tauri-codegen/Cargo.toml index 5f49642cbb78..9f23170ebc8a 100644 --- a/crates/tauri-codegen/Cargo.toml +++ b/crates/tauri-codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tauri-codegen" -version = "2.6.1" +version = "2.6.2" description = "code generation meant to be consumed inside of `tauri` through `tauri-build` or `tauri-macros`" exclude = ["CHANGELOG.md", "/target"] readme = "README.md" @@ -20,7 +20,7 @@ quote = "1" syn = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" -tauri-utils = { version = "2.9.1", path = "../tauri-utils", features = [ +tauri-utils = { version = "2.9.2", path = "../tauri-utils", features = [ "build-2", ] } thiserror = "2" diff --git a/crates/tauri-macros/CHANGELOG.md b/crates/tauri-macros/CHANGELOG.md index 2e3ec6a59577..1058cdb099b1 100644 --- a/crates/tauri-macros/CHANGELOG.md +++ b/crates/tauri-macros/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## \[2.6.2] + +### Dependencies + +- Upgraded to `tauri-utils@2.9.2` +- Upgraded to `tauri-codegen@2.6.2` + ## \[2.6.1] ### Dependencies diff --git a/crates/tauri-macros/Cargo.toml b/crates/tauri-macros/Cargo.toml index 69e7f3891684..8b50579b6db1 100644 --- a/crates/tauri-macros/Cargo.toml +++ b/crates/tauri-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tauri-macros" -version = "2.6.1" +version = "2.6.2" description = "Macros for the tauri crate." exclude = ["CHANGELOG.md", "/target"] readme = "README.md" @@ -20,8 +20,8 @@ proc-macro2 = { version = "1", features = ["span-locations"] } quote = "1" syn = { version = "2", features = ["full"] } heck = "0.5" -tauri-codegen = { version = "2.6.1", default-features = false, path = "../tauri-codegen" } -tauri-utils = { version = "2.9.1", path = "../tauri-utils" } +tauri-codegen = { version = "2.6.2", default-features = false, path = "../tauri-codegen" } +tauri-utils = { version = "2.9.2", path = "../tauri-utils" } [features] custom-protocol = [] diff --git a/crates/tauri-plugin/CHANGELOG.md b/crates/tauri-plugin/CHANGELOG.md index 8667ba6939ac..b721b377ca6c 100644 --- a/crates/tauri-plugin/CHANGELOG.md +++ b/crates/tauri-plugin/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## \[2.6.2] + +### Dependencies + +- Upgraded to `tauri-utils@2.9.2` + ## \[2.6.1] ### Dependencies diff --git a/crates/tauri-plugin/Cargo.toml b/crates/tauri-plugin/Cargo.toml index f169d51852f9..496c3500c97c 100644 --- a/crates/tauri-plugin/Cargo.toml +++ b/crates/tauri-plugin/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tauri-plugin" -version = "2.6.1" +version = "2.6.2" description = "Build script and runtime Tauri plugin definitions" authors.workspace = true homepage.workspace = true @@ -27,7 +27,7 @@ runtime = [] [dependencies] anyhow = { version = "1", optional = true } serde = { version = "1", optional = true } -tauri-utils = { version = "2.9.1", default-features = false, features = [ +tauri-utils = { version = "2.9.2", default-features = false, features = [ "build-2", ], path = "../tauri-utils" } serde_json = { version = "1", optional = true } diff --git a/crates/tauri-runtime-wry/CHANGELOG.md b/crates/tauri-runtime-wry/CHANGELOG.md index 3f31a89f6380..8dacff6c100e 100644 --- a/crates/tauri-runtime-wry/CHANGELOG.md +++ b/crates/tauri-runtime-wry/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## \[2.11.2] + +### Dependencies + +- Upgraded to `tauri-utils@2.9.2` +- Upgraded to `tauri-runtime@2.11.2` + ## \[2.11.1] ### Dependencies diff --git a/crates/tauri-runtime-wry/Cargo.toml b/crates/tauri-runtime-wry/Cargo.toml index 219336a9e479..4a7d21f1a433 100644 --- a/crates/tauri-runtime-wry/Cargo.toml +++ b/crates/tauri-runtime-wry/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tauri-runtime-wry" -version = "2.11.1" +version = "2.11.2" description = "Wry bindings to the Tauri runtime" exclude = ["CHANGELOG.md", "/target"] readme = "README.md" @@ -19,8 +19,8 @@ wry = { version = "0.55.0", default-features = false, features = [ "linux-body", ] } tao = { version = "0.35.0", default-features = false, features = ["rwh_06"] } -tauri-runtime = { version = "2.11.1", path = "../tauri-runtime" } -tauri-utils = { version = "2.9.1", path = "../tauri-utils" } +tauri-runtime = { version = "2.11.2", path = "../tauri-runtime" } +tauri-utils = { version = "2.9.2", path = "../tauri-utils" } raw-window-handle = "0.6" http = "1" url = "2" @@ -70,6 +70,7 @@ macos-private-api = [ # TODO: Remove in v3 - wry does not have this feature anymore objc-exception = [] tracing = ["dep:tracing", "wry/tracing"] +tracing-borrows = ["dep:tracing"] macos-proxy = ["wry/mac-proxy"] unstable = [] common-controls-v6 = [] diff --git a/crates/tauri-runtime-wry/src/dialog/mod.rs b/crates/tauri-runtime-wry/src/dialog/mod.rs index 156d6a6d2a4c..3befa7b8a0bf 100644 --- a/crates/tauri-runtime-wry/src/dialog/mod.rs +++ b/crates/tauri-runtime-wry/src/dialog/mod.rs @@ -5,7 +5,9 @@ #[cfg(windows)] mod windows; -pub fn error>(err: S) { +// Takes a `&'static str` here since we convert clickable hyperlinks, +// DO NOT pass in untrusted input +pub fn error(err: &'static str) { #[cfg(windows)] windows::error(err); diff --git a/crates/tauri-runtime-wry/src/dialog/windows.rs b/crates/tauri-runtime-wry/src/dialog/windows.rs index 2f520c563723..0f9b2c2a65e1 100644 --- a/crates/tauri-runtime-wry/src/dialog/windows.rs +++ b/crates/tauri-runtime-wry/src/dialog/windows.rs @@ -12,8 +12,8 @@ enum Level { Info, } -pub fn error>(err: S) { - dialog_inner(err.as_ref(), Level::Error); +pub fn error(err: &'static str) { + dialog_inner(err, Level::Error); } fn dialog_inner(err: &str, level: Level) { diff --git a/crates/tauri-runtime-wry/src/lib.rs b/crates/tauri-runtime-wry/src/lib.rs index 7cfbe1855033..063c5cb2ee25 100644 --- a/crates/tauri-runtime-wry/src/lib.rs +++ b/crates/tauri-runtime-wry/src/lib.rs @@ -125,10 +125,9 @@ pub use tao::platform::macos::{ use tauri_runtime::ActivationPolicy; use std::{ - cell::RefCell, collections::{ hash_map::Entry::{Occupied, Vacant}, - BTreeMap, HashMap, HashSet, + HashMap, HashSet, }, fmt, ops::Deref, @@ -405,7 +404,7 @@ impl Context { #[cfg(feature = "tracing")] #[derive(Debug, Clone, Default)] -pub struct ActiveTraceSpanStore(Rc>>); +pub struct ActiveTraceSpanStore(Rc>>); #[cfg(feature = "tracing")] impl ActiveTraceSpanStore { @@ -426,16 +425,9 @@ pub enum ActiveTracingSpan { }, } -#[derive(Debug)] -pub struct WindowsStore(pub RefCell>); - -// SAFETY: we ensure this type is only used on the main thread. -#[allow(clippy::non_send_fields_in_send_ty)] -unsafe impl Send for WindowsStore {} +pub use self::windows_store::WindowsStore; -// SAFETY: we ensure this type is only used on the main thread. -#[allow(clippy::non_send_fields_in_send_ty)] -unsafe impl Sync for WindowsStore {} +mod windows_store; #[derive(Debug, Clone)] pub struct DispatcherMainThreadContext { @@ -2923,7 +2915,7 @@ impl Wry { let main_thread_id = current_thread().id(); let web_context = WebContextStore::default(); - let windows = Arc::new(WindowsStore(RefCell::new(BTreeMap::default()))); + let windows = Default::default(); let window_id_map = WindowIdStore::default(); let context = Context { @@ -3026,13 +3018,7 @@ impl Runtime for Wry { context: self.context.clone(), }; - self - .context - .main_thread - .windows - .0 - .borrow_mut() - .insert(window_id, window); + self.context.main_thread.windows.insert(window_id, window)?; let detached_webview = webview_id.map(|id| { let webview = DetachedWebview { @@ -3064,15 +3050,15 @@ impl Runtime for Wry { ) -> Result> { let label = pending.label.clone(); - let window = self - .context - .main_thread - .windows - .0 - .borrow() - .get(&window_id) - .map(|w| (w.inner.clone(), w.focused_webview.clone())); - if let Some((Some(window), focused_webview)) = window { + let window = self.context.main_thread.windows.store(|store| { + store.get(&window_id).and_then(|w| { + w.inner + .as_ref() + .map(|i| (Arc::clone(i), Arc::clone(&w.focused_webview))) + }) + })?; + + if let Some((window, focused_webview)) = window { let window_id_wrapper = Arc::new(Mutex::new(window_id)); let webview_id = self.context.next_webview_id(); @@ -3087,19 +3073,12 @@ impl Runtime for Wry { focused_webview, )?; - #[allow(unknown_lints, clippy::manual_inspect)] - self - .context - .main_thread - .windows - .0 - .borrow_mut() - .get_mut(&window_id) - .map(|w| { + self.context.main_thread.windows.store_mut(move |store| { + if let Some(w) = store.get_mut(&window_id) { w.webviews.push(webview); w.has_children.store(true, Ordering::Relaxed); - w - }); + }; + })?; let dispatcher = WryWebviewDispatcher { window_id: window_id_wrapper, @@ -3314,6 +3293,80 @@ where } } +#[cfg(any( + target_os = "macos", + windows, + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] +fn reparent_webview( + new_parent_window_id: WindowId, + webview: WebviewId, + windows: Arc, +) -> Result<()> { + let removed_webview = windows.store_mut(|store| { + store.get_mut(&new_parent_window_id).and_then(|w| { + w.webviews + .iter() + .position(|w| w.id == webview) + .map(|webview_index| w.webviews.remove(webview_index)) + }) + })?; + + match removed_webview { + None => Err(Error::FailedToSendMessage), + Some(webview) => { + let window = windows.store(|w| { + w.get(&new_parent_window_id) + .and_then(|w| w.inner.as_ref().map(Arc::clone)) + })?; + match window { + None => Err(Error::FailedToSendMessage), + Some(new_parent_window) => { + #[cfg(target_os = "macos")] + let reparent_result = { + use wry::WebViewExtMacOS; + webview.inner.reparent(new_parent_window.ns_window() as _) + }; + + #[cfg(windows)] + let reparent_result = { webview.inner.reparent(new_parent_window.hwnd()) }; + + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + let reparent_result = { + if let Some(container) = new_parent_window.default_vbox() { + webview.inner.reparent(container) + } else { + Err(wry::Error::MessageSender) + } + }; + + match reparent_result { + Ok(_) => { + windows.store_mut(|store| { + store + .get_mut(&new_parent_window_id) + .map(|w| w.webviews.push(webview)) + })?; + Ok(()) + } + Err(_err) => Err(Error::FailedToSendMessage), + } + } + } + } + } +} + pub struct EventLoopIterationContext<'a, T: UserEvent> { pub callback: &'a mut (dyn FnMut(RunEvent) + 'static), pub window_id_map: WindowIdStore, @@ -3370,15 +3423,28 @@ fn handle_user_message( } }, Message::Window(id, window_message) => { - let w = windows.0.borrow().get(&id).map(|w| { - ( - w.inner.clone(), - w.webviews.clone(), - w.has_children.load(Ordering::Relaxed), - w.window_event_listeners.clone(), - ) + let windows = windows.store(|store| { + store.get(&id).and_then(|w| { + w.inner.as_ref().map(|inner| { + ( + Arc::clone(inner), + w.webviews.clone(), + w.has_children.load(Ordering::Relaxed), + Arc::clone(&w.window_event_listeners), + ) + }) + }) }); - if let Some((Some(window), webviews, has_children, window_event_listeners)) = w { + + let windows = match windows { + Ok(windows) => windows, + Err(e) => { + log::error!("{e}"); + return; + } + }; + + if let Some((window, webviews, has_children, window_event_listeners)) = windows { match window_message { WindowMessage::AddEventListener(id, listener) => { window_event_listeners.lock().unwrap().insert(id, listener); @@ -3691,69 +3757,25 @@ fn handle_user_message( target_os = "netbsd", target_os = "openbsd" ))] - if let WebviewMessage::Reparent(new_parent_window_id, tx) = webview_message { - let webview_handle = windows.0.borrow_mut().get_mut(&window_id).and_then(|w| { - w.webviews - .iter() - .position(|w| w.id == webview_id) - .map(|webview_index| w.webviews.remove(webview_index)) - }); - - if let Some(webview) = webview_handle { - if let Some((Some(new_parent_window), new_parent_window_webviews)) = windows - .0 - .borrow_mut() - .get_mut(&new_parent_window_id) - .map(|w| (w.inner.clone(), &mut w.webviews)) - { - #[cfg(target_os = "macos")] - let reparent_result = { - use wry::WebViewExtMacOS; - webview.inner.reparent(new_parent_window.ns_window() as _) - }; - #[cfg(windows)] - let reparent_result = { webview.inner.reparent(new_parent_window.hwnd()) }; - - #[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ))] - let reparent_result = { - if let Some(container) = new_parent_window.default_vbox() { - webview.inner.reparent(container) - } else { - Err(wry::Error::MessageSender) - } - }; - - match reparent_result { - Ok(_) => { - new_parent_window_webviews.push(webview); - tx.send(Ok(())).unwrap(); - } - Err(e) => { - log::error!("failed to reparent webview: {e}"); - tx.send(Err(Error::FailedToSendMessage)).unwrap(); - } - } - } - } else { - tx.send(Err(Error::FailedToSendMessage)).unwrap(); + if let WebviewMessage::Reparent(new_parent_window_id, ref tx) = webview_message { + if let Err(e) = reparent_webview(new_parent_window_id, webview_id, Arc::clone(&windows)) { + log::error!("failed to reparent webview: {e}"); + tx.send(Err(e)).unwrap(); + return; } - - return; } - let webview_handle = windows.0.borrow().get(&window_id).map(|w| { - ( - w.inner.clone(), - w.webviews.iter().find(|w| w.id == webview_id).cloned(), + let window_and_webview = windows.window(window_id, |window| { + window.inner.clone().zip( + window + .webviews + .iter() + .find(|wv| wv.id == webview_id) + .cloned(), ) }); - if let Some((Some(window), Some(webview))) = webview_handle { + + if let Ok(Some((window, webview))) = window_and_webview { match webview_message { WebviewMessage::WebviewEvent(_) => { /* already handled */ } WebviewMessage::SynthesizedWindowEvent(_) => { /* already handled */ } @@ -3818,13 +3840,16 @@ fn handle_user_message( let _ = webview.print(); } WebviewMessage::Close => { - #[allow(unknown_lints, clippy::manual_inspect)] - windows.0.borrow_mut().get_mut(&window_id).map(|window| { - if let Some(i) = window.webviews.iter().position(|w| w.id == webview.id) { - window.webviews.remove(i); + if let Err(e) = windows.store_mut(|store| { + let window = store.get_mut(&window_id); + if let Some(window) = window { + if let Some(i) = window.webviews.iter().position(|w| w.id == webview.id) { + window.webviews.remove(i); + } } - window - }); + }) { + log::error!("unable to remove webview from window: {e}") + } } WebviewMessage::SetBounds(bounds) => { let bounds: RectWrapper = bounds.into(); @@ -4060,30 +4085,37 @@ fn handle_user_message( } } Message::CreateWebview(window_id, handler) => { - let window = windows - .0 - .borrow() - .get(&window_id) - .map(|w| (w.inner.clone(), w.focused_webview.clone())); - if let Some((Some(window), focused_webview)) = window { - match handler(&window, CreateWebviewOptions { focused_webview }) { - Ok(webview) => { - #[allow(unknown_lints, clippy::manual_inspect)] - windows.0.borrow_mut().get_mut(&window_id).map(|w| { - w.webviews.push(webview); - w.has_children.store(true, Ordering::Relaxed); - w - }); - } - Err(e) => { - log::error!("{e}"); + let window = windows.window(window_id, |window| { + (window.inner.clone(), window.focused_webview.clone()) + }); + + match window { + Ok((Some(window), focused_webview)) => { + match handler(&window, CreateWebviewOptions { focused_webview }) { + Ok(webview) => { + if let Err(e) = windows.store_mut(move |store| { + if let Some(w) = store.get_mut(&window_id) { + w.webviews.push(webview); + w.has_children.store(true, Ordering::Relaxed); + }; + }) { + log::error!("unable to add webview to window: {e}"); + } + } + Err(e) => { + log::error!("{e}"); + } } } + Ok(_) => log::error!("window not found: {window_id:?}"), + Err(e) => log::error!("unable to create webview: {e}"), } } Message::CreateWindow(window_id, handler) => match handler(event_loop) { Ok(webview) => { - windows.0.borrow_mut().insert(window_id, webview); + if let Err(e) = windows.insert(window_id, webview) { + log::error!("unable to create window: {e}"); + } } Err(e) => { log::error!("{e}"); @@ -4118,23 +4150,28 @@ fn handle_user_message( None }; - windows.0.borrow_mut().insert( - window_id, - WindowWrapper { - label, - has_children: AtomicBool::new(false), - inner: Some(window.clone()), - window_event_listeners: Default::default(), - webviews: Vec::new(), - #[cfg(windows)] - background_color, - #[cfg(windows)] - is_window_transparent, - #[cfg(windows)] - surface, - focused_webview: Default::default(), - }, - ); + if let Err(e) = windows.store_mut(|store| { + store.insert( + window_id, + WindowWrapper { + label, + has_children: AtomicBool::new(false), + inner: Some(window.clone()), + window_event_listeners: Default::default(), + webviews: Vec::new(), + #[cfg(windows)] + background_color, + #[cfg(windows)] + is_window_transparent, + #[cfg(windows)] + surface, + focused_webview: Default::default(), + }, + ) + }) { + log::error!("unable to add window to store: {e}"); + } + sender.send(Ok(Arc::downgrade(&window))).unwrap(); } else { sender.send(Err(Error::CreateWindow)).unwrap(); @@ -4196,16 +4233,17 @@ fn handle_event_loop( #[cfg(windows)] Event::RedrawRequested(id) => { if let Some(window_id) = window_id_map.get(&id) { - let mut windows_ref = windows.0.borrow_mut(); - if let Some(window) = windows_ref.get_mut(&window_id) { + if let Err(e) = windows.window_mut(window_id, |window| { if window.is_window_transparent { let background_color = window.background_color; - if let Some(surface) = &mut window.surface { - if let Some(window) = &window.inner { - window.draw_surface(surface, background_color); + if let Some(inner) = &window.inner { + if let Some(surface) = &mut window.surface { + inner.draw_surface(surface, background_color); } } } + }) { + log::error!("redraw requested: unable to get window: {e}"); } } } @@ -4220,14 +4258,14 @@ fn handle_event_loop( webview_id, WebviewMessage::WebviewEvent(event), )) => { - let windows_ref = windows.0.borrow(); - if let Some(window) = windows_ref.get(&window_id) { - if let Some(webview) = window.webviews.iter().find(|w| w.id == webview_id) { - let label = webview.label.clone(); - let webview_event_listeners = webview.webview_event_listeners.clone(); - - drop(windows_ref); - + match windows.window(window_id, |window| { + window + .webviews + .iter() + .find(|w| w.id == webview_id) + .map(|w| (w.label.clone(), w.webview_event_listeners.clone())) + }) { + Ok(Some((label, webview_event_listeners))) => { callback(RunEvent::WebviewEvent { label, event: event.clone(), @@ -4238,6 +4276,8 @@ fn handle_event_loop( handler(&event); } } + Ok(None) => log::warn!("webview {webview_id} not found in window {window_id:?}"), + Err(e) => log::error!("failed to handle {event:?} because: {e}"), } } @@ -4247,24 +4287,22 @@ fn handle_event_loop( WebviewMessage::SynthesizedWindowEvent(event), )) => { if let Some(event) = WindowEventWrapper::from(event).0 { - let windows_ref = windows.0.borrow(); - let window = windows_ref.get(&window_id); - if let Some(window) = window { - let label = window.label.clone(); - let window_event_listeners = window.window_event_listeners.clone(); - - drop(windows_ref); - - callback(RunEvent::WindowEvent { - label, - event: event.clone(), - }); + match windows.window(window_id, |window| { + (window.label.clone(), window.window_event_listeners.clone()) + }) { + Ok((label, window_event_listeners)) => { + callback(RunEvent::WindowEvent { + label, + event: event.clone(), + }); - let listeners = window_event_listeners.lock().unwrap(); - let handlers = listeners.values(); - for handler in handlers { - handler(&event); + let listeners = window_event_listeners.lock().unwrap(); + let handlers = listeners.values(); + for handler in handlers { + handler(&event); + } } + Err(e) => log::error!("failed to handle {event:?} because: {e}"), } } } @@ -4274,14 +4312,16 @@ fn handle_event_loop( } => { if let Some(window_id) = window_id_map.get(&window_id) { { - let windows_ref = windows.0.borrow(); - if let Some(window) = windows_ref.get(&window_id) { - if let Some(event) = WindowEventWrapper::parse(window, &event).0 { - let label = window.label.clone(); - let window_event_listeners = window.window_event_listeners.clone(); - - drop(windows_ref); - + match windows.window(window_id, |window| { + WindowEventWrapper::parse(window, &event).0.map(|event| { + ( + event, + window.label.clone(), + window.window_event_listeners.clone(), + ) + }) + }) { + Ok(Some((event, label, window_event_listeners))) => { callback(RunEvent::WindowEvent { label, event: event.clone(), @@ -4292,33 +4332,39 @@ fn handle_event_loop( handler(&event); } } + Ok(None) => { + log::debug!("No WindowEventWrapper found for window {window_id:?} on event {event:?}") + } + Err(e) => log::error!("failed to handle {event:?} because: {e}"), } } match event { #[cfg(windows)] TaoWindowEvent::ThemeChanged(theme) => { - if let Some(window) = windows.0.borrow().get(&window_id) { - for webview in &window.webviews { - let theme = match theme { - TaoTheme::Dark => wry::Theme::Dark, - TaoTheme::Light => wry::Theme::Light, - _ => wry::Theme::Light, - }; - if let Err(e) = webview.set_theme(theme) { - log::error!("failed to set theme: {e}"); + match windows.window(window_id, |w| w.webviews.clone()) { + Ok(webviews) => { + for webview in webviews { + let theme = match theme { + TaoTheme::Dark => wry::Theme::Dark, + TaoTheme::Light => wry::Theme::Light, + _ => wry::Theme::Light, + }; + if let Err(e) = webview.set_theme(theme) { + log::error!("failed to set theme: {e}"); + } } } + Err(e) => log::error!("failed to handle {event:?} because: {e}"), } } TaoWindowEvent::CloseRequested => { on_close_requested(callback, window_id, windows); } TaoWindowEvent::Destroyed => { - let removed = windows.0.borrow_mut().remove(&window_id).is_some(); - if removed { - let is_empty = windows.0.borrow().is_empty(); - if is_empty { + match windows.remove(window_id) { + Ok(false) => { /* more windows exist */ } + Ok(true) => { let (tx, rx) = channel(); callback(RunEvent::ExitRequested { code: None, tx }); @@ -4329,28 +4375,40 @@ fn handle_event_loop( *control_flow = ControlFlow::Exit; } } + Err(e) => { + log::error!("TaoWindowEvent::Destroyed: failed to remove window from store: {e}") + } } } TaoWindowEvent::Resized(size) => { - if let Some((Some(window), webviews)) = windows - .0 - .borrow() - .get(&window_id) - .map(|w| (w.inner.clone(), w.webviews.clone())) - { - let size = size.to_logical::(window.scale_factor()); - for webview in webviews { - if let Some(b) = &*webview.bounds.lock().unwrap() { - if let Err(e) = webview.set_bounds(wry::Rect { - position: LogicalPosition::new(size.width * b.x_rate, size.height * b.y_rate) - .into(), - size: LogicalSize::new(size.width * b.width_rate, size.height * b.height_rate) + match windows.window(window_id, |window| { + window + .inner + .as_ref() + .map(|i| (Arc::clone(i), window.webviews.clone())) + }) { + Ok(Some((window, webviews))) => { + let size = size.to_logical::(window.scale_factor()); + for webview in webviews { + if let Some(b) = &*webview.bounds.lock().unwrap() { + if let Err(e) = webview.set_bounds(wry::Rect { + position: LogicalPosition::new(size.width * b.x_rate, size.height * b.y_rate) + .into(), + size: LogicalSize::new( + size.width * b.width_rate, + size.height * b.height_rate, + ) .into(), - }) { - log::error!("failed to autoresize webview: {e}"); + }) { + log::error!("failed to autoresize webview: {e}"); + } } } } + Ok(_) => { + log::error!("window {window_id:?} has no inner window") + } + Err(e) => log::error!("failed to resize window: {e}"), } } _ => {} @@ -4440,36 +4498,37 @@ fn on_close_requested<'a, T: UserEvent>( windows: Arc, ) { let (tx, rx) = channel(); - let windows_ref = windows.0.borrow(); - if let Some(w) = windows_ref.get(&window_id) { - let label = w.label.clone(); - let window_event_listeners = w.window_event_listeners.clone(); - - drop(windows_ref); - - let listeners = window_event_listeners.lock().unwrap(); - let handlers = listeners.values(); - for handler in handlers { - handler(&WindowEvent::CloseRequested { - signal_tx: tx.clone(), + match windows.window(window_id, |window| { + (window.label.clone(), window.window_event_listeners.clone()) + }) { + Ok((label, window_event_listeners)) => { + let listeners = window_event_listeners.lock().unwrap(); + let handlers = listeners.values(); + for handler in handlers { + handler(&WindowEvent::CloseRequested { + signal_tx: tx.clone(), + }); + } + callback(RunEvent::WindowEvent { + label, + event: WindowEvent::CloseRequested { signal_tx: tx }, }); + if let Ok(true) = rx.try_recv() { + } else { + on_window_close(window_id, windows); + } } - callback(RunEvent::WindowEvent { - label, - event: WindowEvent::CloseRequested { signal_tx: tx }, - }); - if let Ok(true) = rx.try_recv() { - } else { - on_window_close(window_id, windows); - } + Err(e) => log::error!("failed to close window: {e}"), } } fn on_window_close(window_id: WindowId, windows: Arc) { - if let Some(window_wrapper) = windows.0.borrow_mut().get_mut(&window_id) { - window_wrapper.inner = None; + if let Err(e) = windows.window_mut(window_id, |window| { + window.inner = None; #[cfg(windows)] - window_wrapper.surface.take(); + window.surface.take(); + }) { + log::error!("failed to close window: {e}"); } } @@ -4930,31 +4989,34 @@ You may have it installed on another user account, but it is not available for t tauri_runtime::webview::NewWindowResponse::Allow => wry::NewWindowResponse::Allow, #[cfg(desktop)] tauri_runtime::webview::NewWindowResponse::Create { window_id } => { - let windows = &context.main_thread.windows.0; - let webview = windows - .borrow() - .get(&window_id) - .unwrap() - .webviews - .first() - .unwrap() - .clone(); - - #[cfg(desktop)] - wry::NewWindowResponse::Create { - #[cfg(target_os = "macos")] - webview: wry::WebViewExtMacOS::webview(&*webview).as_super().into(), - #[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd", - ))] - webview: webview.webview(), - #[cfg(windows)] - webview: webview.webview(), + match context + .main_thread + .windows + .window(window_id, |window| window.webviews.first().cloned()) + { + Ok(Some(webview)) => + { + #[cfg(desktop)] + return wry::NewWindowResponse::Create { + #[cfg(target_os = "macos")] + webview: wry::WebViewExtMacOS::webview(&*webview).as_super().into(), + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + ))] + webview: webview.webview(), + #[cfg(windows)] + webview: webview.webview(), + } + } + Ok(None) => log::error!("No webviews found in window {window_id:?}"), + Err(e) => log::error!("NewWindowResponse::Create: {e}"), } + + wry::NewWindowResponse::Deny } tauri_runtime::webview::NewWindowResponse::Deny => wry::NewWindowResponse::Deny, } @@ -5125,21 +5187,19 @@ You may have it installed on another user account, but it is not available for t let context_ = context.clone(); let window_id_ = window_id.clone(); webview_builder = webview_builder.with_on_web_content_process_terminate_handler(move || { - if let Ok(windows) = &context_.main_thread.windows.0.try_borrow() { - if let Some(window) = windows.get(&*window_id_.lock().unwrap()) { - if let Some(webview) = window.webviews.iter().find(|w| w.id == id) { - match webview.reload() { - Ok(_) => log::debug!("webview reloaded"), - Err(e) => log::error!("failed to reload webview: {}", e), - } - } else { - log::error!("failed to find webview") + let window_id = *window_id_.lock().unwrap(); + let result = context_.main_thread.windows.window(window_id, |window| { + if let Some(webview) = window.webviews.iter().find(|w| w.id == id) { + match webview.reload() { + Ok(_) => log::debug!("webview reloaded"), + Err(e) => log::error!("failed to reload webview: {e}"), } } else { - log::error!("failed to get window") + log::error!("failed to find webview"); } - } else { - log::error!("failed to borrow windows") + }); + if let Err(e) = result { + log::error!("on_web_content_process_terminate: {e}"); } }); } diff --git a/crates/tauri-runtime-wry/src/windows_store.rs b/crates/tauri-runtime-wry/src/windows_store.rs new file mode 100644 index 000000000000..2372dde53187 --- /dev/null +++ b/crates/tauri-runtime-wry/src/windows_store.rs @@ -0,0 +1,146 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use crate::WindowWrapper; +use std::{ + collections::BTreeMap, + fmt, + sync::{RwLock, TryLockError}, +}; +use tauri_runtime::window::WindowId; + +type WindowMap = BTreeMap; + +type Result = std::result::Result; + +#[derive(Debug)] +pub enum Error { + /// The store's lock could not be acquired (already held in a conflicting mode). + Borrow(LockKind), + /// The store's lock was poisoned by a thread panicking while holding it. + Poisoned(LockKind), + WindowNotFound(WindowId), +} + +#[derive(Debug, Clone, Copy)] +pub enum LockKind { + Read, + Write, +} + +impl fmt::Display for LockKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + LockKind::Read => "read", + LockKind::Write => "write", + }) + } +} + +impl std::error::Error for Error {} +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::Borrow(kind) => write!(f, "windows_store {kind} lock unavailable"), + Error::Poisoned(kind) => write!(f, "windows_store {kind} lock poisoned"), + Error::WindowNotFound(id) => write!(f, "Window not in store: {id:?}"), + } + } +} + +impl From for tauri_runtime::Error { + fn from(value: Error) -> Self { + Self::WindowsStore(Box::new(value)) + } +} + +fn map_try_lock(kind: LockKind, result: std::result::Result>) -> Result { + result.map_err(|e| match e { + TryLockError::Poisoned(_) => Error::Poisoned(kind), + TryLockError::WouldBlock => Error::Borrow(kind), + }) +} + +#[cfg(feature = "tracing-borrows")] +#[track_caller] +fn trace_borrow(kind: LockKind) { + let caller = std::panic::Location::caller(); + tracing::trace!( + target: "tauri::runtime::wry::windows_store", + kind = %kind, + line = caller.line(), + column = caller.column(), + thread = format!("{:?}", std::thread::current().id()), + ); +} + +#[cfg(not(feature = "tracing-borrows"))] +#[inline(always)] +fn trace_borrow(_kind: LockKind) {} + +#[derive(Default)] +pub struct WindowsStore(RwLock); + +impl fmt::Debug for WindowsStore { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("WindowsStore(opaque)").finish() + } +} + +impl WindowsStore { + #[track_caller] + pub fn window(&self, id: WindowId, f: F) -> Result + where + F: FnOnce(&WindowWrapper) -> T, + { + trace_borrow(LockKind::Read); + let store = map_try_lock(LockKind::Read, self.0.try_read())?; + let window = store.get(&id).ok_or(Error::WindowNotFound(id))?; + Ok(f(window)) + } + + #[track_caller] + pub fn window_mut(&self, id: WindowId, f: F) -> Result + where + F: FnOnce(&mut WindowWrapper) -> T, + { + trace_borrow(LockKind::Write); + let mut store = map_try_lock(LockKind::Write, self.0.try_write())?; + let window = store.get_mut(&id).ok_or(Error::WindowNotFound(id))?; + Ok(f(window)) + } + + #[track_caller] + pub fn store(&self, f: F) -> Result + where + F: FnOnce(&WindowMap) -> T, + { + trace_borrow(LockKind::Read); + let store = map_try_lock(LockKind::Read, self.0.try_read())?; + Ok(f(&store)) + } + + #[track_caller] + pub fn store_mut(&self, f: F) -> Result + where + F: FnOnce(&mut WindowMap) -> T, + { + trace_borrow(LockKind::Write); + let mut store = map_try_lock(LockKind::Write, self.0.try_write())?; + Ok(f(&mut store)) + } + + pub fn insert(&self, id: WindowId, window: WindowWrapper) -> Result> { + self.store_mut(|s| s.insert(id, window)) + } + + /// Removes the window from the store and return `true` if the store is now empty. + pub fn remove(&self, id: WindowId) -> Result { + self.store_mut(|s| { + s.remove(&id); + s.is_empty() + }) + } +} + diff --git a/crates/tauri-runtime/CHANGELOG.md b/crates/tauri-runtime/CHANGELOG.md index bbcb4bd54e71..fab7ce5bb382 100644 --- a/crates/tauri-runtime/CHANGELOG.md +++ b/crates/tauri-runtime/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## \[2.11.2] + +### Dependencies + +- Upgraded to `tauri-utils@2.9.2` + ## \[2.11.1] ### Dependencies diff --git a/crates/tauri-runtime/Cargo.toml b/crates/tauri-runtime/Cargo.toml index 48a1c0b4ae31..7b73df4588f6 100644 --- a/crates/tauri-runtime/Cargo.toml +++ b/crates/tauri-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tauri-runtime" -version = "2.11.1" +version = "2.11.2" description = "Runtime for Tauri applications" exclude = ["CHANGELOG.md", "/target"] readme = "README.md" @@ -27,7 +27,7 @@ targets = [ serde = { version = "1", features = ["derive"] } serde_json = "1" thiserror = "2" -tauri-utils = { version = "2.9.1", path = "../tauri-utils" } +tauri-utils = { version = "2.9.2", path = "../tauri-utils" } http = "1" raw-window-handle = "0.6" url = { version = "2" } diff --git a/crates/tauri-runtime/src/lib.rs b/crates/tauri-runtime/src/lib.rs index eac06fbe95ef..549949e46cdc 100644 --- a/crates/tauri-runtime/src/lib.rs +++ b/crates/tauri-runtime/src/lib.rs @@ -167,6 +167,8 @@ pub enum Error { FailedToRemoveDataStore, #[error("Could not find the webview runtime, make sure it is installed")] WebviewRuntimeNotInstalled, + #[error("WindowsStore: {0}")] + WindowsStore(Box), } /// Result type. diff --git a/crates/tauri-schema-generator/schemas/config.schema.json b/crates/tauri-schema-generator/schemas/config.schema.json index dbbd5dff137e..898317a465a0 100644 --- a/crates/tauri-schema-generator/schemas/config.schema.json +++ b/crates/tauri-schema-generator/schemas/config.schema.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://schema.tauri.app/config/2.11.1", + "$id": "https://schema.tauri.app/config/2.11.2", "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", diff --git a/crates/tauri-utils/CHANGELOG.md b/crates/tauri-utils/CHANGELOG.md index dc7ed0537832..435dc48706c7 100644 --- a/crates/tauri-utils/CHANGELOG.md +++ b/crates/tauri-utils/CHANGELOG.md @@ -1,5 +1,37 @@ # Changelog +## \[2.9.2] + +### Bug Fixes + +- [`b5b72ce51`](https://www.github.com/tauri-apps/tauri/commit/b5b72ce51811e9f95b1f7e9a05ea19c8f12ce694) ([#15383](https://www.github.com/tauri-apps/tauri/pull/15383) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix a regression in tauri-utils 2.8.3 that made empty path an invalid resource target, e.g. + + ```json + { + "bundle": { + "resources": { + "README.md": "", + } + } + } + ``` + + (this means `README.md` -> `$RESOURCE/README.md`, note this is a confusing behavior, and will be changed in v3) +- [`3fd8ba2c0`](https://www.github.com/tauri-apps/tauri/commit/3fd8ba2c022717068ff6a154ce12942c3a672232) ([#15388](https://www.github.com/tauri-apps/tauri/pull/15388) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix a regression in tauri-utils 2.8.3 that made an empty directory makes it skip all the following entries, e.g. + + ```json + { + "bundle": { + "resources": [ + "empty-directory", + "README.md" + ] + } + } + ``` + + if `empty-directory` is empty, the `README.md` will not be copied to the resource directory (skipped) + ## \[2.9.1] ### Dependencies diff --git a/crates/tauri-utils/Cargo.toml b/crates/tauri-utils/Cargo.toml index 2f0ae3c0efdd..7f39104d797c 100644 --- a/crates/tauri-utils/Cargo.toml +++ b/crates/tauri-utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tauri-utils" -version = "2.9.1" +version = "2.9.2" description = "Utilities for Tauri" exclude = ["CHANGELOG.md", "/target"] readme = "README.md" diff --git a/crates/tauri-utils/src/resources.rs b/crates/tauri-utils/src/resources.rs index 3fd0290b78ac..6c30b89de486 100644 --- a/crates/tauri-utils/src/resources.rs +++ b/crates/tauri-utils/src/resources.rs @@ -209,7 +209,21 @@ impl ResourcePathsIter<'_> { // preserving the file name as it is ResourcePathsInnerIter::Glob { .. } => dest.join(path.file_name().unwrap()), }, - None => dest.clone(), + None => { + if dest.components().count() == 0 { + // if current_dest is empty while processing a file pattern + // we preserve the file name as it is + // + // e.g. `{ "README.md": "" }` is `README.md` -> `$RESOURCE/README.md` + // + // TODO: This behavior is a confusing special case, + // remove this in v3 or make other cases like this work + // > `{ "README.md": "./folder/" }` is `README.md` -> `$RESOURCE/folder/README.md` (this gives `$RESOURCE/folder` today) + PathBuf::from(path.file_name().unwrap()) + } else { + dest.clone() + } + } } } else { // If [`ResourcePathsIter::pattern_iter`] is a [`PatternIter::Slice`] @@ -221,6 +235,7 @@ impl ResourcePathsIter<'_> { fn next_pattern(&mut self) -> Option> { self.current_dest = None; + self.current_iter = None; let pattern = match &mut self.pattern_iter { PatternIter::Slice(iter) => iter.next()?, @@ -237,10 +252,10 @@ impl ResourcePathsIter<'_> { Err(error) => return Some(Err(error.into())), }; match self.next_current_iter() { - Some(r) => return Some(r), + Some(r) => Some(r), None => { self.current_iter = None; - return Some(Err(crate::Error::GlobPathNotFound(pattern.clone()))); + Some(Err(crate::Error::GlobPathNotFound(pattern.clone()))) } } } else { @@ -257,12 +272,12 @@ impl ResourcePathsIter<'_> { None }, }); + // If the directory is empty, skip and continue to the next pattern + self.next_current_iter().or_else(|| self.next_pattern()) } else { - return Some(self.resource_from_path(path)); + Some(self.resource_from_path(path)) } } - - self.next_current_iter() } } @@ -358,6 +373,7 @@ mod tests { fs::create_dir_all(path.parent().unwrap()).unwrap(); fs::write(path, "").unwrap(); } + fs::create_dir_all("empty-directory").unwrap(); } fn resources_map(literal: &[(&str, &str)]) -> HashMap { @@ -377,6 +393,8 @@ mod tests { let resources = ResourcePaths::new( &[ + // `empty-directory` should not affect anything + "../empty-directory".into(), "../src/script.js".into(), "../src/assets".into(), "../src/index.html".into(), diff --git a/crates/tauri/CHANGELOG.md b/crates/tauri/CHANGELOG.md index 0803ad1a511c..b709e89f0c0b 100644 --- a/crates/tauri/CHANGELOG.md +++ b/crates/tauri/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## \[2.11.2] + +### Bug Fixes + +- [`47e1b7549`](https://www.github.com/tauri-apps/tauri/commit/47e1b754951bffeedbcd6400928d60755fb954de) ([#15386](https://www.github.com/tauri-apps/tauri/pull/15386) by [@DominikPeters](https://www.github.com/tauri-apps/tauri/../../DominikPeters)) Fixed `Submenu.setAsWindowsMenuForNSApp()` calling the Help menu setter instead of the Window menu setter. + +### Dependencies + +- Upgraded to `tauri-utils@2.9.2` +- Upgraded to `tauri-runtime@2.11.2` +- Upgraded to `tauri-runtime-wry@2.11.2` +- Upgraded to `tauri-macros@2.6.2` +- Upgraded to `tauri-build@2.6.2` + ## \[2.11.1] ### Enhancements diff --git a/crates/tauri/Cargo.toml b/crates/tauri/Cargo.toml index 7e6a3669de25..2f87cdba8513 100644 --- a/crates/tauri/Cargo.toml +++ b/crates/tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tauri" -version = "2.11.1" +version = "2.11.2" description = "Make tiny, secure apps for all desktop platforms with Tauri" exclude = ["/test", "/.scripts", "CHANGELOG.md", "/target"] readme = "README.md" @@ -56,12 +56,12 @@ uuid = { version = "1", features = ["v4"], optional = true } url = "2" anyhow = "1" thiserror = "2" -tauri-runtime = { version = "2.11.1", path = "../tauri-runtime" } -tauri-macros = { version = "2.6.1", path = "../tauri-macros" } -tauri-utils = { version = "2.9.1", features = [ +tauri-runtime = { version = "2.11.2", path = "../tauri-runtime" } +tauri-macros = { version = "2.6.2", path = "../tauri-macros" } +tauri-utils = { version = "2.9.2", features = [ "resources", ], path = "../tauri-utils" } -tauri-runtime-wry = { version = "2.11.1", path = "../tauri-runtime-wry", default-features = false, optional = true } +tauri-runtime-wry = { version = "2.11.2", path = "../tauri-runtime-wry", default-features = false, optional = true, features = ["tracing-borrows"] } getrandom = "0.3" serde_repr = "0.1" http = "1" @@ -168,8 +168,8 @@ 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.6.1" } -tauri-utils = { path = "../tauri-utils/", version = "2.9.1", features = [ +tauri-build = { path = "../tauri-build/", default-features = false, version = "2.6.2" } +tauri-utils = { path = "../tauri-utils/", version = "2.9.2", features = [ "build-2", ] } diff --git a/crates/tauri/src/menu/plugin.rs b/crates/tauri/src/menu/plugin.rs index 84ab54a98d22..7f6741d00315 100644 --- a/crates/tauri/src/menu/plugin.rs +++ b/crates/tauri/src/menu/plugin.rs @@ -815,7 +815,7 @@ fn set_as_windows_menu_for_nsapp( { let resources_table = webview.resources_table(); let submenu = resources_table.get::>(rid)?; - submenu.set_as_help_menu_for_nsapp()?; + submenu.set_as_windows_menu_for_nsapp()?; } let _ = rid; diff --git a/examples/api/package.json b/examples/api/package.json index d51a35a93bc3..53780ea95741 100644 --- a/examples/api/package.json +++ b/examples/api/package.json @@ -17,7 +17,7 @@ "@iconify-json/ph": "^1.2.2", "@sveltejs/vite-plugin-svelte": "^7.0.0", "@unocss/extractor-svelte": "^66.6.6", - "svelte": "^5.53.11", + "svelte": "^5.55.7", "unocss": "^66.6.6", "vite": "^8.0.5" } diff --git a/packages/api/package.json b/packages/api/package.json index c54379d052cd..367f80a6ad4d 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -55,7 +55,7 @@ "eslint-plugin-security": "4.0.0", "fast-glob": "3.3.3", "globals": "^17.4.0", - "rollup": "4.60.2", + "rollup": "4.60.3", "tslib": "^2.8.1", "typescript": "^6.0.0", "typescript-eslint": "^8.58.2" diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 23d2df879996..6c89dbbd5c1b 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## \[2.11.2] + +### Dependencies + +- Upgraded to `tauri-cli@2.11.2` + ## \[2.11.1] ### Dependencies diff --git a/packages/cli/package.json b/packages/cli/package.json index 71e70da4653c..5830278fb99b 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@tauri-apps/cli", - "version": "2.11.1", + "version": "2.11.2", "description": "Command line interface for building Tauri apps", "type": "commonjs", "funding": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 470e1f8edcd2..c4875aff5cf5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,13 +32,13 @@ importers: version: 1.2.2 '@sveltejs/vite-plugin-svelte': specifier: ^7.0.0 - version: 7.0.0(svelte@5.53.11)(vite@8.0.5(@emnapi/core@1.8.1)(@emnapi/runtime@1.8.1)(@types/node@24.11.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)) + version: 7.0.0(svelte@5.55.7(@typescript-eslint/types@8.58.2))(vite@8.0.5(@emnapi/core@1.8.1)(@emnapi/runtime@1.8.1)(@types/node@24.11.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)) '@unocss/extractor-svelte': specifier: ^66.6.6 version: 66.6.6 svelte: - specifier: ^5.53.11 - version: 5.53.11 + specifier: ^5.55.7 + version: 5.55.7(@typescript-eslint/types@8.58.2) unocss: specifier: ^66.6.6 version: 66.6.6(@emnapi/core@1.8.1)(@emnapi/runtime@1.8.1)(vite@8.0.5(@emnapi/core@1.8.1)(@emnapi/runtime@1.8.1)(@types/node@24.11.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)) @@ -57,10 +57,10 @@ importers: version: 10.0.1(eslint@10.0.2(jiti@2.6.1)) '@rollup/plugin-terser': specifier: 1.0.0 - version: 1.0.0(rollup@4.60.2) + version: 1.0.0(rollup@4.60.3) '@rollup/plugin-typescript': specifier: 12.3.0 - version: 12.3.0(rollup@4.60.2)(tslib@2.8.1)(typescript@6.0.2) + version: 12.3.0(rollup@4.60.3)(tslib@2.8.1)(typescript@6.0.2) '@types/eslint': specifier: ^9.6.1 version: 9.6.1 @@ -83,8 +83,8 @@ importers: specifier: ^17.4.0 version: 17.4.0 rollup: - specifier: 4.60.2 - version: 4.60.2 + specifier: 4.60.3 + version: 4.60.3 tslib: specifier: ^2.8.1 version: 2.8.1 @@ -1400,141 +1400,141 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.60.2': - resolution: {integrity: sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==} + '@rollup/rollup-android-arm-eabi@4.60.3': + resolution: {integrity: sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.60.2': - resolution: {integrity: sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==} + '@rollup/rollup-android-arm64@4.60.3': + resolution: {integrity: sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.60.2': - resolution: {integrity: sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==} + '@rollup/rollup-darwin-arm64@4.60.3': + resolution: {integrity: sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.60.2': - resolution: {integrity: sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==} + '@rollup/rollup-darwin-x64@4.60.3': + resolution: {integrity: sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.60.2': - resolution: {integrity: sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==} + '@rollup/rollup-freebsd-arm64@4.60.3': + resolution: {integrity: sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.60.2': - resolution: {integrity: sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==} + '@rollup/rollup-freebsd-x64@4.60.3': + resolution: {integrity: sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.60.2': - resolution: {integrity: sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==} + '@rollup/rollup-linux-arm-gnueabihf@4.60.3': + resolution: {integrity: sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==} cpu: [arm] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm-musleabihf@4.60.2': - resolution: {integrity: sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==} + '@rollup/rollup-linux-arm-musleabihf@4.60.3': + resolution: {integrity: sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==} cpu: [arm] os: [linux] libc: [musl] - '@rollup/rollup-linux-arm64-gnu@4.60.2': - resolution: {integrity: sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==} + '@rollup/rollup-linux-arm64-gnu@4.60.3': + resolution: {integrity: sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==} cpu: [arm64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm64-musl@4.60.2': - resolution: {integrity: sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==} + '@rollup/rollup-linux-arm64-musl@4.60.3': + resolution: {integrity: sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==} cpu: [arm64] os: [linux] libc: [musl] - '@rollup/rollup-linux-loong64-gnu@4.60.2': - resolution: {integrity: sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==} + '@rollup/rollup-linux-loong64-gnu@4.60.3': + resolution: {integrity: sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==} cpu: [loong64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-loong64-musl@4.60.2': - resolution: {integrity: sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==} + '@rollup/rollup-linux-loong64-musl@4.60.3': + resolution: {integrity: sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==} cpu: [loong64] os: [linux] libc: [musl] - '@rollup/rollup-linux-ppc64-gnu@4.60.2': - resolution: {integrity: sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==} + '@rollup/rollup-linux-ppc64-gnu@4.60.3': + resolution: {integrity: sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==} cpu: [ppc64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-ppc64-musl@4.60.2': - resolution: {integrity: sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==} + '@rollup/rollup-linux-ppc64-musl@4.60.3': + resolution: {integrity: sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==} cpu: [ppc64] os: [linux] libc: [musl] - '@rollup/rollup-linux-riscv64-gnu@4.60.2': - resolution: {integrity: sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==} + '@rollup/rollup-linux-riscv64-gnu@4.60.3': + resolution: {integrity: sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==} cpu: [riscv64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-riscv64-musl@4.60.2': - resolution: {integrity: sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==} + '@rollup/rollup-linux-riscv64-musl@4.60.3': + resolution: {integrity: sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==} cpu: [riscv64] os: [linux] libc: [musl] - '@rollup/rollup-linux-s390x-gnu@4.60.2': - resolution: {integrity: sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==} + '@rollup/rollup-linux-s390x-gnu@4.60.3': + resolution: {integrity: sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==} cpu: [s390x] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.60.2': - resolution: {integrity: sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==} + '@rollup/rollup-linux-x64-gnu@4.60.3': + resolution: {integrity: sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==} cpu: [x64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-musl@4.60.2': - resolution: {integrity: sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==} + '@rollup/rollup-linux-x64-musl@4.60.3': + resolution: {integrity: sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==} cpu: [x64] os: [linux] libc: [musl] - '@rollup/rollup-openbsd-x64@4.60.2': - resolution: {integrity: sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==} + '@rollup/rollup-openbsd-x64@4.60.3': + resolution: {integrity: sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==} cpu: [x64] os: [openbsd] - '@rollup/rollup-openharmony-arm64@4.60.2': - resolution: {integrity: sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==} + '@rollup/rollup-openharmony-arm64@4.60.3': + resolution: {integrity: sha512-AaXwSvUi3QIPtroAUw1t5yHGIyqKEXwH54WUocFolZhpGDruJcs8c+xPNDRn4XiQsS7MEwnYsHW2l0MBLDMkWg==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.60.2': - resolution: {integrity: sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==} + '@rollup/rollup-win32-arm64-msvc@4.60.3': + resolution: {integrity: sha512-65LAKM/bAWDqKNEelHlcHvm2V+Vfb8C6INFxQXRHCvaVN1rJfwr4NvdP4FyzUaLqWfaCGaadf6UbTm8xJeYfEg==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.60.2': - resolution: {integrity: sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==} + '@rollup/rollup-win32-ia32-msvc@4.60.3': + resolution: {integrity: sha512-EEM2gyhBF5MFnI6vMKdX1LAosE627RGBzIoGMdLloPZkXrUN0Ckqgr2Qi8+J3zip/8NVVro3/FjB+tjhZUgUHA==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.60.2': - resolution: {integrity: sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==} + '@rollup/rollup-win32-x64-gnu@4.60.3': + resolution: {integrity: sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.60.2': - resolution: {integrity: sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==} + '@rollup/rollup-win32-x64-msvc@4.60.3': + resolution: {integrity: sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA==} cpu: [x64] os: [win32] @@ -1578,6 +1578,9 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -1895,8 +1898,8 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} - devalue@5.6.4: - resolution: {integrity: sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA==} + devalue@5.8.1: + resolution: {integrity: sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==} duplexer@0.1.2: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} @@ -1973,8 +1976,13 @@ packages: resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} engines: {node: '>=0.10'} - esrap@2.2.3: - resolution: {integrity: sha512-8fOS+GIGCQZl/ZIlhl59htOlms6U8NvX6ZYgYHpRU/b6tVSh3uHkOHZikl3D4cMbYM0JlpBe+p/BkZEi8J9XIQ==} + esrap@2.2.8: + resolution: {integrity: sha512-MPweq2EvEGj8jwOI7Hgycw/QIHzqA1EbAM8lG7p+FBfZbZq/hQ6h3AMsqnu/djzisH1KVWNzbb7LSgIVtMlPSg==} + peerDependencies: + '@typescript-eslint/types': ^8.2.0 + peerDependenciesMeta: + '@typescript-eslint/types': + optional: true esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} @@ -2387,8 +2395,8 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - rollup@4.60.2: - resolution: {integrity: sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==} + rollup@4.60.3: + resolution: {integrity: sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -2470,8 +2478,8 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - svelte@5.53.11: - resolution: {integrity: sha512-GYmqRjRhJYLQBonfdfGAt28gkfWEShrtXKGXcFGneXi502aBE+I1dJcs/YQriByvP6xqXRz/OdBGC6tfvUQHyQ==} + svelte@5.55.7: + resolution: {integrity: sha512-ymI5ykLPwIHW839E053FQbI1G+jnRFJEw3Kv5Y4njixVWywQBx+NUFpkkKyk5LIb36Fg9DVXSYpqiGekLD0hyw==} engines: {node: '>=18'} terser@5.46.0: @@ -3692,104 +3700,104 @@ snapshots: '@rolldown/pluginutils@1.0.0-rc.12': {} - '@rollup/plugin-terser@1.0.0(rollup@4.60.2)': + '@rollup/plugin-terser@1.0.0(rollup@4.60.3)': dependencies: serialize-javascript: 7.0.5 smob: 1.6.1 terser: 5.46.0 optionalDependencies: - rollup: 4.60.2 + rollup: 4.60.3 - '@rollup/plugin-typescript@12.3.0(rollup@4.60.2)(tslib@2.8.1)(typescript@6.0.2)': + '@rollup/plugin-typescript@12.3.0(rollup@4.60.3)(tslib@2.8.1)(typescript@6.0.2)': dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.60.2) + '@rollup/pluginutils': 5.3.0(rollup@4.60.3) resolve: 1.22.11 typescript: 6.0.2 optionalDependencies: - rollup: 4.60.2 + rollup: 4.60.3 tslib: 2.8.1 - '@rollup/pluginutils@5.3.0(rollup@4.60.2)': + '@rollup/pluginutils@5.3.0(rollup@4.60.3)': dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 estree-walker: 2.0.2 picomatch: 4.0.4 optionalDependencies: - rollup: 4.60.2 + rollup: 4.60.3 - '@rollup/rollup-android-arm-eabi@4.60.2': + '@rollup/rollup-android-arm-eabi@4.60.3': optional: true - '@rollup/rollup-android-arm64@4.60.2': + '@rollup/rollup-android-arm64@4.60.3': optional: true - '@rollup/rollup-darwin-arm64@4.60.2': + '@rollup/rollup-darwin-arm64@4.60.3': optional: true - '@rollup/rollup-darwin-x64@4.60.2': + '@rollup/rollup-darwin-x64@4.60.3': optional: true - '@rollup/rollup-freebsd-arm64@4.60.2': + '@rollup/rollup-freebsd-arm64@4.60.3': optional: true - '@rollup/rollup-freebsd-x64@4.60.2': + '@rollup/rollup-freebsd-x64@4.60.3': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.60.2': + '@rollup/rollup-linux-arm-gnueabihf@4.60.3': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.60.2': + '@rollup/rollup-linux-arm-musleabihf@4.60.3': optional: true - '@rollup/rollup-linux-arm64-gnu@4.60.2': + '@rollup/rollup-linux-arm64-gnu@4.60.3': optional: true - '@rollup/rollup-linux-arm64-musl@4.60.2': + '@rollup/rollup-linux-arm64-musl@4.60.3': optional: true - '@rollup/rollup-linux-loong64-gnu@4.60.2': + '@rollup/rollup-linux-loong64-gnu@4.60.3': optional: true - '@rollup/rollup-linux-loong64-musl@4.60.2': + '@rollup/rollup-linux-loong64-musl@4.60.3': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.60.2': + '@rollup/rollup-linux-ppc64-gnu@4.60.3': optional: true - '@rollup/rollup-linux-ppc64-musl@4.60.2': + '@rollup/rollup-linux-ppc64-musl@4.60.3': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.60.2': + '@rollup/rollup-linux-riscv64-gnu@4.60.3': optional: true - '@rollup/rollup-linux-riscv64-musl@4.60.2': + '@rollup/rollup-linux-riscv64-musl@4.60.3': optional: true - '@rollup/rollup-linux-s390x-gnu@4.60.2': + '@rollup/rollup-linux-s390x-gnu@4.60.3': optional: true - '@rollup/rollup-linux-x64-gnu@4.60.2': + '@rollup/rollup-linux-x64-gnu@4.60.3': optional: true - '@rollup/rollup-linux-x64-musl@4.60.2': + '@rollup/rollup-linux-x64-musl@4.60.3': optional: true - '@rollup/rollup-openbsd-x64@4.60.2': + '@rollup/rollup-openbsd-x64@4.60.3': optional: true - '@rollup/rollup-openharmony-arm64@4.60.2': + '@rollup/rollup-openharmony-arm64@4.60.3': optional: true - '@rollup/rollup-win32-arm64-msvc@4.60.2': + '@rollup/rollup-win32-arm64-msvc@4.60.3': optional: true - '@rollup/rollup-win32-ia32-msvc@4.60.2': + '@rollup/rollup-win32-ia32-msvc@4.60.3': optional: true - '@rollup/rollup-win32-x64-gnu@4.60.2': + '@rollup/rollup-win32-x64-gnu@4.60.3': optional: true - '@rollup/rollup-win32-x64-msvc@4.60.2': + '@rollup/rollup-win32-x64-msvc@4.60.3': optional: true '@sindresorhus/is@7.2.0': {} @@ -3802,12 +3810,12 @@ snapshots: dependencies: acorn: 8.16.0 - '@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.5(@emnapi/core@1.8.1)(@emnapi/runtime@1.8.1)(@types/node@24.11.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0))': + '@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.7(@typescript-eslint/types@8.58.2))(vite@8.0.5(@emnapi/core@1.8.1)(@emnapi/runtime@1.8.1)(@types/node@24.11.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0))': dependencies: deepmerge: 4.3.1 magic-string: 0.30.21 obug: 2.1.1 - svelte: 5.53.11 + svelte: 5.55.7(@typescript-eslint/types@8.58.2) vite: 8.0.5(@emnapi/core@1.8.1)(@emnapi/runtime@1.8.1)(@types/node@24.11.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0) vitefu: 1.1.2(vite@8.0.5(@emnapi/core@1.8.1)(@emnapi/runtime@1.8.1)(@types/node@24.11.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)) @@ -3832,6 +3840,8 @@ snapshots: '@types/estree@1.0.8': {} + '@types/estree@1.0.9': {} + '@types/json-schema@7.0.15': {} '@types/node@24.11.0': @@ -4211,7 +4221,7 @@ snapshots: detect-libc@2.1.2: {} - devalue@5.6.4: {} + devalue@5.8.1: {} duplexer@0.1.2: {} @@ -4267,7 +4277,7 @@ snapshots: eslint-scope@9.1.1: dependencies: '@types/esrecurse': 4.3.1 - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 esrecurse: 4.3.0 estraverse: 5.3.0 @@ -4324,9 +4334,11 @@ snapshots: dependencies: estraverse: 5.3.0 - esrap@2.2.3: + esrap@2.2.8(@typescript-eslint/types@8.58.2): dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + optionalDependencies: + '@typescript-eslint/types': 8.58.2 esrecurse@4.3.0: dependencies: @@ -4338,7 +4350,7 @@ snapshots: estree-walker@3.0.3: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 esutils@2.0.3: {} @@ -4437,7 +4449,7 @@ snapshots: is-reference@3.0.3: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 isexe@2.0.0: {} @@ -4715,35 +4727,35 @@ snapshots: - '@emnapi/core' - '@emnapi/runtime' - rollup@4.60.2: + rollup@4.60.3: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.60.2 - '@rollup/rollup-android-arm64': 4.60.2 - '@rollup/rollup-darwin-arm64': 4.60.2 - '@rollup/rollup-darwin-x64': 4.60.2 - '@rollup/rollup-freebsd-arm64': 4.60.2 - '@rollup/rollup-freebsd-x64': 4.60.2 - '@rollup/rollup-linux-arm-gnueabihf': 4.60.2 - '@rollup/rollup-linux-arm-musleabihf': 4.60.2 - '@rollup/rollup-linux-arm64-gnu': 4.60.2 - '@rollup/rollup-linux-arm64-musl': 4.60.2 - '@rollup/rollup-linux-loong64-gnu': 4.60.2 - '@rollup/rollup-linux-loong64-musl': 4.60.2 - '@rollup/rollup-linux-ppc64-gnu': 4.60.2 - '@rollup/rollup-linux-ppc64-musl': 4.60.2 - '@rollup/rollup-linux-riscv64-gnu': 4.60.2 - '@rollup/rollup-linux-riscv64-musl': 4.60.2 - '@rollup/rollup-linux-s390x-gnu': 4.60.2 - '@rollup/rollup-linux-x64-gnu': 4.60.2 - '@rollup/rollup-linux-x64-musl': 4.60.2 - '@rollup/rollup-openbsd-x64': 4.60.2 - '@rollup/rollup-openharmony-arm64': 4.60.2 - '@rollup/rollup-win32-arm64-msvc': 4.60.2 - '@rollup/rollup-win32-ia32-msvc': 4.60.2 - '@rollup/rollup-win32-x64-gnu': 4.60.2 - '@rollup/rollup-win32-x64-msvc': 4.60.2 + '@rollup/rollup-android-arm-eabi': 4.60.3 + '@rollup/rollup-android-arm64': 4.60.3 + '@rollup/rollup-darwin-arm64': 4.60.3 + '@rollup/rollup-darwin-x64': 4.60.3 + '@rollup/rollup-freebsd-arm64': 4.60.3 + '@rollup/rollup-freebsd-x64': 4.60.3 + '@rollup/rollup-linux-arm-gnueabihf': 4.60.3 + '@rollup/rollup-linux-arm-musleabihf': 4.60.3 + '@rollup/rollup-linux-arm64-gnu': 4.60.3 + '@rollup/rollup-linux-arm64-musl': 4.60.3 + '@rollup/rollup-linux-loong64-gnu': 4.60.3 + '@rollup/rollup-linux-loong64-musl': 4.60.3 + '@rollup/rollup-linux-ppc64-gnu': 4.60.3 + '@rollup/rollup-linux-ppc64-musl': 4.60.3 + '@rollup/rollup-linux-riscv64-gnu': 4.60.3 + '@rollup/rollup-linux-riscv64-musl': 4.60.3 + '@rollup/rollup-linux-s390x-gnu': 4.60.3 + '@rollup/rollup-linux-x64-gnu': 4.60.3 + '@rollup/rollup-linux-x64-musl': 4.60.3 + '@rollup/rollup-openbsd-x64': 4.60.3 + '@rollup/rollup-openharmony-arm64': 4.60.3 + '@rollup/rollup-win32-arm64-msvc': 4.60.3 + '@rollup/rollup-win32-ia32-msvc': 4.60.3 + '@rollup/rollup-win32-x64-gnu': 4.60.3 + '@rollup/rollup-win32-x64-msvc': 4.60.3 fsevents: 2.3.3 run-parallel@1.2.0: @@ -4836,24 +4848,26 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte@5.53.11: + svelte@5.55.7(@typescript-eslint/types@8.58.2): dependencies: '@jridgewell/remapping': 2.3.5 '@jridgewell/sourcemap-codec': 1.5.5 '@sveltejs/acorn-typescript': 1.0.9(acorn@8.16.0) - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 '@types/trusted-types': 2.0.7 acorn: 8.16.0 aria-query: 5.3.1 axobject-query: 4.1.0 clsx: 2.1.1 - devalue: 5.6.4 + devalue: 5.8.1 esm-env: 1.2.2 - esrap: 2.2.3 + esrap: 2.2.8(@typescript-eslint/types@8.58.2) is-reference: 3.0.3 locate-character: 3.0.0 magic-string: 0.30.21 zimmerframe: 1.1.4 + transitivePeerDependencies: + - '@typescript-eslint/types' terser@5.46.0: dependencies: @@ -4977,7 +4991,7 @@ snapshots: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 postcss: 8.5.12 - rollup: 4.60.2 + rollup: 4.60.3 tinyglobby: 0.2.16 optionalDependencies: '@types/node': 24.11.0