diff --git a/.changes/android-external-files-fix.md b/.changes/android-external-files-fix.md new file mode 100644 index 000000000..b6c2a1446 --- /dev/null +++ b/.changes/android-external-files-fix.md @@ -0,0 +1,5 @@ +--- +"tauri": patch:bug +--- + +Fixed 500 error when accessing local video files in Android external storage directory via `convertFileSrc`. Added better error handling and logging for Android external storage access to help diagnose permission and accessibility issues. \ No newline at end of file diff --git a/.changes/change-pr-13253.md b/.changes/change-pr-13253.md new file mode 100644 index 000000000..e42a66072 --- /dev/null +++ b/.changes/change-pr-13253.md @@ -0,0 +1,6 @@ +--- +"@tauri-apps/cli": patch:enhance +"tauri-cli": patch:enhance +--- + +Allow electron to run the CLI directly diff --git a/.changes/change-pr-14766.md b/.changes/change-pr-14766.md new file mode 100644 index 000000000..4fb885f88 --- /dev/null +++ b/.changes/change-pr-14766.md @@ -0,0 +1,5 @@ +--- +"tauri-macos-sign": patch:deps +--- + +Remove once-cell-regex from direct dependencies. diff --git a/.changes/empty-vec-instead-of-none.md b/.changes/empty-vec-instead-of-none.md new file mode 100644 index 000000000..6b0dd9fd9 --- /dev/null +++ b/.changes/empty-vec-instead-of-none.md @@ -0,0 +1,6 @@ +--- +"@tauri-apps/cli": patch:enhance +"tauri-cli": patch:enhance +--- + +Simplified internal representation of `features: Option>` with `Vec`, no user facing changes diff --git a/.changes/fix-android-bundle-flag.md b/.changes/fix-android-bundle-flag.md new file mode 100644 index 000000000..1fc06e69e --- /dev/null +++ b/.changes/fix-android-bundle-flag.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": patch:bug +"@tauri-apps/cli": patch:bug +--- + +Fix `android build`'s `--aab` and `--apk` flags requiring a value to be provided. diff --git a/.changes/fix-binary-patching.md b/.changes/fix-binary-patching.md new file mode 100644 index 000000000..0778d600f --- /dev/null +++ b/.changes/fix-binary-patching.md @@ -0,0 +1,8 @@ +--- +"tauri": minor:changes +"tauri-cli": minor:changes +"tauri-bundler": minor:changes +"@tauri-apps/cli": minor:changes +--- + +Change the way bundle type information is added to binary files. Instead of looking up the value of a variable we simply look for the default value. diff --git a/.changes/fix-empty-entitlements.md b/.changes/fix-empty-entitlements.md new file mode 100644 index 000000000..0bc9eea40 --- /dev/null +++ b/.changes/fix-empty-entitlements.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": patch:bug +"@tauri-apps/cli": patch:bug +--- + +Fix empty associated-domains entitlements when domains are not configured for deep links. diff --git a/.changes/fix-inspect-description.md b/.changes/fix-inspect-description.md new file mode 100644 index 000000000..df1c87db9 --- /dev/null +++ b/.changes/fix-inspect-description.md @@ -0,0 +1,6 @@ +--- +"@tauri-apps/cli": patch:bug +"tauri-cli": patch:bug +--- + +Fixed the command description for `tauri inspect` diff --git a/.changes/only-watch-dependencies.md b/.changes/only-watch-dependencies.md new file mode 100644 index 000000000..e638cb591 --- /dev/null +++ b/.changes/only-watch-dependencies.md @@ -0,0 +1,6 @@ +--- +"@tauri-apps/cli": patch:bug +"tauri-cli": patch:bug +--- + +Only watch dependent workspace members when running `tauri dev` instead of watching on all members diff --git a/.changes/reduce-internal-statics.md b/.changes/reduce-internal-statics.md new file mode 100644 index 000000000..bc20780b1 --- /dev/null +++ b/.changes/reduce-internal-statics.md @@ -0,0 +1,6 @@ +--- +"@tauri-apps/cli": patch:changes +"tauri-cli": patch:changes +--- + +Refactored internal use of static on config and directory resolvings, no user facing changes, please report any regressions if you encounter any diff --git a/.changes/runtime-bsd.md b/.changes/runtime-bsd.md new file mode 100644 index 000000000..c9c97b4c4 --- /dev/null +++ b/.changes/runtime-bsd.md @@ -0,0 +1,6 @@ +--- +tauri-runtime: patch:bug +tauri-runtime-wry: patch:bug +--- + +Fix compilation errors when targeting BSD. diff --git a/.changes/signing-env-vars.md b/.changes/signing-env-vars.md new file mode 100644 index 000000000..c164c3b9a --- /dev/null +++ b/.changes/signing-env-vars.md @@ -0,0 +1,14 @@ +--- +"tauri-cli": patch:enhance +"@tauri-apps/cli": patch:enhance +--- + +Added new environment variables for `tauri signer sign` command, to align with existing environment variables used in `tauri build`, `tauri bundle` and `tauri signer generate` +- `TAURI_SIGNING_PRIVATE_KEY` +- `TAURI_SIGNING_PRIVATE_KEY_PATH` +- `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` + +The old environment variables are deprecated and will be removed in a future release. +- `TAURI_PRIVATE_KEY` +- `TAURI_PRIVATE_KEY_PATH` +- `TAURI_PRIVATE_KEY_PASSWORD` diff --git a/.changes/updater-signer-files-without-extension.md b/.changes/updater-signer-files-without-extension.md new file mode 100644 index 000000000..1ff41edbd --- /dev/null +++ b/.changes/updater-signer-files-without-extension.md @@ -0,0 +1,6 @@ +--- +tauri-cli: patch:bug +"@tauri-apps/cli": patch:bug +--- + +`tauri signer sign` doesn't work for files without an extension diff --git a/.changes/webkitgtk202.md b/.changes/webkitgtk202.md new file mode 100644 index 000000000..739ce1ebd --- /dev/null +++ b/.changes/webkitgtk202.md @@ -0,0 +1,7 @@ +--- +tauri-runtime-wry: minor:deps +tauri-runtime: minor:deps +tauri: minor:deps +--- + +**Breaking Change** for `with_webview` users: Updated webkit2gtk-rs crates to `v2.0.2`. diff --git a/.changes/webview-set-simple-fullscreen.md b/.changes/webview-set-simple-fullscreen.md new file mode 100644 index 000000000..ba0cdf8bf --- /dev/null +++ b/.changes/webview-set-simple-fullscreen.md @@ -0,0 +1,7 @@ +--- +'tauri': 'minor:feat' +--- + +Add `set_simple_fullscreen` method to `WebviewWindow`. + +This method was already available on the `Window` type and is now also available on `WebviewWindow` for consistency. On macOS, it toggles fullscreen mode without creating a new macOS Space. On other platforms, it falls back to regular fullscreen. diff --git a/.changes/wry-054.md b/.changes/wry-054.md new file mode 100644 index 000000000..9e4a0cdbe --- /dev/null +++ b/.changes/wry-054.md @@ -0,0 +1,6 @@ +--- +tauri-runtime-wry: patch:deps +tauri: patch:deps +--- + +Update wry to `v0.54`. \ No newline at end of file diff --git a/.github/workflows/publish-cli-rs.yml b/.github/workflows/publish-cli-rs.yml index a6471161c..39887eec7 100644 --- a/.github/workflows/publish-cli-rs.yml +++ b/.github/workflows/publish-cli-rs.yml @@ -38,35 +38,62 @@ jobs: rust_target: aarch64-pc-windows-msvc ext: '.exe' args: '' + - os: ubuntu-22.04 + rust_target: riscv64gc-unknown-linux-gnu + ext: '' + args: '' + cross: true steps: - uses: actions/checkout@v4 - name: 'Setup Rust' + if: ${{ !matrix.config.cross }} uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.config.rust_target }} - uses: Swatinem/rust-cache@v2 + if: ${{ !matrix.config.cross }} with: key: ${{ matrix.config.rust_target }} - name: install Linux dependencies - if: matrix.config.os == 'ubuntu-latest' + if: ${{ !matrix.config.cross && startsWith(matrix.config.os, 'ubuntu') }} run: | sudo apt-get update sudo apt-get install -y libgtk-3-dev + - name: Install cross + if: ${{ matrix.config.cross }} + uses: taiki-e/install-action@v2 + with: + tool: cross@0.2.5 + - name: Build CLI + if: ${{ !matrix.config.cross }} run: cargo build --manifest-path ./crates/tauri-cli/Cargo.toml --profile release-size-optimized ${{ matrix.config.args }} + - name: Build CLI (cross) + if: ${{ matrix.config.cross }} + run: cross build --manifest-path ./crates/tauri-cli/Cargo.toml --target ${{ matrix.config.rust_target }} --profile release-size-optimized ${{ matrix.config.args }} + - name: Upload CLI + if: ${{ !matrix.config.cross }} uses: actions/upload-artifact@v4 with: name: cargo-tauri-${{ matrix.config.rust_target }}${{ matrix.config.ext }} path: target/release-size-optimized/cargo-tauri${{ matrix.config.ext }} if-no-files-found: error + - name: Upload CLI (cross) + if: ${{ matrix.config.cross }} + uses: actions/upload-artifact@v4 + with: + name: cargo-tauri-${{ matrix.config.rust_target }}${{ matrix.config.ext }} + path: target/${{ matrix.config.rust_target }}/release-size-optimized/cargo-tauri${{ matrix.config.ext }} + if-no-files-found: error + upload: needs: build runs-on: ubuntu-latest diff --git a/.taurignore b/.taurignore deleted file mode 100644 index 6e49b1ea8..000000000 --- a/.taurignore +++ /dev/null @@ -1,16 +0,0 @@ -.changes -.devcontainer -.docker -.github -.scripts -.vscode -audits -bench -packages/api -packages/cli -crates/tauri-cli -crates/tauri-bundler -crates/tauri-driver -crates/tauri-macos-sign -crates/tauri-schema-generator -crates/tests diff --git a/Cargo.lock b/Cargo.lock index 50bead4b4..2df411247 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -300,7 +300,7 @@ dependencies = [ "ring", "rsa", "scroll", - "security-framework", + "security-framework 2.11.1", "security-framework-sys", "semver", "serde", @@ -567,7 +567,7 @@ dependencies = [ "http 1.3.1", "http-body 1.0.1", "http-body-util", - "hyper 1.5.2", + "hyper 1.8.1", "hyper-util", "itoa", "matchit 0.8.4", @@ -1152,10 +1152,11 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.22" +version = "1.2.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32db95edf998450acc7881c932f94cd9b05c87b4b2599e8bab064753da4acfd1" +checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -2333,11 +2334,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "erased-serde" -version = "0.4.5" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24e2389d65ab4fab27dc2a5de7b191e1f6617d1f1c8855c0dc569c94a4cbb18d" +checksum = "89e8918065695684b2b0702da20382d5ae6065cf3327bc2d6436bd49a71ce9f3" dependencies = [ "serde", + "serde_core", "typeid", ] @@ -2468,6 +2470,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" + [[package]] name = "flate2" version = "1.1.1" @@ -2853,10 +2861,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", - "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "wasm-bindgen", ] [[package]] @@ -3347,7 +3353,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2", + "socket2 0.5.8", "tokio", "tower-service", "tracing", @@ -3356,13 +3362,14 @@ dependencies = [ [[package]] name = "hyper" -version = "1.5.2" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", + "futures-core", "h2 0.4.7", "http 1.3.1", "http-body 1.0.1", @@ -3370,6 +3377,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", + "pin-utils", "smallvec", "tokio", "want", @@ -3397,14 +3405,13 @@ checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" dependencies = [ "futures-util", "http 1.3.1", - "hyper 1.5.2", + "hyper 1.8.1", "hyper-util", - "rustls 0.23.20", + "rustls 0.23.35", "rustls-pki-types", "tokio", "tokio-rustls 0.26.1", "tower-service", - "webpki-roots 0.26.7", ] [[package]] @@ -3415,7 +3422,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.5.2", + "hyper 1.8.1", "hyper-util", "native-tls", "tokio", @@ -3425,18 +3432,23 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.10" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" dependencies = [ + "base64 0.22.1", "bytes", "futures-channel", + "futures-core", "futures-util", "http 1.3.1", "http-body 1.0.1", - "hyper 1.5.2", + "hyper 1.8.1", + "ipnet", + "libc", + "percent-encoding", "pin-project-lite", - "socket2", + "socket2 0.6.1", "tokio", "tower-service", "tracing", @@ -3817,6 +3829,16 @@ version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" +[[package]] +name = "iri-string" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "is-terminal" version = "0.4.13" @@ -3916,20 +3938,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "jni" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" -dependencies = [ - "cesu8", - "combine", - "jni-sys", - "log", - "thiserror 1.0.69", - "walkdir", -] - [[package]] name = "jni" version = "0.21.1" @@ -4077,7 +4085,7 @@ dependencies = [ "http 1.3.1", "http-body 1.0.1", "http-body-util", - "hyper 1.5.2", + "hyper 1.8.1", "hyper-util", "jsonrpsee-core", "jsonrpsee-types", @@ -4140,7 +4148,6 @@ dependencies = [ "referencing", "regex", "regex-syntax", - "reqwest 0.12.12", "serde", "serde_json", "uuid-simd", @@ -4289,9 +4296,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.169" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "libfuzzer-sys" @@ -4428,9 +4435,9 @@ checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" [[package]] name = "log" -version = "0.4.22" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" dependencies = [ "value-bag", ] @@ -4720,10 +4727,10 @@ dependencies = [ "libc", "log", "openssl", - "openssl-probe", + "openssl-probe 0.1.5", "openssl-sys", "schannel", - "security-framework", + "security-framework 2.11.1", "security-framework-sys", "tempfile", ] @@ -5353,6 +5360,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "openssl-probe" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391" + [[package]] name = "openssl-src" version = "300.4.1+3.4.0" @@ -6356,58 +6369,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "quinn" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" -dependencies = [ - "bytes", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls 0.23.20", - "socket2", - "thiserror 2.0.12", - "tokio", - "tracing", -] - -[[package]] -name = "quinn-proto" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" -dependencies = [ - "bytes", - "getrandom 0.2.15", - "rand 0.8.5", - "ring", - "rustc-hash", - "rustls 0.23.20", - "rustls-pki-types", - "slab", - "thiserror 2.0.12", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c40286217b4ba3a71d644d752e6a0b71f13f1b6a2c5311acfcbe0c2418ed904" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2", - "tracing", - "windows-sys 0.59.0", -] - [[package]] name = "quote" version = "1.0.38" @@ -6818,51 +6779,44 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.12" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" +checksum = "04e9018c9d814e5f30cc16a0f03271aeab3571e609612d9fe78c1aa8d11c2f62" dependencies = [ "base64 0.22.1", "bytes", - "futures-channel", "futures-core", "futures-util", "http 1.3.1", "http-body 1.0.1", "http-body-util", - "hyper 1.5.2", + "hyper 1.8.1", "hyper-rustls 0.27.5", "hyper-tls", "hyper-util", - "ipnet", "js-sys", "log", - "mime", "native-tls", - "once_cell", "percent-encoding", "pin-project-lite", - "quinn", - "rustls 0.23.20", - "rustls-pemfile 2.2.0", + "rustls 0.23.35", "rustls-pki-types", + "rustls-platform-verifier", "serde", "serde_json", - "serde_urlencoded", "sync_wrapper 1.0.2", "tokio", "tokio-native-tls", "tokio-rustls 0.26.1", "tokio-util", "tower 0.5.2", + "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 0.26.7", - "windows-registry 0.2.0", ] [[package]] @@ -6944,9 +6898,9 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.7.45" +version = "0.7.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" +checksum = "2297bf9c81a3f0dc96bc9521370b88f054168c29826a75e89c55ff196e7ed6a1" dependencies = [ "bitvec", "bytecheck", @@ -6962,9 +6916,9 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.7.45" +version = "0.7.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" +checksum = "84d7b42d4b8d06048d3ac8db0eb31bcb942cbeb709f0b5f2b2ebde398d3038f5" dependencies = [ "proc-macro2", "quote", @@ -7026,9 +6980,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.7" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ "const-oid", "digest", @@ -7168,15 +7122,15 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.20" +version = "0.23.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b" +checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" dependencies = [ "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.102.8", + "rustls-webpki 0.103.8", "subtle", "zeroize", ] @@ -7187,10 +7141,10 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" dependencies = [ - "openssl-probe", + "openssl-probe 0.1.5", "rustls-pemfile 1.0.4", "schannel", - "security-framework", + "security-framework 2.11.1", ] [[package]] @@ -7199,11 +7153,23 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" dependencies = [ - "openssl-probe", + "openssl-probe 0.1.5", "rustls-pemfile 2.2.0", "rustls-pki-types", "schannel", - "security-framework", + "security-framework 2.11.1", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe 0.2.0", + "rustls-pki-types", + "schannel", + "security-framework 3.5.1", ] [[package]] @@ -7226,32 +7192,32 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.10.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" +checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" dependencies = [ - "web-time", + "zeroize", ] [[package]] name = "rustls-platform-verifier" -version = "0.3.4" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afbb878bdfdf63a336a5e63561b1835e7a8c91524f51621db870169eac84b490" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" dependencies = [ - "core-foundation 0.9.4", + "core-foundation 0.10.0", "core-foundation-sys", - "jni 0.19.0", + "jni", "log", "once_cell", - "rustls 0.23.20", - "rustls-native-certs 0.7.3", + "rustls 0.23.35", + "rustls-native-certs 0.8.3", "rustls-platform-verifier-android", - "rustls-webpki 0.102.8", - "security-framework", + "rustls-webpki 0.103.8", + "security-framework 3.5.1", "security-framework-sys", - "webpki-roots 0.26.7", - "winapi", + "webpki-root-certs", + "windows-sys 0.60.2", ] [[package]] @@ -7281,6 +7247,17 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustls-webpki" +version = "0.103.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.19" @@ -7479,15 +7456,27 @@ dependencies = [ "core-foundation 0.9.4", "core-foundation-sys", "libc", - "num-bigint", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +dependencies = [ + "bitflags 2.7.0", + "core-foundation 0.10.0", + "core-foundation-sys", + "libc", "security-framework-sys", ] [[package]] name = "security-framework-sys" -version = "2.14.0" +version = "2.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" dependencies = [ "core-foundation-sys", "libc", @@ -7864,9 +7853,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook" -version = "0.3.17" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +checksum = "2a37d01603c37b5466f808de79f845c7116049b0579adb70a6b7d47c1fa3a952" dependencies = [ "libc", "signal-hook-registry", @@ -7883,9 +7872,9 @@ dependencies = [ [[package]] name = "signal-hook-tokio" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213241f76fb1e37e27de3b6aa1b068a2c333233b59cca6634f634b80a27ecf1e" +checksum = "e513e435a8898a0002270f29d0a708b7879708fb5c4d00e46983ca2d2d378cf0" dependencies = [ "futures-core", "libc", @@ -8020,6 +8009,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "socket2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + [[package]] name = "socks" version = "0.3.4" @@ -8446,7 +8445,7 @@ dependencies = [ "gdkwayland-sys", "gdkx11-sys", "gtk", - "jni 0.21.1", + "jni", "lazy_static", "libc", "log", @@ -8522,7 +8521,7 @@ dependencies = [ "http 1.3.1", "http-range", "image", - "jni 0.21.1", + "jni", "libc", "log", "mime", @@ -8538,7 +8537,8 @@ dependencies = [ "quickcheck", "quickcheck_macros", "raw-window-handle", - "reqwest 0.12.12", + "reqwest 0.13.1", + "rustls 0.23.35", "serde", "serde_json", "serde_repr", @@ -8626,7 +8626,7 @@ dependencies = [ "uuid", "walkdir", "which", - "windows-registry 0.5.0", + "windows-registry", "windows-sys 0.60.2", "zip 4.0.0", ] @@ -8758,7 +8758,7 @@ dependencies = [ "futures", "futures-util", "http-body-util", - "hyper 1.5.2", + "hyper 1.8.1", "hyper-util", "pico-args", "serde", @@ -8799,11 +8799,12 @@ dependencies = [ "chrono", "dirs 6.0.0", "log", - "once-cell-regex", + "once_cell", "os_pipe", "p12", "plist", "rand 0.9.1", + "regex", "serde", "serde_json", "tempfile", @@ -8879,7 +8880,7 @@ dependencies = [ "dpi", "gtk", "http 1.3.1", - "jni 0.21.1", + "jni", "objc2 0.6.0", "objc2-ui-kit", "objc2-web-kit", @@ -8900,7 +8901,7 @@ version = "2.9.3" dependencies = [ "gtk", "http 1.3.1", - "jni 0.21.1", + "jni", "log", "objc2 0.6.0", "objc2-app-kit", @@ -8978,6 +8979,7 @@ dependencies = [ "serial_test", "serialize-to-javascript", "swift-rs", + "tauri", "thiserror 2.0.12", "toml 0.9.10+spec-1.1.0", "url", @@ -9225,7 +9227,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.8", "tokio-macros", "windows-sys 0.52.0", ] @@ -9267,7 +9269,7 @@ version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" dependencies = [ - "rustls 0.23.20", + "rustls 0.23.35", "tokio", ] @@ -9450,6 +9452,24 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags 2.7.0", + "bytes", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "iri-string", + "pin-project-lite", + "tower 0.5.2", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.3" @@ -9789,33 +9809,31 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "3.0.3" +version = "3.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "217751151c53226090391713e533d9a5e904ba2570dabaaace29032687589c3e" +checksum = "d39cb1dbab692d82a977c0392ffac19e188bd9186a9f32806f0aaa859d75585a" dependencies = [ "base64 0.22.1", - "cc", "der", "flate2", "log", "native-tls", "percent-encoding", - "rustls 0.23.20", - "rustls-pemfile 2.2.0", + "rustls 0.23.35", "rustls-pki-types", "rustls-platform-verifier", "socks", "ureq-proto", "utf-8", "webpki-root-certs", - "webpki-roots 0.26.7", + "webpki-roots 1.0.5", ] [[package]] name = "ureq-proto" -version = "0.3.0" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c51fe73e1d8c4e06bb2698286f7e7453c6fc90528d6d2e7fc36bb4e87fe09b1" +checksum = "d81f9efa9df032be5934a46a068815a10a042b494b6a58cb0a1a97bb5467ed6f" dependencies = [ "base64 0.22.1", "http 1.3.1", @@ -9939,9 +9957,9 @@ dependencies = [ [[package]] name = "value-bag" -version = "1.10.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ef4c4aa54d5d05a279399bfa921ec387b7aba77caf7a682ae8d86785b8fdad2" +checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" dependencies = [ "value-bag-serde1", "value-bag-sval2", @@ -9949,20 +9967,20 @@ dependencies = [ [[package]] name = "value-bag-serde1" -version = "1.10.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb773bd36fd59c7ca6e336c94454d9c66386416734817927ac93d81cb3c5b0b" +checksum = "16530907bfe2999a1773ca5900a65101e092c70f642f25cc23ca0c43573262c5" dependencies = [ "erased-serde", - "serde", + "serde_core", "serde_fmt", ] [[package]] name = "value-bag-sval2" -version = "1.10.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53a916a702cac43a88694c97657d449775667bcd14b70419441d05b7fea4a83a" +checksum = "d00ae130edd690eaa877e4f40605d534790d1cf1d651e7685bd6a144521b251f" dependencies = [ "sval", "sval_buffer", @@ -10153,21 +10171,11 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "webkit2gtk" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76b1bc1e54c581da1e9f179d0b38512ba358fb1af2d634a1affe42e37172361a" +checksum = "a1027150013530fb2eaf806408df88461ae4815a45c541c8975e61d6f2fc4793" dependencies = [ "bitflags 1.3.2", "cairo-rs", @@ -10189,9 +10197,9 @@ dependencies = [ [[package]] name = "webkit2gtk-sys" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62daa38afc514d1f8f12b8693d30d5993ff77ced33ce30cd04deebc267a6d57c" +checksum = "916a5f65c2ef0dfe12fff695960a2ec3d4565359fdbb2e9943c974e06c734ea5" dependencies = [ "bitflags 1.3.2", "cairo-sys-rs", @@ -10209,9 +10217,9 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "0.26.7" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cd5da49bdf1f30054cfe0b8ce2958b8fbeb67c4d82c8967a598af481bef255c" +checksum = "36a29fc0408b113f68cf32637857ab740edfafdf460c326cd2afaa2d84cc05dc" dependencies = [ "rustls-pki-types", ] @@ -10224,9 +10232,9 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "webpki-roots" -version = "0.26.7" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" +checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" dependencies = [ "rustls-pki-types", ] @@ -10396,7 +10404,7 @@ dependencies = [ "windows-implement", "windows-interface", "windows-link", - "windows-result 0.3.2", + "windows-result", "windows-strings 0.4.0", ] @@ -10448,17 +10456,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-registry" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" -dependencies = [ - "windows-result 0.2.0", - "windows-strings 0.1.0", - "windows-targets 0.52.6", -] - [[package]] name = "windows-registry" version = "0.5.0" @@ -10466,19 +10463,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c44a98275e31bfd112bb06ba96c8ab13c03383a3753fdddd715406a1824c7e0" dependencies = [ "windows-link", - "windows-result 0.3.2", + "windows-result", "windows-strings 0.3.1", ] -[[package]] -name = "windows-result" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-result" version = "0.3.2" @@ -10488,16 +10476,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-strings" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" -dependencies = [ - "windows-result 0.2.0", - "windows-targets 0.52.6", -] - [[package]] name = "windows-strings" version = "0.3.1" @@ -10938,9 +10916,9 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "wry" -version = "0.53.4" +version = "0.54.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d78ec082b80fa088569a970d043bb3050abaabf4454101d44514ee8d9a8c9f6" +checksum = "e456eeaf7f09413fdc16799782879b2b9f1d264dfdbce4cf7e924df0ef36afb9" dependencies = [ "base64 0.22.1", "block2 0.6.0", @@ -10954,7 +10932,7 @@ dependencies = [ "html5ever", "http 1.3.1", "javascriptcore-rs", - "jni 0.21.1", + "jni", "kuchikiki", "libc", "ndk", diff --git a/crates/tauri-bundler/src/bundle.rs b/crates/tauri-bundler/src/bundle.rs index bac77c041..042a3dfb2 100644 --- a/crates/tauri-bundler/src/bundle.rs +++ b/crates/tauri-bundler/src/bundle.rs @@ -4,6 +4,8 @@ // SPDX-License-Identifier: MIT mod category; +#[cfg(any(target_os = "linux", target_os = "windows"))] +mod kmp; #[cfg(target_os = "linux")] mod linux; #[cfg(target_os = "macos")] @@ -15,29 +17,46 @@ mod windows; use tauri_utils::{display_path, platform::Target as TargetPlatform}; +#[cfg(any(target_os = "linux", target_os = "windows"))] +const BUNDLE_VAR_TOKEN: &[u8] = b"__TAURI_BUNDLE_TYPE_VAR_UNK"; /// Patch a binary with bundle type information +#[cfg(any(target_os = "linux", target_os = "windows"))] fn patch_binary(binary: &PathBuf, package_type: &PackageType) -> crate::Result<()> { - match package_type { - #[cfg(target_os = "linux")] - PackageType::AppImage | PackageType::Deb | PackageType::Rpm => { - log::info!( - "Patching binary {:?} for type {}", - binary, - package_type.short_name() - ); - linux::patch_binary(binary, package_type)?; - } - PackageType::Nsis | PackageType::WindowsMsi => { - log::info!( - "Patching binary {:?} for type {}", - binary, - package_type.short_name() - ); - windows::patch_binary(binary, package_type)?; - } - _ => (), - } + let mut file_data = std::fs::read(binary).expect("Could not read binary file."); + if let Some(bundle_var_index) = kmp::index_of(BUNDLE_VAR_TOKEN, &file_data) { + #[cfg(target_os = "linux")] + let bundle_type = match package_type { + crate::PackageType::Deb => b"__TAURI_BUNDLE_TYPE_VAR_DEB", + crate::PackageType::Rpm => b"__TAURI_BUNDLE_TYPE_VAR_RPM", + crate::PackageType::AppImage => b"__TAURI_BUNDLE_TYPE_VAR_APP", + _ => { + return Err(crate::Error::InvalidPackageType( + package_type.short_name().to_owned(), + "Linux".to_owned(), + )) + } + }; + #[cfg(target_os = "windows")] + let bundle_type = match package_type { + crate::PackageType::Nsis => b"__TAURI_BUNDLE_TYPE_VAR_NSS", + crate::PackageType::WindowsMsi => b"__TAURI_BUNDLE_TYPE_VAR_MSI", + _ => { + return Err(crate::Error::InvalidPackageType( + package_type.short_name().to_owned(), + "Windows".to_owned(), + )) + } + }; + + file_data[bundle_var_index..bundle_var_index + BUNDLE_VAR_TOKEN.len()] + .copy_from_slice(bundle_type); + + std::fs::write(binary, &file_data) + .map_err(|e| crate::Error::BinaryWriteError(e.to_string()))?; + } else { + return Err(crate::Error::MissingBundleTypeVar); + } Ok(()) } @@ -92,22 +111,17 @@ pub fn bundle_project(settings: &Settings) -> crate::Result> { .expect("Main binary missing in settings"); let main_binary_path = settings.binary_path(main_binary); - // When packaging multiple binary types, we make a copy of the unsigned main_binary so that we can - // restore it after each package_type step. This avoids two issues: + // We make a copy of the unsigned main_binary so that we can restore it after each package_type step. + // This allows us to patch the binary correctly and avoids two issues: // - modifying a signed binary without updating its PE checksum can break signature verification // - 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.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)?; - std::io::copy(&mut unsigned_main_binary, &mut unsigned_main_binary_copy)?; - } + // TODO: change this to work on a copy while preserving the main binary unchanged + let mut main_binary_copy = tempfile::tempfile()?; + let mut main_binary_orignal = std::fs::File::open(&main_binary_path)?; + std::io::copy(&mut main_binary_orignal, &mut main_binary_copy)?; - let mut main_binary_signed = false; let mut bundles = Vec::::new(); for package_type in &package_types { // bundle was already built! e.g. DMG already built .app @@ -115,22 +129,14 @@ pub fn bundle_project(settings: &Settings) -> crate::Result> { continue; } + #[cfg(any(target_os = "linux", target_os = "windows"))] if let Err(e) = patch_binary(&main_binary_path, package_type) { log::warn!("Failed to add bundler type to the binary: {e}. Updater plugin may not be able to update this package. This shouldn't normally happen, please report it to https://github.com/tauri-apps/tauri/issues"); } // sign main binary for every package type after patch 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) - .truncate(true) - .open(&main_binary_path)?; - unsigned_main_binary_copy.seek(SeekFrom::Start(0))?; - std::io::copy(&mut unsigned_main_binary_copy, &mut signed_main_binary)?; - } windows::sign::try_sign(&main_binary_path, settings)?; - main_binary_signed = true; } let bundle_paths = match package_type { @@ -172,6 +178,14 @@ pub fn bundle_project(settings: &Settings) -> crate::Result> { package_type: package_type.to_owned(), bundle_paths, }); + + // Restore unsigned and unpatched binary + let mut modified_main_binary = std::fs::OpenOptions::new() + .write(true) + .truncate(true) + .open(&main_binary_path)?; + main_binary_copy.seek(SeekFrom::Start(0))?; + std::io::copy(&mut main_binary_copy, &mut modified_main_binary)?; } if let Some(updater) = settings.updater() { diff --git a/crates/tauri-bundler/src/bundle/kmp/mod.rs b/crates/tauri-bundler/src/bundle/kmp/mod.rs new file mode 100644 index 000000000..3e8489023 --- /dev/null +++ b/crates/tauri-bundler/src/bundle/kmp/mod.rs @@ -0,0 +1,61 @@ +// Copyright 2016-2019 Cargo-Bundle developers +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +// Knuth–Morris–Pratt algorithm +// based on https://github.com/howeih/rust_kmp +#[cfg(any(target_os = "linux", target_os = "windows"))] +pub fn index_of(pattern: &[u8], target: &[u8]) -> Option { + let failure_function = find_failure_function(pattern); + + let mut t_i: usize = 0; + let mut p_i: usize = 0; + let target_len = target.len(); + let mut result_idx = None; + let pattern_len = pattern.len(); + + while (t_i < target_len) && (p_i < pattern_len) { + if target[t_i] == pattern[p_i] { + if result_idx.is_none() { + result_idx.replace(t_i); + } + t_i += 1; + p_i += 1; + if p_i >= pattern_len { + return result_idx; + } + } else { + if p_i == 0 { + p_i = 0; + t_i += 1; + } else { + p_i = failure_function[p_i - 1]; + } + result_idx = None; + } + } + None +} + +#[cfg(any(target_os = "linux", target_os = "windows"))] +fn find_failure_function(pattern: &[u8]) -> Vec { + let mut i = 1; + let mut j = 0; + let pattern_length = pattern.len(); + let end_i = pattern_length - 1; + let mut failure_function = vec![0usize; pattern_length]; + while i <= end_i { + if pattern[i] == pattern[j] { + failure_function[i] = j + 1; + i += 1; + j += 1; + } else if j == 0 { + failure_function[i] = 0; + i += 1; + } else { + j = failure_function[j - 1]; + } + } + failure_function +} diff --git a/crates/tauri-bundler/src/bundle/linux/mod.rs b/crates/tauri-bundler/src/bundle/linux/mod.rs index ba66a8d39..8459b0528 100644 --- a/crates/tauri-bundler/src/bundle/linux/mod.rs +++ b/crates/tauri-bundler/src/bundle/linux/mod.rs @@ -7,8 +7,3 @@ pub mod appimage; pub mod debian; pub mod freedesktop; pub mod rpm; - -mod util; - -#[cfg(target_os = "linux")] -pub use util::patch_binary; diff --git a/crates/tauri-bundler/src/bundle/linux/util.rs b/crates/tauri-bundler/src/bundle/linux/util.rs deleted file mode 100644 index be1256295..000000000 --- a/crates/tauri-bundler/src/bundle/linux/util.rs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2019-2024 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -/// Change value of __TAURI_BUNDLE_TYPE static variable to mark which package type it was bundled in -#[cfg(target_os = "linux")] -pub fn patch_binary( - binary_path: &std::path::PathBuf, - package_type: &crate::PackageType, -) -> crate::Result<()> { - let mut file_data = std::fs::read(binary_path).expect("Could not read binary file."); - - let elf = match goblin::Object::parse(&file_data)? { - goblin::Object::Elf(elf) => elf, - _ => return Err(crate::Error::GenericError("Not an ELF file".to_owned())), - }; - - let offset = find_bundle_type_symbol(elf).ok_or(crate::Error::MissingBundleTypeVar)?; - let offset = offset as usize; - if offset + 3 <= file_data.len() { - let chars = &mut file_data[offset..offset + 3]; - match package_type { - crate::PackageType::Deb => chars.copy_from_slice(b"DEB"), - crate::PackageType::Rpm => chars.copy_from_slice(b"RPM"), - crate::PackageType::AppImage => chars.copy_from_slice(b"APP"), - _ => { - return Err(crate::Error::InvalidPackageType( - package_type.short_name().to_owned(), - "linux".to_owned(), - )) - } - } - - std::fs::write(binary_path, &file_data) - .map_err(|error| crate::Error::BinaryWriteError(error.to_string()))?; - } else { - return Err(crate::Error::BinaryOffsetOutOfRange); - } - - Ok(()) -} - -/// Find address of a symbol in relocations table -#[cfg(target_os = "linux")] -fn find_bundle_type_symbol(elf: goblin::elf::Elf<'_>) -> Option { - for sym in elf.syms.iter() { - if let Some(name) = elf.strtab.get_at(sym.st_name) { - if name == "__TAURI_BUNDLE_TYPE" { - for reloc in elf.dynrelas.iter() { - if reloc.r_offset == sym.st_value { - return Some(reloc.r_addend.unwrap()); - } - } - } - } - } - - None -} diff --git a/crates/tauri-bundler/src/bundle/macos/sign.rs b/crates/tauri-bundler/src/bundle/macos/sign.rs index 8d8bf6c2b..acde62708 100644 --- a/crates/tauri-bundler/src/bundle/macos/sign.rs +++ b/crates/tauri-bundler/src/bundle/macos/sign.rs @@ -21,7 +21,7 @@ pub fn keychain(identity: Option<&str>) -> crate::Result>, - /// the list of of RPM dependencies your application recommends. + /// the list of RPM dependencies your application recommends. pub recommends: Option>, /// The list of RPM dependencies your application provides. pub provides: Option>, diff --git a/crates/tauri-bundler/src/bundle/windows/mod.rs b/crates/tauri-bundler/src/bundle/windows/mod.rs index 366e000e1..b92fb5f56 100644 --- a/crates/tauri-bundler/src/bundle/windows/mod.rs +++ b/crates/tauri-bundler/src/bundle/windows/mod.rs @@ -14,5 +14,3 @@ pub use util::{ NSIS_OUTPUT_FOLDER_NAME, NSIS_UPDATER_OUTPUT_FOLDER_NAME, WIX_OUTPUT_FOLDER_NAME, WIX_UPDATER_OUTPUT_FOLDER_NAME, }; - -pub use util::patch_binary; diff --git a/crates/tauri-bundler/src/bundle/windows/util.rs b/crates/tauri-bundler/src/bundle/windows/util.rs index 3685d0c06..55cfea3a9 100644 --- a/crates/tauri-bundler/src/bundle/windows/util.rs +++ b/crates/tauri-bundler/src/bundle/windows/util.rs @@ -77,75 +77,3 @@ pub fn os_bitness<'a>() -> Option<&'a str> { _ => None, } } - -pub fn patch_binary(binary_path: &PathBuf, package_type: &crate::PackageType) -> crate::Result<()> { - let mut file_data = std::fs::read(binary_path)?; - - let pe = match goblin::Object::parse(&file_data)? { - goblin::Object::PE(pe) => pe, - _ => { - return Err(crate::Error::BinaryParseError( - std::io::Error::new(std::io::ErrorKind::InvalidInput, "binary is not a PE file").into(), - )); - } - }; - - let tauri_bundle_section = pe - .sections - .iter() - .find(|s| s.name().unwrap_or_default() == ".taubndl") - .ok_or(crate::Error::MissingBundleTypeVar)?; - - let data_offset = tauri_bundle_section.pointer_to_raw_data as usize; - 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 - .iter() - .find(|s| s.name().unwrap_or_default() == ".rdata") - .ok_or_else(|| { - crate::Error::BinaryParseError( - std::io::Error::new(std::io::ErrorKind::InvalidInput, ".rdata section not found").into(), - ) - })?; - - let rva = ptr_value.checked_sub(pe.image_base as u64).ok_or_else(|| { - crate::Error::BinaryParseError( - std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid RVA offset").into(), - ) - })?; - - // see "Relative virtual address (RVA)" for explanation of offset arithmetic here: - // https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#general-concepts - let file_offset = rdata_section.pointer_to_raw_data as usize - + (rva as usize).saturating_sub(rdata_section.virtual_address as usize); - - // Overwrite the string at that offset - 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"), - _ => { - return Err(crate::Error::InvalidPackageType( - package_type.short_name().to_owned(), - "windows".to_owned(), - )); - } - } - - std::fs::write(binary_path, &file_data) - .map_err(|e| crate::Error::BinaryWriteError(e.to_string()))?; - - Ok(()) -} diff --git a/crates/tauri-bundler/src/error.rs b/crates/tauri-bundler/src/error.rs index 518cb6ca9..40547a3ee 100644 --- a/crates/tauri-bundler/src/error.rs +++ b/crates/tauri-bundler/src/error.rs @@ -99,19 +99,13 @@ pub enum Error { #[error("Wrong package type {0} for platform {1}")] InvalidPackageType(String, String), /// Bundle type symbol missing in binary - #[cfg_attr( - target_os = "linux", - error("__TAURI_BUNDLE_TYPE variable not found in binary. Make sure tauri crate and tauri-cli are up to date and that symbol stripping is disabled (https://doc.rust-lang.org/cargo/reference/profiles.html#strip)") - )] - #[cfg_attr( - not(target_os = "linux"), - error("__TAURI_BUNDLE_TYPE variable not found in binary. Make sure tauri crate and tauri-cli are up to date") - )] + #[error("__TAURI_BUNDLE_TYPE variable not found in binary. Make sure tauri crate and tauri-cli are up to date")] MissingBundleTypeVar, /// Failed to write binary file changed #[error("Failed to write binary file changes: `{0}`")] BinaryWriteError(String), /// Invalid offset while patching binary file + #[deprecated] #[error("Invalid offset while patching binary file")] BinaryOffsetOutOfRange, /// Unsupported architecture. diff --git a/crates/tauri-cli/Cargo.toml b/crates/tauri-cli/Cargo.toml index 5aa656ff7..131b654a7 100644 --- a/crates/tauri-cli/Cargo.toml +++ b/crates/tauri-cli/Cargo.toml @@ -66,7 +66,7 @@ tauri-utils = { version = "2.8.1", path = "../tauri-utils", features = [ "html-manipulation", ] } toml = "0.9" -jsonschema = "0.33" +jsonschema = { version = "0.33", default-features = false } handlebars = "6" include_dir = "0.7" dirs = "6" diff --git a/crates/tauri-cli/config.schema.json b/crates/tauri-cli/config.schema.json index 02c0bf908..58fab551a 100644 --- a/crates/tauri-cli/config.schema.json +++ b/crates/tauri-cli/config.schema.json @@ -165,7 +165,7 @@ "type": "object", "properties": { "windows": { - "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 ```", + "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": { diff --git a/crates/tauri-cli/src/acl/capability/new.rs b/crates/tauri-cli/src/acl/capability/new.rs index aceddc05d..14324be18 100644 --- a/crates/tauri-cli/src/acl/capability/new.rs +++ b/crates/tauri-cli/src/acl/capability/new.rs @@ -7,12 +7,7 @@ use std::{collections::HashSet, path::PathBuf}; use clap::Parser; use tauri_utils::acl::capability::{Capability, PermissionEntry}; -use crate::{ - acl::FileFormat, - error::ErrorExt, - helpers::{app_paths::tauri_dir, prompts}, - Result, -}; +use crate::{acl::FileFormat, error::ErrorExt, helpers::prompts, Result}; #[derive(Debug, Parser)] #[clap(about = "Create a new permission file")] @@ -37,7 +32,7 @@ pub struct Options { } pub fn command(options: Options) -> Result<()> { - crate::helpers::app_paths::resolve(); + let dirs = crate::helpers::app_paths::resolve_dirs(); let identifier = match options.identifier { Some(i) => i, @@ -111,8 +106,7 @@ pub fn command(options: Options) -> Result<()> { .canonicalize() .fs_context("failed to canonicalize capability file path", o.clone())?, None => { - let dir = tauri_dir(); - let capabilities_dir = dir.join("capabilities"); + let capabilities_dir = dirs.tauri.join("capabilities"); capabilities_dir.join(format!( "{}.{}", capability.identifier, diff --git a/crates/tauri-cli/src/acl/permission/ls.rs b/crates/tauri-cli/src/acl/permission/ls.rs index 1265c3c24..ccebfebf0 100644 --- a/crates/tauri-cli/src/acl/permission/ls.rs +++ b/crates/tauri-cli/src/acl/permission/ls.rs @@ -6,7 +6,6 @@ use clap::Parser; use crate::{ error::{Context, ErrorExt}, - helpers::app_paths::tauri_dir, Result, }; use colored::Colorize; @@ -25,9 +24,10 @@ pub struct Options { } pub fn command(options: Options) -> Result<()> { - crate::helpers::app_paths::resolve(); + let dirs = crate::helpers::app_paths::resolve_dirs(); - let acl_manifests_path = tauri_dir() + let acl_manifests_path = dirs + .tauri .join("gen") .join("schemas") .join("acl-manifests.json"); diff --git a/crates/tauri-cli/src/add.rs b/crates/tauri-cli/src/add.rs index 8a45c0880..074b0e3b2 100644 --- a/crates/tauri-cli/src/add.rs +++ b/crates/tauri-cli/src/add.rs @@ -10,7 +10,7 @@ use crate::{ acl, error::ErrorExt, helpers::{ - app_paths::{resolve_frontend_dir, tauri_dir}, + app_paths::{resolve_frontend_dir, Dirs}, cargo, npm::PackageManager, }, @@ -39,11 +39,11 @@ pub struct Options { } pub fn command(options: Options) -> Result<()> { - crate::helpers::app_paths::resolve(); - run(options) + let dirs = crate::helpers::app_paths::resolve_dirs(); + run(options, &dirs) } -pub fn run(options: Options) -> Result<()> { +pub fn run(options: Options, dirs: &Dirs) -> Result<()> { let (plugin, version) = options .plugin .split_once('@') @@ -71,7 +71,6 @@ pub fn run(options: Options) -> Result<()> { } let frontend_dir = resolve_frontend_dir(); - let tauri_dir = tauri_dir(); let target_str = metadata .desktop_only @@ -90,7 +89,7 @@ pub fn run(options: Options) -> Result<()> { branch: options.branch.as_deref(), rev: options.rev.as_deref(), tag: options.tag.as_deref(), - cwd: Some(tauri_dir), + cwd: Some(dirs.tauri), target: target_str, })?; @@ -117,7 +116,7 @@ pub fn run(options: Options) -> Result<()> { (None, None, None, None) => npm_name, _ => crate::error::bail!("Only one of --tag, --rev and --branch can be specified"), }; - manager.install(&[npm_spec], tauri_dir)?; + manager.install(&[npm_spec], dirs.tauri)?; } let _ = acl::permission::add::command(acl::permission::add::Options { @@ -143,7 +142,10 @@ pub fn run(options: Options) -> Result<()> { let plugin_init = format!(".plugin(tauri_plugin_{plugin_snake_case}::{plugin_init_fn})"); let re = Regex::new(r"(tauri\s*::\s*Builder\s*::\s*default\(\))(\s*)").unwrap(); - for file in [tauri_dir.join("src/main.rs"), tauri_dir.join("src/lib.rs")] { + for file in [ + dirs.tauri.join("src/main.rs"), + dirs.tauri.join("src/lib.rs"), + ] { let contents = std::fs::read_to_string(&file).fs_context("failed to read Rust entry point", file.clone())?; @@ -166,7 +168,7 @@ pub fn run(options: Options) -> Result<()> { log::info!("Running `cargo fmt`..."); let _ = Command::new("cargo") .arg("fmt") - .current_dir(tauri_dir) + .current_dir(dirs.tauri) .status(); } diff --git a/crates/tauri-cli/src/build.rs b/crates/tauri-cli/src/build.rs index ac8c5552a..b43b3c9ae 100644 --- a/crates/tauri-cli/src/build.rs +++ b/crates/tauri-cli/src/build.rs @@ -7,8 +7,8 @@ use crate::{ error::{Context, ErrorExt}, helpers::{ self, - app_paths::{frontend_dir, tauri_dir}, - config::{get as get_config, ConfigMetadata, FrontendDist}, + app_paths::Dirs, + config::{get_config, ConfigMetadata, FrontendDist}, }, info::plugins::check_mismatched_packages, interface::{rust::get_cargo_target_dir, AppInterface, Interface}, @@ -40,7 +40,7 @@ pub struct Options { pub target: Option, /// Space or comma separated list of features to activate #[clap(short, long, action = ArgAction::Append, num_args(0..))] - pub features: Option>, + pub features: Vec, /// Space or comma separated list of bundles to package. #[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')] pub bundles: Option>, @@ -82,7 +82,7 @@ pub struct Options { } pub fn command(mut options: Options, verbosity: u8) -> Result<()> { - crate::helpers::app_paths::resolve(); + let dirs = crate::helpers::app_paths::resolve_dirs(); if options.no_sign { log::warn!("--no-sign flag detected: Signing will be skipped."); @@ -99,41 +99,37 @@ pub fn command(mut options: Options, verbosity: u8) -> Result<()> { let config = get_config( target, &options.config.iter().map(|c| &c.0).collect::>(), + dirs.tauri, )?; - let mut interface = AppInterface::new( - config.lock().unwrap().as_ref().unwrap(), - options.target.clone(), - )?; + let mut interface = AppInterface::new(&config, options.target.clone(), dirs.tauri)?; - let config_guard = config.lock().unwrap(); - let config_ = config_guard.as_ref().unwrap(); + setup(&interface, &mut options, &config, &dirs, false)?; - setup(&interface, &mut options, config_, false)?; - - if let Some(minimum_system_version) = &config_.bundle.macos.minimum_system_version { + 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(); - let out_dir = app_settings.out_dir(&interface_options)?; + let out_dir = app_settings.out_dir(&interface_options, dirs.tauri)?; - let bin_path = interface.build(interface_options)?; + let bin_path = interface.build(interface_options, &dirs)?; log::info!(action ="Built"; "application at: {}", tauri_utils::display_path(bin_path)); let app_settings = interface.app_settings(); - if !options.no_bundle && (config_.bundle.active || options.bundles.is_some()) { + if !options.no_bundle && (config.bundle.active || options.bundles.is_some()) { crate::bundle::bundle( &options.into(), verbosity, ci, &interface, &*app_settings, - config_, + &config, + &dirs, &out_dir, )?; } @@ -145,14 +141,13 @@ pub fn setup( interface: &AppInterface, options: &mut Options, config: &ConfigMetadata, + dirs: &Dirs, mobile: bool, ) -> Result<()> { - let tauri_path = tauri_dir(); - // TODO: Maybe optimize this to run in parallel in the future // see https://github.com/tauri-apps/tauri/pull/13993#discussion_r2280697117 log::info!("Looking up installed tauri packages to check mismatched versions..."); - if let Err(error) = check_mismatched_packages(frontend_dir(), tauri_path) { + if let Err(error) = check_mismatched_packages(dirs.frontend, dirs.tauri) { if options.ignore_version_mismatches { log::error!("{error}"); } else { @@ -160,7 +155,7 @@ pub fn setup( } } - set_current_dir(tauri_path).context("failed to set current directory")?; + set_current_dir(dirs.tauri).context("failed to set current directory")?; let bundle_identifier_source = config .find_bundle_identifier_overwriter() @@ -191,7 +186,13 @@ pub fn setup( } if let Some(before_build) = config.build.before_build_command.clone() { - helpers::run_hook("beforeBuildCommand", before_build, interface, options.debug)?; + helpers::run_hook( + "beforeBuildCommand", + before_build, + interface, + options.debug, + dirs.frontend, + )?; } if let Some(FrontendDist::Directory(web_asset_path)) = &config.build.frontend_dist { @@ -219,7 +220,7 @@ pub fn setup( // Issue #13287 - Allow the use of target dir inside frontendDist/distDir // https://github.com/tauri-apps/tauri/issues/13287 - let target_path = get_cargo_target_dir(&options.args)?; + let target_path = get_cargo_target_dir(&options.args, dirs.tauri)?; let mut out_folders = Vec::new(); if let Ok(web_asset_canonical) = dunce::canonicalize(web_asset_path) { if let Ok(relative_path) = target_path.strip_prefix(&web_asset_canonical) { @@ -252,8 +253,7 @@ pub fn setup( options .features - .get_or_insert(Vec::new()) - .extend(config.build.features.clone().unwrap_or_default()); + .extend_from_slice(config.build.features.as_deref().unwrap_or_default()); interface.build_options(&mut options.args, &mut options.features, mobile); Ok(()) diff --git a/crates/tauri-cli/src/bundle.rs b/crates/tauri-cli/src/bundle.rs index 646f44ddb..638c215e9 100644 --- a/crates/tauri-cli/src/bundle.rs +++ b/crates/tauri-cli/src/bundle.rs @@ -16,8 +16,8 @@ use crate::{ error::{Context, ErrorExt}, helpers::{ self, - app_paths::tauri_dir, - config::{get as get_config, ConfigMetadata}, + app_paths::Dirs, + config::{get_config, ConfigMetadata}, updater_signature, }, interface::{AppInterface, AppSettings, Interface}, @@ -71,7 +71,7 @@ pub struct Options { pub config: Vec, /// Space or comma separated list of features, should be the same features passed to `tauri build` if any. #[clap(short, long, action = ArgAction::Append, num_args(0..))] - pub features: Option>, + pub features: Vec, /// Target triple to build against. /// /// It must be one of the values outputted by `$rustc --print target-list` or `universal-apple-darwin` for an universal macOS application. @@ -118,7 +118,7 @@ impl From for Options { } pub fn command(options: Options, verbosity: u8) -> crate::Result<()> { - crate::helpers::app_paths::resolve(); + let dirs = crate::helpers::app_paths::resolve_dirs(); let ci = options.ci; @@ -131,27 +131,21 @@ pub fn command(options: Options, verbosity: u8) -> crate::Result<()> { let config = get_config( target, &options.config.iter().map(|c| &c.0).collect::>(), + dirs.tauri, )?; - let interface = AppInterface::new( - config.lock().unwrap().as_ref().unwrap(), - options.target.clone(), - )?; + let interface = AppInterface::new(&config, options.target.clone(), dirs.tauri)?; - let tauri_path = tauri_dir(); - std::env::set_current_dir(tauri_path).context("failed to set current directory")?; + std::env::set_current_dir(dirs.tauri).context("failed to set current directory")?; - 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 { + 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(); - let out_dir = app_settings.out_dir(&interface_options)?; + let out_dir = app_settings.out_dir(&interface_options, dirs.tauri)?; bundle( &options, @@ -159,7 +153,8 @@ pub fn command(options: Options, verbosity: u8) -> crate::Result<()> { ci, &interface, &*app_settings, - config_, + &config, + &dirs, &out_dir, ) } @@ -172,6 +167,7 @@ pub fn bundle( interface: &AppInterface, app_settings: &A, config: &ConfigMetadata, + dirs: &Dirs, out_dir: &Path, ) -> crate::Result<()> { let package_types: Vec = if let Some(bundles) = &options.bundles { @@ -198,12 +194,19 @@ pub fn bundle( before_bundle, interface, options.debug, + dirs.frontend, )?; } } let mut settings = app_settings - .get_bundler_settings(options.clone().into(), config, out_dir, package_types) + .get_bundler_settings( + options.clone().into(), + config, + out_dir, + package_types, + dirs.tauri, + ) .with_context(|| "failed to build bundler settings")?; settings.set_no_sign(options.no_sign); diff --git a/crates/tauri-cli/src/dev.rs b/crates/tauri-cli/src/dev.rs index 7ebabb458..a66186919 100644 --- a/crates/tauri-cli/src/dev.rs +++ b/crates/tauri-cli/src/dev.rs @@ -5,11 +5,9 @@ use crate::{ error::{Context, ErrorExt}, helpers::{ - app_paths::{frontend_dir, tauri_dir}, + app_paths::Dirs, command_env, - config::{ - get as get_config, reload as reload_config, BeforeDevCommand, ConfigHandle, FrontendDist, - }, + config::{get_config, reload_config, BeforeDevCommand, ConfigMetadata, FrontendDist}, }, info::plugins::check_mismatched_packages, interface::{AppInterface, ExitReason, Interface}, @@ -57,7 +55,7 @@ pub struct Options { pub target: Option, /// List of cargo features to activate #[clap(short, long, action = ArgAction::Append, num_args(0..))] - pub features: Option>, + pub features: Vec, /// Exit on panic #[clap(short, long)] pub exit_on_panic: bool, @@ -99,61 +97,57 @@ pub struct Options { } pub fn command(options: Options) -> Result<()> { - crate::helpers::app_paths::resolve(); + let dirs = crate::helpers::app_paths::resolve_dirs(); - let r = command_internal(options); + let r = command_internal(options, dirs); if r.is_err() { kill_before_dev_process(); } r } -fn command_internal(mut options: Options) -> Result<()> { +fn command_internal(mut options: Options, dirs: Dirs) -> Result<()> { let target = options .target .as_deref() .map(Target::from_triple) .unwrap_or_else(Target::current); - let config = get_config( + let mut config = get_config( target, &options.config.iter().map(|c| &c.0).collect::>(), + dirs.tauri, )?; - let mut interface = AppInterface::new( - config.lock().unwrap().as_ref().unwrap(), - options.target.clone(), - )?; + let mut interface = AppInterface::new(&config, options.target.clone(), dirs.tauri)?; - setup(&interface, &mut options, config)?; + setup(&interface, &mut options, &mut config, &dirs)?; let exit_on_panic = options.exit_on_panic; let no_watch = options.no_watch; - interface.dev(options.into(), move |status, reason| { - on_app_exit(status, reason, exit_on_panic, no_watch) - }) + interface.dev( + &mut config, + options.into(), + move |status, reason| on_app_exit(status, reason, exit_on_panic, no_watch), + &dirs, + ) } -pub fn setup(interface: &AppInterface, options: &mut Options, config: ConfigHandle) -> Result<()> { - let tauri_path = tauri_dir(); - +pub fn setup( + interface: &AppInterface, + options: &mut Options, + config: &mut ConfigMetadata, + dirs: &Dirs, +) -> Result<()> { std::thread::spawn(|| { - if let Err(error) = check_mismatched_packages(frontend_dir(), tauri_path) { + if let Err(error) = check_mismatched_packages(dirs.frontend, dirs.tauri) { log::error!("{error}"); } }); - set_current_dir(tauri_path).context("failed to set current directory")?; + set_current_dir(dirs.tauri).context("failed to set current directory")?; - if let Some(before_dev) = config - .lock() - .unwrap() - .as_ref() - .unwrap() - .build - .before_dev_command - .clone() - { + if let Some(before_dev) = config.build.before_dev_command.clone() { let (script, script_cwd, wait) = match before_dev { BeforeDevCommand::Script(s) if s.is_empty() => (None, None, false), BeforeDevCommand::Script(s) => (Some(s), None, false), @@ -161,7 +155,7 @@ pub fn setup(interface: &AppInterface, options: &mut Options, config: ConfigHand (Some(script), cwd.map(Into::into), wait) } }; - let cwd = script_cwd.unwrap_or_else(|| frontend_dir().clone()); + let cwd = script_cwd.unwrap_or_else(|| dirs.frontend.to_owned()); if let Some(before_dev) = script { log::info!(action = "Running"; "BeforeDevCommand (`{}`)", before_dev); let mut env = command_env(true); @@ -235,45 +229,14 @@ pub fn setup(interface: &AppInterface, options: &mut Options, config: ConfigHand } if options.runner.is_none() { - options.runner = config - .lock() - .unwrap() - .as_ref() - .unwrap() - .build - .runner - .clone(); + options.runner = config.build.runner.clone(); } - let mut cargo_features = config - .lock() - .unwrap() - .as_ref() - .unwrap() - .build - .features - .clone() - .unwrap_or_default(); - if let Some(features) = &options.features { - cargo_features.extend(features.clone()); - } + let mut cargo_features = config.build.features.clone().unwrap_or_default(); + cargo_features.extend(options.features.clone()); - let mut dev_url = config - .lock() - .unwrap() - .as_ref() - .unwrap() - .build - .dev_url - .clone(); - let frontend_dist = config - .lock() - .unwrap() - .as_ref() - .unwrap() - .build - .frontend_dist - .clone(); + let mut dev_url = config.build.dev_url.clone(); + let frontend_dist = config.build.frontend_dist.clone(); if !options.no_dev_server && dev_url.is_none() { if let Some(FrontendDist::Directory(path)) = &frontend_dist { if path.exists() { @@ -296,7 +259,11 @@ pub fn setup(interface: &AppInterface, options: &mut Options, config: ConfigHand } }))); - reload_config(&options.config.iter().map(|c| &c.0).collect::>())?; + reload_config( + config, + &options.config.iter().map(|c| &c.0).collect::>(), + dirs.tauri, + )?; } } } @@ -349,16 +316,9 @@ pub fn setup(interface: &AppInterface, options: &mut Options, config: ConfigHand } if options.additional_watch_folders.is_empty() { - options.additional_watch_folders.extend( - config - .lock() - .unwrap() - .as_ref() - .unwrap() - .build - .additional_watch_folders - .clone(), - ); + options + .additional_watch_folders + .extend(config.build.additional_watch_folders.clone()); } Ok(()) diff --git a/crates/tauri-cli/src/helpers/app_paths.rs b/crates/tauri-cli/src/helpers/app_paths.rs index 3c728f5ba..5b3e0d91a 100644 --- a/crates/tauri-cli/src/helpers/app_paths.rs +++ b/crates/tauri-cli/src/helpers/app_paths.rs @@ -23,6 +23,11 @@ const ENV_TAURI_APP_PATH: &str = "TAURI_APP_PATH"; // path to the frontend app directory, usually `/` const ENV_TAURI_FRONTEND_PATH: &str = "TAURI_FRONTEND_PATH"; +pub struct Dirs { + pub tauri: &'static Path, + pub frontend: &'static Path, +} + static FRONTEND_DIR: OnceLock = OnceLock::new(); static TAURI_DIR: OnceLock = OnceLock::new(); @@ -122,8 +127,8 @@ pub fn resolve_tauri_dir() -> Option { }) } -pub fn resolve() { - TAURI_DIR.set(resolve_tauri_dir().unwrap_or_else(|| { +pub fn resolve_dirs() -> Dirs { + let tauri = TAURI_DIR.get_or_init(|| resolve_tauri_dir().unwrap_or_else(|| { let env_var_name = env_tauri_app_path().is_some().then(|| format!("`{ENV_TAURI_APP_PATH}`")); panic!("Couldn't recognize the {} folder as a Tauri project. It must contain a `{}`, `{}` or `{}` file in any subfolder.", env_var_name.as_deref().unwrap_or("current"), @@ -131,16 +136,11 @@ pub fn resolve() { ConfigFormat::Json5.into_file_name(), ConfigFormat::Toml.into_file_name() ) - })).expect("tauri dir already resolved"); - FRONTEND_DIR - .set(resolve_frontend_dir().unwrap_or_else(|| tauri_dir().parent().unwrap().to_path_buf())) - .expect("app dir already resolved"); -} - -pub fn tauri_dir() -> &'static PathBuf { - TAURI_DIR - .get() - .expect("app paths not initialized, this is a Tauri CLI bug") + })); + let frontend = FRONTEND_DIR.get_or_init(|| { + resolve_frontend_dir().unwrap_or_else(|| tauri.parent().unwrap().to_path_buf()) + }); + Dirs { tauri, frontend } } pub fn resolve_frontend_dir() -> Option { @@ -165,9 +165,3 @@ pub fn resolve_frontend_dir() -> Option { }) .map(|p| p.parent().unwrap().to_path_buf()) } - -pub fn frontend_dir() -> &'static PathBuf { - FRONTEND_DIR - .get() - .expect("app paths not initialized, this is a Tauri CLI bug") -} diff --git a/crates/tauri-cli/src/helpers/cargo_manifest.rs b/crates/tauri-cli/src/helpers/cargo_manifest.rs index 0ccf71790..b1bfa9a10 100644 --- a/crates/tauri-cli/src/helpers/cargo_manifest.rs +++ b/crates/tauri-cli/src/helpers/cargo_manifest.rs @@ -56,7 +56,7 @@ pub fn cargo_manifest_and_lock(tauri_dir: &Path) -> (Option, Opti .ok() .and_then(|manifest_contents| toml::from_str(&manifest_contents).ok()); - let lock: Option = get_workspace_dir() + let lock: Option = get_workspace_dir(tauri_dir) .ok() .and_then(|p| fs::read_to_string(p.join("Cargo.lock")).ok()) .and_then(|s| toml::from_str(&s).ok()); diff --git a/crates/tauri-cli/src/helpers/config.rs b/crates/tauri-cli/src/helpers/config.rs index d25c29d4f..ce1f11c5b 100644 --- a/crates/tauri-cli/src/helpers/config.rs +++ b/crates/tauri-cli/src/helpers/config.rs @@ -13,8 +13,9 @@ use std::{ collections::HashMap, env::{current_dir, set_current_dir, set_var}, ffi::{OsStr, OsString}, + path::Path, process::exit, - sync::Mutex, + sync::OnceLock, }; use crate::error::Context; @@ -54,8 +55,7 @@ impl ConfigMetadata { for (ext, config) in &self.extensions { if let Some(identifier) = config .as_object() - .and_then(|bundle_config| bundle_config.get("identifier")) - .and_then(|id| id.as_str()) + .and_then(|bundle_config| bundle_config.get("identifier")?.as_str()) { if identifier == self.inner.identifier { return Some(ext.clone()); @@ -66,14 +66,11 @@ impl ConfigMetadata { } } -pub type ConfigHandle = &'static Mutex>; - pub fn wix_settings(config: WixConfig) -> tauri_bundler::WixSettings { tauri_bundler::WixSettings { version: config.version, upgrade_code: config.upgrade_code, - fips_compliant: std::env::var("TAURI_BUNDLER_WIX_FIPS_COMPLIANT") - .ok() + fips_compliant: std::env::var_os("TAURI_BUNDLER_WIX_FIPS_COMPLIANT") .map(|v| v == "true") .unwrap_or(config.fips_compliant), language: tauri_bundler::WixLanguage(match config.language { @@ -141,22 +138,22 @@ pub fn custom_sign_settings( } } -fn config_handle() -> ConfigHandle { - static CONFIG_HANDLE: Mutex> = Mutex::new(None); - &CONFIG_HANDLE +fn config_schema_validator() -> &'static jsonschema::Validator { + // TODO: Switch to `LazyLock` when we bump MSRV to above 1.80 + static CONFIG_SCHEMA_VALIDATOR: OnceLock = OnceLock::new(); + CONFIG_SCHEMA_VALIDATOR.get_or_init(|| { + let schema: JsonValue = serde_json::from_str(include_str!("../../config.schema.json")) + .expect("Failed to parse config schema bundled in the tauri-cli"); + jsonschema::validator_for(&schema).expect("Config schema bundled in the tauri-cli is invalid") + }) } -/// Gets the static parsed config from `tauri.conf.json`. -fn get_internal( +fn load_config( merge_configs: &[&serde_json::Value], reload: bool, target: Target, -) -> crate::Result { - if !reload && config_handle().lock().unwrap().is_some() { - return Ok(config_handle()); - } - - let tauri_dir = super::app_paths::tauri_dir(); + tauri_dir: &Path, +) -> crate::Result { let (mut config, config_path) = tauri_utils::config::parse::parse_value(target, tauri_dir.join("tauri.conf.json")) .context("failed to parse config")?; @@ -165,8 +162,7 @@ fn get_internal( let original_identifier = config .as_object() - .and_then(|config| config.get("identifier")) - .and_then(|id| id.as_str()) + .and_then(|config| config.get("identifier")?.as_str()) .map(ToString::to_string); if let Some((platform_config, config_path)) = @@ -192,17 +188,14 @@ fn get_internal( if config_path.extension() == Some(OsStr::new("json")) || config_path.extension() == Some(OsStr::new("json5")) { - let schema: JsonValue = serde_json::from_str(include_str!("../../config.schema.json")) - .context("failed to parse config schema")?; - let validator = jsonschema::validator_for(&schema).expect("Invalid schema"); - let mut errors = validator.iter_errors(&config).peekable(); + let mut errors = config_schema_validator().iter_errors(&config).peekable(); if errors.peek().is_some() { for error in errors { let path = error.instance_path.into_iter().join(" > "); if path.is_empty() { - log::error!("`{config_file_name:?}` error: {}", error); + log::error!("`{config_file_name:?}` error: {error}"); } else { - log::error!("`{config_file_name:?}` error on `{}`: {}", path, error); + log::error!("`{config_file_name:?}` error on `{path}`: {error}"); } } if !reload { @@ -233,59 +226,54 @@ fn get_internal( std::env::set_var(REMOVE_UNUSED_COMMANDS_ENV_VAR, tauri_dir); } - *config_handle().lock().unwrap() = Some(ConfigMetadata { + Ok(ConfigMetadata { target, original_identifier, inner: config, extensions, - }); - - Ok(config_handle()) + }) } -pub fn get(target: Target, merge_configs: &[&serde_json::Value]) -> crate::Result { - get_internal(merge_configs, false, target) +pub fn get_config( + target: Target, + merge_configs: &[&serde_json::Value], + tauri_dir: &Path, +) -> crate::Result { + load_config(merge_configs, false, target, tauri_dir) } -pub fn reload(merge_configs: &[&serde_json::Value]) -> crate::Result { - let target = config_handle() - .lock() - .unwrap() - .as_ref() - .map(|conf| conf.target); - if let Some(target) = target { - get_internal(merge_configs, true, target) - } else { - crate::error::bail!("config not loaded"); - } +pub fn reload_config( + config: &mut ConfigMetadata, + merge_configs: &[&serde_json::Value], + tauri_dir: &Path, +) -> crate::Result<()> { + let target = config.target; + *config = load_config(merge_configs, true, target, tauri_dir)?; + Ok(()) } /// merges the loaded config with the given value -pub fn merge_with(merge_configs: &[&serde_json::Value]) -> crate::Result { - let handle = config_handle(); - +pub fn merge_config_with( + config: &mut ConfigMetadata, + merge_configs: &[&serde_json::Value], +) -> crate::Result<()> { if merge_configs.is_empty() { - return Ok(handle); + return Ok(()); } - if let Some(config_metadata) = &mut *handle.lock().unwrap() { - let mut merge_config = serde_json::Value::Object(Default::default()); - for conf in merge_configs { - merge_patches(&mut merge_config, conf); - } - - let merge_config_str = serde_json::to_string(&merge_config).unwrap(); - set_var("TAURI_CONFIG", merge_config_str); - - let mut value = - serde_json::to_value(config_metadata.inner.clone()).context("failed to serialize config")?; - merge(&mut value, &merge_config); - config_metadata.inner = serde_json::from_value(value).context("failed to parse config")?; - - Ok(handle) - } else { - crate::error::bail!("config not loaded"); + let mut merge_config = serde_json::Value::Object(Default::default()); + for conf in merge_configs { + merge_patches(&mut merge_config, conf); } + + let merge_config_str = serde_json::to_string(&merge_config).unwrap(); + set_var("TAURI_CONFIG", merge_config_str); + + let mut value = + serde_json::to_value(config.inner.clone()).context("failed to serialize config")?; + merge(&mut value, &merge_config); + config.inner = serde_json::from_value(value).context("failed to parse config")?; + Ok(()) } /// Same as [`json_patch::merge`] but doesn't delete the key when the patch's value is `null` diff --git a/crates/tauri-cli/src/helpers/mod.rs b/crates/tauri-cli/src/helpers/mod.rs index 06ff052ce..3d2a5f0e9 100644 --- a/crates/tauri-cli/src/helpers/mod.rs +++ b/crates/tauri-cli/src/helpers/mod.rs @@ -35,8 +35,6 @@ use crate::{ CommandExt, }; -use self::app_paths::frontend_dir; - pub fn command_env(debug: bool) -> HashMap<&'static str, String> { let mut map = HashMap::new(); @@ -78,13 +76,14 @@ pub fn run_hook( hook: HookCommand, interface: &AppInterface, debug: bool, + frontend_dir: &Path, ) -> crate::Result<()> { let (script, script_cwd) = match hook { HookCommand::Script(s) if s.is_empty() => (None, None), HookCommand::Script(s) => (Some(s), None), HookCommand::ScriptWithOptions { script, cwd } => (Some(script), cwd.map(Into::into)), }; - let cwd = script_cwd.unwrap_or_else(|| frontend_dir().clone()); + let cwd = script_cwd.unwrap_or_else(|| frontend_dir.to_owned()); if let Some(script) = script { log::info!(action = "Running"; "{} `{}`", name, script); diff --git a/crates/tauri-cli/src/helpers/updater_signature.rs b/crates/tauri-cli/src/helpers/updater_signature.rs index 9367e7bad..ba9012359 100644 --- a/crates/tauri-cli/src/helpers/updater_signature.rs +++ b/crates/tauri-cli/src/helpers/updater_signature.rs @@ -120,9 +120,14 @@ where { let bin_path = bin_path.as_ref(); // We need to append .sig at the end it's where the signature will be stored - let mut extension = bin_path.extension().unwrap().to_os_string(); - extension.push(".sig"); - let signature_path = bin_path.with_extension(extension); + // TODO: use with_added_extension when we bump MSRV to > 1.91' + let signature_path = if let Some(ext) = bin_path.extension() { + let mut extension = ext.to_os_string(); + extension.push(".sig"); + bin_path.with_extension(extension) + } else { + bin_path.with_extension("sig") + }; let trusted_comment = format!( "timestamp:{}\tfile:{}", diff --git a/crates/tauri-cli/src/icon.rs b/crates/tauri-cli/src/icon.rs index 6228dc97b..4945fb826 100644 --- a/crates/tauri-cli/src/icon.rs +++ b/crates/tauri-cli/src/icon.rs @@ -4,7 +4,6 @@ use crate::{ error::{Context, Error, ErrorExt}, - helpers::app_paths::tauri_dir, Result, }; @@ -237,8 +236,8 @@ fn parse_bg_color(bg_color_string: &String) -> Result> { pub fn command(options: Options) -> Result<()> { let input = options.input; let out_dir = options.output.unwrap_or_else(|| { - crate::helpers::app_paths::resolve(); - tauri_dir().join("icons") + let dirs = crate::helpers::app_paths::resolve_dirs(); + dirs.tauri.join("icons") }); let png_icon_sizes = options.png.unwrap_or_default(); diff --git a/crates/tauri-cli/src/info/app.rs b/crates/tauri-cli/src/info/app.rs index 624d79bd3..6814c96bd 100644 --- a/crates/tauri-cli/src/info/app.rs +++ b/crates/tauri-cli/src/info/app.rs @@ -3,56 +3,46 @@ // SPDX-License-Identifier: MIT use super::SectionItem; +use crate::helpers::config::ConfigMetadata; use crate::helpers::framework; -use std::{ - fs::read_to_string, - path::{Path, PathBuf}, -}; -use tauri_utils::platform::Target; +use std::{fs::read_to_string, path::PathBuf}; -pub fn items(frontend_dir: Option<&PathBuf>, tauri_dir: Option<&Path>) -> Vec { +pub fn items(config: &ConfigMetadata, frontend_dir: Option<&PathBuf>) -> Vec { let mut items = Vec::new(); - if tauri_dir.is_some() { - if let Ok(config) = crate::helpers::config::get(Target::current(), &[]) { - let config_guard = config.lock().unwrap(); - let config = config_guard.as_ref().unwrap(); + let bundle_or_build = if config.bundle.active { + "bundle" + } else { + "build" + }; + items.push(SectionItem::new().description(format!("build-type: {bundle_or_build}"))); - let bundle_or_build = if config.bundle.active { - "bundle" - } else { - "build" - }; - items.push(SectionItem::new().description(format!("build-type: {bundle_or_build}"))); + let csp = config + .app + .security + .csp + .clone() + .map(|c| c.to_string()) + .unwrap_or_else(|| "unset".to_string()); + items.push(SectionItem::new().description(format!("CSP: {csp}"))); - let csp = config - .app - .security - .csp - .clone() - .map(|c| c.to_string()) - .unwrap_or_else(|| "unset".to_string()); - items.push(SectionItem::new().description(format!("CSP: {csp}"))); + if let Some(frontend_dist) = &config.build.frontend_dist { + items.push(SectionItem::new().description(format!("frontendDist: {frontend_dist}"))); + } - if let Some(frontend_dist) = &config.build.frontend_dist { - items.push(SectionItem::new().description(format!("frontendDist: {frontend_dist}"))); + if let Some(dev_url) = &config.build.dev_url { + items.push(SectionItem::new().description(format!("devUrl: {dev_url}"))); + } + + if let Some(frontend_dir) = frontend_dir { + if let Ok(package_json) = read_to_string(frontend_dir.join("package.json")) { + let (framework, bundler) = framework::infer_from_package_json(&package_json); + + if let Some(framework) = framework { + items.push(SectionItem::new().description(format!("framework: {framework}"))); } - if let Some(dev_url) = &config.build.dev_url { - items.push(SectionItem::new().description(format!("devUrl: {dev_url}"))); - } - - if let Some(frontend_dir) = frontend_dir { - if let Ok(package_json) = read_to_string(frontend_dir.join("package.json")) { - let (framework, bundler) = framework::infer_from_package_json(&package_json); - - if let Some(framework) = framework { - items.push(SectionItem::new().description(format!("framework: {framework}"))); - } - - if let Some(bundler) = bundler { - items.push(SectionItem::new().description(format!("bundler: {bundler}"))); - } - } + if let Some(bundler) = bundler { + items.push(SectionItem::new().description(format!("bundler: {bundler}"))); } } } diff --git a/crates/tauri-cli/src/info/mod.rs b/crates/tauri-cli/src/info/mod.rs index 0f89d31c1..cb52415a9 100644 --- a/crates/tauri-cli/src/info/mod.rs +++ b/crates/tauri-cli/src/info/mod.rs @@ -12,6 +12,7 @@ use colored::{ColoredString, Colorize}; use dialoguer::{theme::ColorfulTheme, Confirm}; use serde::Deserialize; use std::fmt::{self, Display, Formatter}; +use tauri_utils::platform::Target; mod app; mod env_nodejs; @@ -265,11 +266,6 @@ pub fn command(options: Options) -> Result<()> { let frontend_dir = resolve_frontend_dir(); let tauri_dir = resolve_tauri_dir(); - if tauri_dir.is_some() { - // safe to initialize - crate::helpers::app_paths::resolve(); - } - let package_manager = frontend_dir .as_ref() .map(packages_nodejs::package_manager) @@ -313,9 +309,11 @@ pub fn command(options: Options) -> Result<()> { interactive, items: Vec::new(), }; - app - .items - .extend(app::items(frontend_dir.as_ref(), tauri_dir.as_deref())); + if let Some(tauri_dir) = &tauri_dir { + if let Ok(config) = crate::helpers::config::get_config(Target::current(), &[], tauri_dir) { + app.items.extend(app::items(&config, frontend_dir.as_ref())); + }; + } environment.display(); diff --git a/crates/tauri-cli/src/info/plugins.rs b/crates/tauri-cli/src/info/plugins.rs index a5080caf1..d0ede6b25 100644 --- a/crates/tauri-cli/src/info/plugins.rs +++ b/crates/tauri-cli/src/info/plugins.rs @@ -111,33 +111,27 @@ pub fn items( ) -> Vec { let mut items = Vec::new(); - if tauri_dir.is_some() || frontend_dir.is_some() { - if let Some(tauri_dir) = tauri_dir { - let (manifest, lock) = cargo_manifest_and_lock(tauri_dir); + if let Some(tauri_dir) = tauri_dir { + let (manifest, lock) = cargo_manifest_and_lock(tauri_dir); - for p in helpers::plugins::known_plugins().keys() { - let dep = format!("tauri-plugin-{p}"); - let crate_version = crate_version(tauri_dir, manifest.as_ref(), lock.as_ref(), &dep); - if !crate_version.has_version() { - continue; - } - let item = packages_rust::rust_section_item(&dep, crate_version); - items.push(item); - - let Some(frontend_dir) = frontend_dir else { - continue; - }; - - let package = format!("@tauri-apps/plugin-{p}"); - - let item = packages_nodejs::nodejs_section_item( - package, - None, - frontend_dir.clone(), - package_manager, - ); - items.push(item); + for p in helpers::plugins::known_plugins().keys() { + let dep = format!("tauri-plugin-{p}"); + let crate_version = crate_version(tauri_dir, manifest.as_ref(), lock.as_ref(), &dep); + if !crate_version.has_version() { + continue; } + let item = packages_rust::rust_section_item(&dep, crate_version); + items.push(item); + + let Some(frontend_dir) = frontend_dir else { + continue; + }; + + let package = format!("@tauri-apps/plugin-{p}"); + + let item = + packages_nodejs::nodejs_section_item(package, None, frontend_dir.clone(), package_manager); + items.push(item); } } diff --git a/crates/tauri-cli/src/inspect.rs b/crates/tauri-cli/src/inspect.rs index 68d040ed2..de26dd984 100644 --- a/crates/tauri-cli/src/inspect.rs +++ b/crates/tauri-cli/src/inspect.rs @@ -1,6 +1,7 @@ // Copyright 2019-2024 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT +use std::path::Path; use crate::Result; use clap::{Parser, Subcommand}; @@ -8,7 +9,7 @@ use clap::{Parser, Subcommand}; use crate::interface::{AppInterface, AppSettings, Interface}; #[derive(Debug, Parser)] -#[clap(about = "Manage or create permissions for your app or plugin")] +#[clap(about = "Inspect values used by Tauri")] pub struct Cli { #[clap(subcommand)] command: Commands, @@ -21,36 +22,34 @@ enum Commands { } pub fn command(cli: Cli) -> Result<()> { + let dirs = crate::helpers::app_paths::resolve_dirs(); match cli.command { - Commands::WixUpgradeCode => wix_upgrade_code(), + Commands::WixUpgradeCode => wix_upgrade_code(dirs.tauri), } } // NOTE: if this is ever changed, make sure to also update Wix upgrade code generation in tauri-bundler -fn wix_upgrade_code() -> Result<()> { - crate::helpers::app_paths::resolve(); - +fn wix_upgrade_code(tauri_dir: &Path) -> Result<()> { let target = tauri_utils::platform::Target::Windows; - let config = crate::helpers::config::get(target, &[])?; + let config = crate::helpers::config::get_config(target, &[], tauri_dir)?; - let interface = AppInterface::new(config.lock().unwrap().as_ref().unwrap(), None)?; + let interface = AppInterface::new(&config, None, tauri_dir)?; let product_name = interface.app_settings().get_package_settings().product_name; let upgrade_code = uuid::Uuid::new_v5( &uuid::Uuid::NAMESPACE_DNS, format!("{product_name}.exe.app.x64").as_bytes(), - ) - .to_string(); + ); log::info!("Default WiX Upgrade Code, derived from {product_name}: {upgrade_code}"); - if let Some(code) = config.lock().unwrap().as_ref().and_then(|c| { - c.bundle - .windows - .wix - .as_ref() - .and_then(|wix| wix.upgrade_code) - }) { + if let Some(code) = config + .bundle + .windows + .wix + .as_ref() + .and_then(|wix| wix.upgrade_code) + { log::info!("Application Upgrade Code override: {code}"); } diff --git a/crates/tauri-cli/src/interface/mod.rs b/crates/tauri-cli/src/interface/mod.rs index 00ca4c3d1..dc15e7ecb 100644 --- a/crates/tauri-cli/src/interface/mod.rs +++ b/crates/tauri-cli/src/interface/mod.rs @@ -11,7 +11,10 @@ use std::{ sync::Arc, }; -use crate::{error::Context, helpers::config::Config}; +use crate::{ + error::Context, helpers::app_paths::Dirs, helpers::config::Config, + helpers::config::ConfigMetadata, +}; use tauri_bundler::bundle::{PackageType, Settings, SettingsBuilder}; pub use rust::{MobileOptions, Options, Rust as AppInterface, WatcherOptions}; @@ -32,9 +35,14 @@ pub trait AppSettings { options: &Options, config: &Config, features: &[String], + tauri_dir: &Path, ) -> crate::Result; - fn app_binary_path(&self, options: &Options) -> crate::Result; - fn get_binaries(&self, options: &Options) -> crate::Result>; + fn app_binary_path(&self, options: &Options, tauri_dir: &Path) -> crate::Result; + fn get_binaries( + &self, + options: &Options, + tauri_dir: &Path, + ) -> crate::Result>; fn app_name(&self) -> Option; fn lib_name(&self) -> Option; @@ -44,9 +52,10 @@ pub trait AppSettings { config: &Config, out_dir: &Path, package_types: Vec, + tauri_dir: &Path, ) -> crate::Result { let no_default_features = options.args.contains(&"--no-default-features".into()); - let mut enabled_features = options.features.clone().unwrap_or_default(); + let mut enabled_features = options.features.clone(); if !no_default_features { enabled_features.push("default".into()); } @@ -57,7 +66,7 @@ pub trait AppSettings { tauri_utils::platform::target_triple().context("failed to get target triple")? }; - let mut bins = self.get_binaries(&options)?; + let mut bins = self.get_binaries(&options, tauri_dir)?; if let Some(main_binary_name) = &config.main_binary_name { let main = bins.iter_mut().find(|b| b.main()).context("no main bin?")?; main.set_name(main_binary_name.to_owned()); @@ -65,7 +74,7 @@ pub trait AppSettings { let mut settings_builder = SettingsBuilder::new() .package_settings(self.get_package_settings()) - .bundle_settings(self.get_bundle_settings(&options, config, &enabled_features)?) + .bundle_settings(self.get_bundle_settings(&options, config, &enabled_features, tauri_dir)?) .binaries(bins) .project_out_directory(out_dir) .target(target) @@ -73,7 +82,7 @@ pub trait AppSettings { if config.bundle.use_local_tools_dir { settings_builder = settings_builder.local_tools_directory( - rust::get_cargo_metadata() + rust::get_cargo_metadata(tauri_dir) .context("failed to get cargo metadata")? .target_directory, ) @@ -99,23 +108,31 @@ pub enum ExitReason { pub trait Interface: Sized { type AppSettings: AppSettings; - fn new(config: &Config, target: Option) -> crate::Result; + fn new(config: &Config, target: Option, tauri_dir: &Path) -> crate::Result; fn app_settings(&self) -> Arc; fn env(&self) -> HashMap<&str, String>; - fn build(&mut self, options: Options) -> crate::Result; + fn build(&mut self, options: Options, dirs: &Dirs) -> crate::Result; fn dev, ExitReason) + Send + Sync + 'static>( &mut self, + config: &mut ConfigMetadata, options: Options, on_exit: F, + dirs: &Dirs, ) -> crate::Result<()>; - fn mobile_dev crate::Result>>( + fn mobile_dev< + R: Fn(MobileOptions, &ConfigMetadata) -> crate::Result>, + >( &mut self, + config: &mut ConfigMetadata, options: MobileOptions, runner: R, + dirs: &Dirs, ) -> crate::Result<()>; - fn watch crate::Result>>( + fn watch crate::Result>>( &mut self, + config: &mut ConfigMetadata, options: WatcherOptions, runner: R, + dirs: &Dirs, ) -> crate::Result<()>; } diff --git a/crates/tauri-cli/src/interface/rust.rs b/crates/tauri-cli/src/interface/rust.rs index 0f7d2fd02..3878b8845 100644 --- a/crates/tauri-cli/src/interface/rust.rs +++ b/crates/tauri-cli/src/interface/rust.rs @@ -7,6 +7,7 @@ use std::{ ffi::OsStr, fs::FileType, io::{BufRead, Write}, + iter::once, path::{Path, PathBuf}, process::Command, str::FromStr, @@ -15,7 +16,6 @@ use std::{ }; use dunce::canonicalize; -use glob::glob; use ignore::gitignore::{Gitignore, GitignoreBuilder}; use notify::RecursiveMode; use notify_debouncer_full::new_debouncer; @@ -31,8 +31,8 @@ use super::{AppSettings, DevProcess, ExitReason, Interface}; use crate::{ error::{Context, Error, ErrorExt}, helpers::{ - app_paths::{frontend_dir, tauri_dir}, - config::{nsis_settings, reload as reload_config, wix_settings, BundleResources, Config}, + app_paths::Dirs, + config::{nsis_settings, reload_config, wix_settings, BundleResources, Config, ConfigMetadata}, }, ConfigValue, }; @@ -51,7 +51,7 @@ pub struct Options { pub runner: Option, pub debug: bool, pub target: Option, - pub features: Option>, + pub features: Vec, pub args: Vec, pub config: Vec, pub no_watch: bool, @@ -108,7 +108,7 @@ impl From for Options { #[derive(Debug, Clone)] pub struct MobileOptions { pub debug: bool, - pub features: Option>, + pub features: Vec, pub args: Vec, pub config: Vec, pub no_watch: bool, @@ -137,7 +137,7 @@ pub struct Rust { impl Interface for Rust { type AppSettings = RustAppSettings; - fn new(config: &Config, target: Option) -> crate::Result { + fn new(config: &Config, target: Option, tauri_dir: &Path) -> crate::Result { let manifest = { let (tx, rx) = sync_channel(1); let mut watcher = new_debouncer(Duration::from_secs(1), None, move |r| { @@ -147,14 +147,9 @@ impl Interface for Rust { }) .unwrap(); watcher - .watch(tauri_dir().join("Cargo.toml"), RecursiveMode::NonRecursive) - .with_context(|| { - format!( - "failed to watch {}", - tauri_dir().join("Cargo.toml").display() - ) - })?; - let (manifest, modified) = rewrite_manifest(config)?; + .watch(tauri_dir.join("Cargo.toml"), RecursiveMode::NonRecursive) + .with_context(|| format!("failed to watch {}", tauri_dir.join("Cargo.toml").display()))?; + let (manifest, modified) = rewrite_manifest(config, tauri_dir)?; if modified { // Wait for the modified event so we don't trigger a re-build later on let _ = rx.recv_timeout(Duration::from_secs(2)); @@ -172,7 +167,7 @@ impl Interface for Rust { ); } - let app_settings = RustAppSettings::new(config, manifest, target)?; + let app_settings = RustAppSettings::new(config, manifest, target, tauri_dir)?; Ok(Self { app_settings: Arc::new(app_settings), @@ -186,20 +181,23 @@ impl Interface for Rust { self.app_settings.clone() } - fn build(&mut self, options: Options) -> crate::Result { + fn build(&mut self, options: Options, dirs: &Dirs) -> crate::Result { desktop::build( options, &self.app_settings, &mut self.available_targets, self.config_features.clone(), self.main_binary_name.as_deref(), + dirs.tauri, ) } fn dev, ExitReason) + Send + Sync + 'static>( &mut self, + config: &mut ConfigMetadata, mut options: Options, on_exit: F, + dirs: &Dirs, ) -> crate::Result<()> { let on_exit = Arc::new(on_exit); @@ -223,20 +221,29 @@ impl Interface for Rust { Ok(()) } else { let merge_configs = options.config.iter().map(|c| &c.0).collect::>(); - let run = Arc::new(|rust: &mut Rust| { - let on_exit = on_exit.clone(); - rust.run_dev(options.clone(), run_args.clone(), move |status, reason| { - on_exit(status, reason) - }) - }); - self.run_dev_watcher(&options.additional_watch_folders, &merge_configs, run) + self.run_dev_watcher( + config, + &options.additional_watch_folders, + &merge_configs, + |rust: &mut Rust, _config| { + let on_exit = on_exit.clone(); + rust.run_dev(options.clone(), run_args.clone(), move |status, reason| { + on_exit(status, reason) + }) + }, + dirs, + ) } } - fn mobile_dev crate::Result>>( + fn mobile_dev< + R: Fn(MobileOptions, &ConfigMetadata) -> crate::Result>, + >( &mut self, + config: &mut ConfigMetadata, mut options: MobileOptions, runner: R, + dirs: &Dirs, ) -> crate::Result<()> { let mut run_args = Vec::new(); dev_options( @@ -248,27 +255,36 @@ impl Interface for Rust { ); if options.no_watch { - runner(options)?; + runner(options, config)?; Ok(()) } else { self.watch( + config, WatcherOptions { config: options.config.clone(), additional_watch_folders: options.additional_watch_folders.clone(), }, - move || runner(options.clone()), + move |config| runner(options.clone(), config), + dirs, ) } } - fn watch crate::Result>>( + fn watch crate::Result>>( &mut self, + config: &mut ConfigMetadata, options: WatcherOptions, runner: R, + dirs: &Dirs, ) -> crate::Result<()> { let merge_configs = options.config.iter().map(|c| &c.0).collect::>(); - let run = Arc::new(|_rust: &mut Rust| runner()); - self.run_dev_watcher(&options.additional_watch_folders, &merge_configs, run) + self.run_dev_watcher( + config, + &options.additional_watch_folders, + &merge_configs, + |_rust: &mut Rust, config| runner(config), + dirs, + ) } fn env(&self) -> HashMap<&str, String> { @@ -393,7 +409,7 @@ fn dev_options( mobile: bool, args: &mut Vec, run_args: &mut Vec, - features: &mut Option>, + features: &mut Vec, app_settings: &RustAppSettings, ) { let mut dev_args = Vec::new(); @@ -429,35 +445,25 @@ fn dev_options( }) .collect(); args.push("--no-default-features".into()); - if !enable_features.is_empty() { - features.get_or_insert(Vec::new()).extend(enable_features); - } + features.extend(enable_features); } } -// Copied from https://github.com/rust-lang/cargo/blob/69255bb10de7f74511b5cef900a9d102247b6029/src/cargo/core/workspace.rs#L665 -fn expand_member_path(path: &Path) -> crate::Result> { - let path = path.to_str().context("path is not UTF-8 compatible")?; - let res = glob(path).with_context(|| format!("failed to expand glob pattern for {path}"))?; - let res = res - .map(|p| p.with_context(|| format!("failed to expand glob pattern for {path}"))) - .collect::, _>>()?; - Ok(res) -} - -fn get_watch_folders(additional_watch_folders: &[PathBuf]) -> crate::Result> { - let tauri_path = tauri_dir(); - let workspace_path = get_workspace_dir()?; - +fn get_watch_folders( + additional_watch_folders: &[PathBuf], + tauri_dir: &Path, +) -> crate::Result> { // We always want to watch the main tauri folder. - let mut watch_folders = vec![tauri_path.to_path_buf()]; + let mut watch_folders = vec![tauri_dir.to_path_buf()]; + + watch_folders.extend(get_in_workspace_dependency_paths(tauri_dir)?); // Add the additional watch folders, resolving the path from the tauri path if it is relative watch_folders.extend(additional_watch_folders.iter().filter_map(|dir| { let path = if dir.is_absolute() { dir.to_owned() } else { - tauri_path.join(dir) + tauri_dir.join(dir) }; let canonicalized = canonicalize(&path).ok(); @@ -470,43 +476,12 @@ fn get_watch_folders(additional_watch_folders: &[PathBuf]) -> crate::Result { - if expanded_paths.is_empty() { - watch_folders.push(p); - } else { - watch_folders.extend(expanded_paths); - } - } - Err(err) => { - // If this fails cargo itself should fail too. But we still try to keep going with the unexpanded path. - log::error!("Error watching {}: {}", p.display(), err.to_string()); - watch_folders.push(p); - } - }; - } - } - Ok(watch_folders) } impl Rust { - pub fn build_options( - &self, - args: &mut Vec, - features: &mut Option>, - mobile: bool, - ) { - features - .get_or_insert(Vec::new()) - .push("tauri/custom-protocol".into()); + pub fn build_options(&self, args: &mut Vec, features: &mut Vec, mobile: bool) { + features.push("tauri/custom-protocol".into()); if mobile { args.push("--lib".into()); } else { @@ -530,22 +505,30 @@ impl Rust { .map(|c| Box::new(c) as Box) } - fn run_dev_watcher crate::Result>>( + fn run_dev_watcher< + F: Fn(&mut Rust, &ConfigMetadata) -> crate::Result>, + >( &mut self, + config: &mut ConfigMetadata, additional_watch_folders: &[PathBuf], merge_configs: &[&serde_json::Value], - run: Arc, + run: F, + dirs: &Dirs, ) -> crate::Result<()> { - let child = run(self)?; + let child = run(self, config)?; let process = Arc::new(Mutex::new(child)); let (tx, rx) = sync_channel(1); - let frontend_path = frontend_dir(); - let watch_folders = get_watch_folders(additional_watch_folders)?; + let watch_folders = get_watch_folders(additional_watch_folders, dirs.tauri)?; - let common_ancestor = common_path::common_path_all(watch_folders.iter().map(Path::new)) - .expect("watch_folders should not be empty"); + let common_ancestor = common_path::common_path_all( + watch_folders + .iter() + .map(Path::new) + .chain(once(self.app_settings.workspace_dir.as_path())), + ) + .expect("watch_folders should not be empty"); let ignore_matcher = build_ignore_matcher(&common_ancestor); let mut watcher = new_debouncer(Duration::from_secs(1), None, move |r| { @@ -582,22 +565,21 @@ impl Rust { if let Some(event_path) = event.paths.first() { if !ignore_matcher.is_ignore(event_path, event_path.is_dir()) { - if is_configuration_file(self.app_settings.target_platform, event_path) { - if let Ok(config) = reload_config(merge_configs) { - let (manifest, modified) = - rewrite_manifest(config.lock().unwrap().as_ref().unwrap())?; - if modified { - *self.app_settings.manifest.lock().unwrap() = manifest; - // no need to run the watcher logic, the manifest was modified - // and it will trigger the watcher again - continue; - } + if is_configuration_file(self.app_settings.target_platform, event_path) + && reload_config(config, merge_configs, dirs.tauri).is_ok() + { + let (manifest, modified) = rewrite_manifest(config, dirs.tauri)?; + if modified { + *self.app_settings.manifest.lock().unwrap() = manifest; + // no need to run the watcher logic, the manifest was modified + // and it will trigger the watcher again + continue; } } log::info!( "File {} changed. Rebuilding application...", - display_path(event_path.strip_prefix(frontend_path).unwrap_or(event_path)) + display_path(event_path.strip_prefix(dirs.frontend).unwrap_or(event_path)) ); let mut p = process.lock().unwrap(); @@ -610,7 +592,7 @@ impl Rust { break; } } - *p = run(self)?; + *p = run(self, config)?; } } } @@ -681,7 +663,7 @@ pub struct TomlWorkspaceField { #[derive(Clone, Debug, Deserialize)] struct WorkspaceSettings { /// the workspace members. - members: Option>, + // members: Option>, package: Option, } @@ -768,6 +750,7 @@ pub struct RustAppSettings { cargo_config: CargoConfig, target_triple: String, target_platform: TargetPlatform, + workspace_dir: PathBuf, } #[derive(Deserialize)] @@ -846,6 +829,7 @@ impl AppSettings for RustAppSettings { options: &Options, config: &Config, features: &[String], + tauri_dir: &Path, ) -> crate::Result { let arch64bits = self.target_triple.starts_with("x86_64") || self.target_triple.starts_with("aarch64") @@ -876,6 +860,7 @@ impl AppSettings for RustAppSettings { self, features, config, + tauri_dir, config.bundle.clone(), updater_settings, arch64bits, @@ -920,8 +905,8 @@ impl AppSettings for RustAppSettings { Ok(settings) } - fn app_binary_path(&self, options: &Options) -> crate::Result { - let binaries = self.get_binaries(options)?; + fn app_binary_path(&self, options: &Options, tauri_dir: &Path) -> crate::Result { + let binaries = self.get_binaries(options, tauri_dir)?; let bin_name = binaries .iter() .find(|x| x.main()) @@ -929,7 +914,7 @@ impl AppSettings for RustAppSettings { .name(); let out_dir = self - .out_dir(options) + .out_dir(options, tauri_dir) .context("failed to get project out directory")?; let mut path = out_dir.join(bin_name); @@ -947,7 +932,7 @@ impl AppSettings for RustAppSettings { Ok(path) } - fn get_binaries(&self, options: &Options) -> crate::Result> { + fn get_binaries(&self, options: &Options, tauri_dir: &Path) -> crate::Result> { let mut binaries = Vec::new(); if let Some(bins) = &self.cargo_settings.bin { @@ -957,11 +942,12 @@ impl AppSettings for RustAppSettings { .clone() .unwrap_or_default(); for bin in bins { - if let (Some(req_features), Some(opt_features)) = - (&bin.required_features, &options.features) - { + if let Some(req_features) = &bin.required_features { // Check if all required features are enabled. - if !req_features.iter().all(|feat| opt_features.contains(feat)) { + if !req_features + .iter() + .all(|feat| options.features.contains(feat)) + { continue; } } @@ -975,8 +961,6 @@ impl AppSettings for RustAppSettings { } } - let tauri_dir = tauri_dir(); - let mut binaries_paths = std::fs::read_dir(tauri_dir.join("src/bin")) .map(|dir| { dir @@ -1068,8 +1052,12 @@ impl AppSettings for RustAppSettings { } impl RustAppSettings { - pub fn new(config: &Config, manifest: Manifest, target: Option) -> crate::Result { - let tauri_dir = tauri_dir(); + pub fn new( + config: &Config, + manifest: Manifest, + target: Option, + tauri_dir: &Path, + ) -> crate::Result { let cargo_settings = CargoSettings::load(tauri_dir).context("failed to load Cargo settings")?; let cargo_package_settings = match &cargo_settings.package { Some(package_info) => package_info.clone(), @@ -1080,7 +1068,8 @@ impl RustAppSettings { } }; - let ws_package_settings = CargoSettings::load(&get_workspace_dir()?) + let workspace_dir = get_workspace_dir(tauri_dir)?; + let ws_package_settings = CargoSettings::load(&workspace_dir) .context("failed to load Cargo settings from workspace root")? .workspace .and_then(|v| v.package); @@ -1175,6 +1164,7 @@ impl RustAppSettings { cargo_config, target_triple, target_platform, + workspace_dir, }) } @@ -1185,8 +1175,8 @@ impl RustAppSettings { .or_else(|| self.cargo_config.build().target()) } - pub fn out_dir(&self, options: &Options) -> crate::Result { - get_target_dir(self.target(options), options) + pub fn out_dir(&self, options: &Options, tauri_dir: &Path) -> crate::Result { + get_target_dir(self.target(options), options, tauri_dir) } } @@ -1194,12 +1184,29 @@ impl RustAppSettings { pub(crate) struct CargoMetadata { pub(crate) target_directory: PathBuf, pub(crate) workspace_root: PathBuf, + workspace_members: Vec, + packages: Vec, } -pub(crate) fn get_cargo_metadata() -> crate::Result { +#[derive(Deserialize)] +struct Package { + name: String, + id: String, + manifest_path: PathBuf, + dependencies: Vec, +} + +#[derive(Deserialize)] +struct Dependency { + name: String, + /// Local package + path: Option, +} + +pub(crate) fn get_cargo_metadata(tauri_dir: &Path) -> crate::Result { let output = Command::new("cargo") .args(["metadata", "--no-deps", "--format-version", "1"]) - .current_dir(tauri_dir()) + .current_dir(tauri_dir) .output() .map_err(|error| Error::CommandFailed { command: "cargo metadata --no-deps --format-version 1".to_string(), @@ -1216,16 +1223,66 @@ pub(crate) fn get_cargo_metadata() -> crate::Result { serde_json::from_slice(&output.stdout).context("failed to parse cargo metadata") } +/// Get the tauri project crate's dependencies that are inside the workspace +fn get_in_workspace_dependency_paths(tauri_dir: &Path) -> crate::Result> { + let metadata = get_cargo_metadata(tauri_dir)?; + let tauri_project_manifest_path = tauri_dir.join("Cargo.toml"); + let tauri_project_package = metadata + .packages + .iter() + .find(|package| package.manifest_path == tauri_project_manifest_path) + .context("tauri project package doesn't exist in cargo metadata output `packages`")?; + + let workspace_packages = metadata + .workspace_members + .iter() + .map(|member_package_id| { + metadata + .packages + .iter() + .find(|package| package.id == *member_package_id) + .context("workspace member doesn't exist in cargo metadata output `packages`") + }) + .collect::>>()?; + + let mut found_dependency_paths = Vec::new(); + find_dependencies( + tauri_project_package, + &workspace_packages, + &mut found_dependency_paths, + ); + Ok(found_dependency_paths) +} + +fn find_dependencies( + package: &Package, + workspace_packages: &Vec<&Package>, + found_dependency_paths: &mut Vec, +) { + for dependency in &package.dependencies { + if let Some(path) = &dependency.path { + if let Some(package) = workspace_packages.iter().find(|workspace_package| { + workspace_package.name == dependency.name + && path.join("Cargo.toml") == workspace_package.manifest_path + && !found_dependency_paths.contains(path) + }) { + found_dependency_paths.push(path.to_owned()); + find_dependencies(package, workspace_packages, found_dependency_paths); + } + } + } +} + /// Get the cargo target directory based on the provided arguments. /// If "--target-dir" is specified in args, use it as the target directory (relative to current directory). /// Otherwise, use the target directory from cargo metadata. -pub(crate) fn get_cargo_target_dir(args: &[String]) -> crate::Result { +pub(crate) fn get_cargo_target_dir(args: &[String], tauri_dir: &Path) -> crate::Result { let path = if let Some(target) = get_cargo_option(args, "--target-dir") { std::env::current_dir() .context("failed to get current directory")? .join(target) } else { - get_cargo_metadata() + get_cargo_metadata(tauri_dir) .context("failed to run 'cargo metadata' command to get target directory")? .target_directory }; @@ -1235,8 +1292,12 @@ pub(crate) fn get_cargo_target_dir(args: &[String]) -> crate::Result { /// This function determines the 'target' directory and suffixes it with the profile /// to determine where the compiled binary will be located. -fn get_target_dir(triple: Option<&str>, options: &Options) -> crate::Result { - let mut path = get_cargo_target_dir(&options.args)?; +fn get_target_dir( + triple: Option<&str>, + options: &Options, + tauri_dir: &Path, +) -> crate::Result { + let mut path = get_cargo_target_dir(&options.args, tauri_dir)?; if let Some(triple) = triple { path.push(triple); @@ -1261,9 +1322,9 @@ fn get_cargo_option<'a>(args: &'a [String], option: &'a str) -> Option<&'a str> } /// Executes `cargo metadata` to get the workspace directory. -pub fn get_workspace_dir() -> crate::Result { +pub fn get_workspace_dir(tauri_dir: &Path) -> crate::Result { Ok( - get_cargo_metadata() + get_cargo_metadata(tauri_dir) .context("failed to run 'cargo metadata' command to get workspace directory")? .workspace_root, ) @@ -1289,6 +1350,7 @@ fn tauri_config_to_bundle_settings( settings: &RustAppSettings, features: &[String], tauri_config: &Config, + tauri_dir: &Path, config: crate::helpers::config::BundleConfig, updater_config: Option, arch64bits: bool, @@ -1433,14 +1495,16 @@ fn tauri_config_to_bundle_settings( .map(tauri_bundler::bundle::Entitlements::Path) } else { let mut app_links_entitlements = plist::Dictionary::new(); - app_links_entitlements.insert( - "com.apple.developer.associated-domains".to_string(), - domains - .into_iter() - .map(|domain| format!("applinks:{domain}").into()) - .collect::>() - .into(), - ); + if !domains.is_empty() { + app_links_entitlements.insert( + "com.apple.developer.associated-domains".to_string(), + domains + .into_iter() + .map(|domain| format!("applinks:{domain}").into()) + .collect::>() + .into(), + ); + } let entitlements = if let Some(user_provided_entitlements) = config.macos.entitlements { crate::helpers::plist::merge_plist(vec![ PathBuf::from(user_provided_entitlements).into(), @@ -1570,7 +1634,7 @@ fn tauri_config_to_bundle_settings( info_plist: { let mut src_plists = vec![]; - let path = tauri_dir().join("Info.plist"); + let path = tauri_dir.join("Info.plist"); if path.exists() { src_plists.push(path.into()); } @@ -1612,7 +1676,7 @@ fn tauri_config_to_bundle_settings( .unwrap() }) }), - license_file: config.license_file.map(|l| tauri_dir().join(l)), + license_file: config.license_file.map(|l| tauri_dir.join(l)), updater: updater_config, ..Default::default() }) @@ -1749,7 +1813,7 @@ mod tests { #[test] fn parse_target_dir_from_opts() { - crate::helpers::app_paths::resolve(); + let dirs = crate::helpers::app_paths::resolve_dirs(); let current_dir = std::env::current_dir().unwrap(); let options = Options { @@ -1766,11 +1830,11 @@ mod tests { }; assert_eq!( - get_target_dir(None, &options).unwrap(), + get_target_dir(None, &options, dirs.tauri).unwrap(), current_dir.join("path/to/some/dir/release") ); assert_eq!( - get_target_dir(Some("x86_64-pc-windows-msvc"), &options).unwrap(), + get_target_dir(Some("x86_64-pc-windows-msvc"), &options, dirs.tauri).unwrap(), current_dir .join("path/to/some/dir") .join("x86_64-pc-windows-msvc") @@ -1789,23 +1853,27 @@ mod tests { }; #[cfg(windows)] - assert!(get_target_dir(Some("x86_64-pc-windows-msvc"), &options) - .unwrap() - .ends_with("x86_64-pc-windows-msvc\\release")); + assert!( + get_target_dir(Some("x86_64-pc-windows-msvc"), &options, dirs.tauri) + .unwrap() + .ends_with("x86_64-pc-windows-msvc\\release") + ); #[cfg(not(windows))] - assert!(get_target_dir(Some("x86_64-pc-windows-msvc"), &options) - .unwrap() - .ends_with("x86_64-pc-windows-msvc/release")); + assert!( + get_target_dir(Some("x86_64-pc-windows-msvc"), &options, dirs.tauri) + .unwrap() + .ends_with("x86_64-pc-windows-msvc/release") + ); #[cfg(windows)] { std::env::set_var("CARGO_TARGET_DIR", "D:\\path\\to\\env\\dir"); assert_eq!( - get_target_dir(None, &options).unwrap(), + get_target_dir(None, &options, dirs.tauri).unwrap(), PathBuf::from("D:\\path\\to\\env\\dir\\release") ); assert_eq!( - get_target_dir(Some("x86_64-pc-windows-msvc"), &options).unwrap(), + get_target_dir(Some("x86_64-pc-windows-msvc"), &options, dirs.tauri).unwrap(), PathBuf::from("D:\\path\\to\\env\\dir\\x86_64-pc-windows-msvc\\release") ); } @@ -1814,11 +1882,11 @@ mod tests { { std::env::set_var("CARGO_TARGET_DIR", "/path/to/env/dir"); assert_eq!( - get_target_dir(None, &options).unwrap(), + get_target_dir(None, &options, dirs.tauri).unwrap(), PathBuf::from("/path/to/env/dir/release") ); assert_eq!( - get_target_dir(Some("x86_64-pc-windows-msvc"), &options).unwrap(), + get_target_dir(Some("x86_64-pc-windows-msvc"), &options, dirs.tauri).unwrap(), PathBuf::from("/path/to/env/dir/x86_64-pc-windows-msvc/release") ); } diff --git a/crates/tauri-cli/src/interface/rust/desktop.rs b/crates/tauri-cli/src/interface/rust/desktop.rs index e747f236b..15cd503e6 100644 --- a/crates/tauri-cli/src/interface/rust/desktop.rs +++ b/crates/tauri-cli/src/interface/rust/desktop.rs @@ -12,7 +12,7 @@ use shared_child::SharedChild; use std::{ fs, io::{BufReader, ErrorKind, Write}, - path::PathBuf, + path::{Path, PathBuf}, process::{Command, ExitStatus, Stdio}, sync::{ atomic::{AtomicBool, Ordering}, @@ -158,9 +158,10 @@ pub fn build( available_targets: &mut Option>, config_features: Vec, main_binary_name: Option<&str>, + tauri_dir: &Path, ) -> crate::Result { - let out_dir = app_settings.out_dir(&options)?; - let bin_path = app_settings.app_binary_path(&options)?; + let out_dir = app_settings.out_dir(&options, tauri_dir)?; + let bin_path = app_settings.app_binary_path(&options, tauri_dir)?; if !std::env::var("STATIC_VCRUNTIME").is_ok_and(|v| v == "false") { std::env::set_var("STATIC_VCRUNTIME", "true"); @@ -182,7 +183,7 @@ pub fn build( options.target.replace(triple.into()); let triple_out_dir = app_settings - .out_dir(&options) + .out_dir(&options, tauri_dir) .with_context(|| format!("failed to get {triple} out dir"))?; build_production_app(options, available_targets, config_features.clone()) @@ -262,9 +263,7 @@ fn cargo_command( build_cmd.args(&options.args); let mut features = config_features; - if let Some(f) = options.features { - features.extend(f); - } + features.extend(options.features); if !features.is_empty() { build_cmd.arg("--features"); build_cmd.arg(features.join(",")); diff --git a/crates/tauri-cli/src/interface/rust/manifest.rs b/crates/tauri-cli/src/interface/rust/manifest.rs index 03e9768bd..40d307c2b 100644 --- a/crates/tauri-cli/src/interface/rust/manifest.rs +++ b/crates/tauri-cli/src/interface/rust/manifest.rs @@ -4,10 +4,7 @@ use crate::{ error::{Context, ErrorExt}, - helpers::{ - app_paths::tauri_dir, - config::{Config, PatternKind}, - }, + helpers::config::{Config, PatternKind}, }; use itertools::Itertools; @@ -272,8 +269,8 @@ fn inject_features( Ok(persist) } -pub fn rewrite_manifest(config: &Config) -> crate::Result<(Manifest, bool)> { - let manifest_path = tauri_dir().join("Cargo.toml"); +pub fn rewrite_manifest(config: &Config, tauri_dir: &Path) -> crate::Result<(Manifest, bool)> { + let manifest_path = tauri_dir.join("Cargo.toml"); let (mut manifest, original_manifest_str) = read_manifest(&manifest_path)?; let mut dependencies = Vec::new(); @@ -354,10 +351,7 @@ mod tests { } else { None }; - if let Some(f) = item_table - .and_then(|t| t.get("features").cloned()) - .and_then(|f| f.as_array().cloned()) - { + if let Some(f) = item_table.and_then(|t| t.get("features")?.as_array().cloned()) { for feature in f.iter() { let feature = feature.as_str().expect("feature is not a string"); if !dep.all_cli_managed_features.contains(&feature) { diff --git a/crates/tauri-cli/src/migrate/migrations/v1/mod.rs b/crates/tauri-cli/src/migrate/migrations/v1/mod.rs index 14a9cd7c4..6372db7b5 100644 --- a/crates/tauri-cli/src/migrate/migrations/v1/mod.rs +++ b/crates/tauri-cli/src/migrate/migrations/v1/mod.rs @@ -2,35 +2,31 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use crate::{ - error::Context, - helpers::app_paths::{frontend_dir, tauri_dir}, - Result, -}; +use crate::{error::Context, helpers::app_paths::Dirs, Result}; mod config; mod frontend; mod manifest; -pub fn run() -> Result<()> { - let tauri_dir = tauri_dir(); - let frontend_dir = frontend_dir(); - - let mut migrated = config::migrate(tauri_dir).context("Could not migrate config")?; - manifest::migrate(tauri_dir).context("Could not migrate manifest")?; - let plugins = frontend::migrate(frontend_dir)?; +pub fn run(dirs: &Dirs) -> Result<()> { + let mut migrated = config::migrate(dirs.tauri).context("Could not migrate config")?; + manifest::migrate(dirs.tauri).context("Could not migrate manifest")?; + let plugins = frontend::migrate(dirs.frontend)?; migrated.plugins.extend(plugins); // Add plugins for plugin in migrated.plugins { - crate::add::run(crate::add::Options { - plugin: plugin.clone(), - branch: None, - tag: None, - rev: None, - no_fmt: false, - }) + crate::add::run( + crate::add::Options { + plugin: plugin.clone(), + branch: None, + tag: None, + rev: None, + no_fmt: false, + }, + dirs, + ) .with_context(|| format!("Could not migrate plugin '{plugin}'"))?; } diff --git a/crates/tauri-cli/src/migrate/migrations/v2_beta.rs b/crates/tauri-cli/src/migrate/migrations/v2_beta.rs index aad493670..dcb6e52c2 100644 --- a/crates/tauri-cli/src/migrate/migrations/v2_beta.rs +++ b/crates/tauri-cli/src/migrate/migrations/v2_beta.rs @@ -4,10 +4,7 @@ use crate::{ error::{Context, ErrorExt}, - helpers::{ - app_paths::{frontend_dir, tauri_dir}, - npm::PackageManager, - }, + helpers::{app_paths::Dirs, npm::PackageManager}, interface::rust::manifest::{read_manifest, serialize_manifest}, Result, }; @@ -16,17 +13,14 @@ use std::{fs::read_to_string, path::Path}; use toml_edit::{DocumentMut, Item, Table, TableLike, Value}; -pub fn run() -> Result<()> { - let frontend_dir = frontend_dir(); - let tauri_dir = tauri_dir(); - - let manifest_path = tauri_dir.join("Cargo.toml"); +pub fn run(dirs: &Dirs) -> Result<()> { + let manifest_path = dirs.tauri.join("Cargo.toml"); let (mut manifest, _) = read_manifest(&manifest_path)?; migrate_manifest(&mut manifest)?; - migrate_permissions(tauri_dir)?; + migrate_permissions(dirs.tauri)?; - migrate_npm_dependencies(frontend_dir)?; + migrate_npm_dependencies(dirs.frontend)?; std::fs::write(&manifest_path, serialize_manifest(&manifest)) .fs_context("failed to rewrite Cargo manifest", &manifest_path)?; diff --git a/crates/tauri-cli/src/migrate/mod.rs b/crates/tauri-cli/src/migrate/mod.rs index 575a2516b..9a75089b0 100644 --- a/crates/tauri-cli/src/migrate/mod.rs +++ b/crates/tauri-cli/src/migrate/mod.rs @@ -4,10 +4,7 @@ use crate::{ error::{bail, Context, ErrorExt}, - helpers::{ - app_paths::tauri_dir, - cargo_manifest::{crate_version, CargoLock, CargoManifest}, - }, + helpers::cargo_manifest::{crate_version, CargoLock, CargoManifest}, interface::rust::get_workspace_dir, Result, }; @@ -17,22 +14,20 @@ use std::{fs::read_to_string, str::FromStr}; mod migrations; pub fn command() -> Result<()> { - crate::helpers::app_paths::resolve(); + let dirs = crate::helpers::app_paths::resolve_dirs(); - let tauri_dir = tauri_dir(); - - let manifest_contents = read_to_string(tauri_dir.join("Cargo.toml")).fs_context( + let manifest_contents = read_to_string(dirs.tauri.join("Cargo.toml")).fs_context( "failed to read Cargo manifest", - tauri_dir.join("Cargo.toml"), + dirs.tauri.join("Cargo.toml"), )?; let manifest = toml::from_str::(&manifest_contents).with_context(|| { format!( "failed to parse Cargo manifest {}", - tauri_dir.join("Cargo.toml").display() + dirs.tauri.join("Cargo.toml").display() ) })?; - let workspace_dir = get_workspace_dir()?; + let workspace_dir = get_workspace_dir(dirs.tauri)?; let lock_path = workspace_dir.join("Cargo.lock"); let lock = if lock_path.exists() { let lockfile_contents = @@ -44,19 +39,19 @@ pub fn command() -> Result<()> { None }; - let tauri_version = crate_version(tauri_dir, Some(&manifest), lock.as_ref(), "tauri") + let tauri_version = crate_version(dirs.tauri, Some(&manifest), lock.as_ref(), "tauri") .version .context("failed to get tauri version")?; let tauri_version = semver::Version::from_str(&tauri_version) .with_context(|| format!("failed to parse tauri version {tauri_version}"))?; if tauri_version.major == 1 { - migrations::v1::run().context("failed to migrate from v1")?; + migrations::v1::run(&dirs).context("failed to migrate from v1")?; } else if tauri_version.major == 2 { if let Some((pre, _number)) = tauri_version.pre.as_str().split_once('.') { match pre { "beta" => { - migrations::v2_beta::run().context("failed to migrate from v2 beta")?; + migrations::v2_beta::run(&dirs).context("failed to migrate from v2 beta")?; } "alpha" => { bail!( diff --git a/crates/tauri-cli/src/mobile/android/android_studio_script.rs b/crates/tauri-cli/src/mobile/android/android_studio_script.rs index c3dfdff4b..ce1d6ba8e 100644 --- a/crates/tauri-cli/src/mobile/android/android_studio_script.rs +++ b/crates/tauri-cli/src/mobile/android/android_studio_script.rs @@ -5,7 +5,7 @@ use super::{detect_target_ok, ensure_init, env, get_app, get_config, read_options, MobileTarget}; use crate::{ error::{Context, ErrorExt}, - helpers::config::{get as get_tauri_config, reload as reload_tauri_config}, + helpers::config::{get_config as get_tauri_config, reload_config as reload_tauri_config}, interface::{AppInterface, Interface}, mobile::CliOptions, Error, Result, @@ -38,7 +38,7 @@ pub struct Options { } pub fn command(options: Options) -> Result<()> { - crate::helpers::app_paths::resolve(); + let dirs = crate::helpers::app_paths::resolve_dirs(); let profile = if options.release { Profile::Release @@ -46,41 +46,32 @@ pub fn command(options: Options) -> Result<()> { Profile::Debug }; - let (tauri_config, cli_options) = { - let tauri_config = get_tauri_config(tauri_utils::platform::Target::Android, &[])?; - let cli_options = { - let tauri_config_guard = tauri_config.lock().unwrap(); - let tauri_config_ = tauri_config_guard.as_ref().unwrap(); - read_options(tauri_config_) - }; + let mut tauri_config = get_tauri_config(tauri_utils::platform::Target::Android, &[], dirs.tauri)?; + let cli_options = read_options(&tauri_config); - let tauri_config = if cli_options.config.is_empty() { - tauri_config - } else { - // reload config with merges from the android dev|build script - reload_tauri_config( - &cli_options - .config - .iter() - .map(|conf| &conf.0) - .collect::>(), - )? - }; - - (tauri_config, cli_options) + if !cli_options.config.is_empty() { + // reload config with merges from the android dev|build script + reload_tauri_config( + &mut tauri_config, + &cli_options + .config + .iter() + .map(|conf| &conf.0) + .collect::>(), + dirs.tauri, + )? }; let (config, metadata) = { - let tauri_config_guard = tauri_config.lock().unwrap(); - let tauri_config_ = tauri_config_guard.as_ref().unwrap(); let (config, metadata) = get_config( &get_app( MobileTarget::Android, - tauri_config_, - &AppInterface::new(tauri_config_, None)?, + &tauri_config, + &AppInterface::new(&tauri_config, None, dirs.tauri)?, + dirs.tauri, ), - tauri_config_, - None, + &tauri_config, + &[], &cli_options, ); (config, metadata) @@ -95,7 +86,8 @@ pub fn command(options: Options) -> Result<()> { )?; if !cli_options.config.is_empty() { - crate::helpers::config::merge_with( + crate::helpers::config::merge_config_with( + &mut tauri_config, &cli_options .config .iter() @@ -107,16 +99,7 @@ pub fn command(options: Options) -> Result<()> { let env = env(std::env::var("CI").is_ok())?; if cli_options.dev { - let dev_url = tauri_config - .lock() - .unwrap() - .as_ref() - .unwrap() - .build - .dev_url - .clone(); - - if let Some(url) = dev_url { + if let Some(url) = &tauri_config.build.dev_url { let localhost = match url.host() { Some(url::Host::Domain(d)) => d == "localhost", Some(url::Host::Ipv4(i)) => i == std::net::Ipv4Addr::LOCALHOST, diff --git a/crates/tauri-cli/src/mobile/android/build.rs b/crates/tauri-cli/src/mobile/android/build.rs index 4aae6df68..7b85ef47d 100644 --- a/crates/tauri-cli/src/mobile/android/build.rs +++ b/crates/tauri-cli/src/mobile/android/build.rs @@ -10,8 +10,8 @@ use crate::{ build::Options as BuildOptions, error::Context, helpers::{ - app_paths::tauri_dir, - config::{get as get_tauri_config, ConfigHandle}, + app_paths::Dirs, + config::{get_config as get_tauri_config, ConfigMetadata}, flock, }, interface::{AppInterface, Interface, Options as InterfaceOptions}, @@ -27,6 +27,7 @@ use cargo_mobile2::{ }; use std::env::set_current_dir; +use std::path::Path; #[derive(Debug, Clone, Parser)] #[clap( @@ -48,7 +49,7 @@ pub struct Options { pub targets: Option>, /// List of cargo features to activate #[clap(short, long, action = ArgAction::Append, num_args(0..))] - pub features: Option>, + pub features: Vec, /// JSON strings or paths to JSON, JSON5 or TOML files to merge with the default configuration file /// /// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts. @@ -63,10 +64,12 @@ pub struct Options { pub split_per_abi: bool, /// Build APKs. #[clap(long)] - pub apk: Option, + pub apk: bool, /// Build AABs. #[clap(long)] - pub aab: Option, + pub aab: bool, + #[clap(skip)] + pub skip_bundle: bool, /// Open Android Studio #[clap(short, long)] pub open: bool, @@ -116,8 +119,25 @@ pub struct BuiltApplication { } pub fn command(options: Options, noise_level: NoiseLevel) -> Result { - crate::helpers::app_paths::resolve(); + let dirs = crate::helpers::app_paths::resolve_dirs(); + let tauri_config = get_tauri_config( + tauri_utils::platform::Target::Android, + &options + .config + .iter() + .map(|conf| &conf.0) + .collect::>(), + dirs.tauri, + )?; + run(options, noise_level, &dirs, &tauri_config) +} +pub fn run( + options: Options, + noise_level: NoiseLevel, + dirs: &Dirs, + tauri_config: &ConfigMetadata, +) -> Result { delete_codegen_vars(); let mut build_options: BuildOptions = options.clone().into(); @@ -133,26 +153,15 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result>(), - )?; let (interface, config, metadata) = { - let tauri_config_guard = tauri_config.lock().unwrap(); - let tauri_config_ = tauri_config_guard.as_ref().unwrap(); - - let interface = AppInterface::new(tauri_config_, build_options.target.clone())?; + let interface = AppInterface::new(tauri_config, build_options.target.clone(), dirs.tauri)?; interface.build_options(&mut Vec::new(), &mut build_options.features, true); - let app = get_app(MobileTarget::Android, tauri_config_, &interface); + let app = get_app(MobileTarget::Android, tauri_config, &interface, dirs.tauri); let (config, metadata) = get_config( &app, - tauri_config_, - build_options.features.as_ref(), + tauri_config, + &build_options.features, &Default::default(), ); (interface, config, metadata) @@ -164,11 +173,10 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result Result Result Result { - if !(options.apk.is_some() || options.aab.is_some()) { + if !(options.skip_bundle || options.apk || options.aab) { // if the user didn't specify the format to build, we'll do both - options.apk = Some(true); - options.aab = Some(true); + options.apk = true; + options.aab = true; } let interface_options = InterfaceOptions { @@ -257,7 +258,7 @@ fn run_build( }; let app_settings = interface.app_settings(); - let out_dir = app_settings.out_dir(&interface_options)?; + let out_dir = app_settings.out_dir(&interface_options, tauri_dir)?; let _lock = flock::open_rw(out_dir.join("lock").with_extension("android"), "Android")?; let cli_options = CliOptions { @@ -269,11 +270,11 @@ fn run_build( config: build_options.config, target_device: options.target_device.clone(), }; - let handle = write_options(tauri_config.lock().unwrap().as_ref().unwrap(), cli_options)?; + let handle = write_options(tauri_config, cli_options)?; - inject_resources(config, tauri_config.lock().unwrap().as_ref().unwrap())?; + inject_resources(config, tauri_config)?; - let apk_outputs = if options.apk.unwrap_or_default() { + let apk_outputs = if options.apk { apk::build( config, env, @@ -287,7 +288,7 @@ fn run_build( Vec::new() }; - let aab_outputs = if options.aab.unwrap_or_default() { + let aab_outputs = if options.aab { aab::build( config, env, diff --git a/crates/tauri-cli/src/mobile/android/dev.rs b/crates/tauri-cli/src/mobile/android/dev.rs index e5961bfa8..a4cfaacd4 100644 --- a/crates/tauri-cli/src/mobile/android/dev.rs +++ b/crates/tauri-cli/src/mobile/android/dev.rs @@ -10,8 +10,8 @@ use crate::{ dev::Options as DevOptions, error::{Context, ErrorExt}, helpers::{ - app_paths::tauri_dir, - config::{get as get_tauri_config, ConfigHandle}, + app_paths::Dirs, + config::{get_config as get_tauri_config, ConfigMetadata}, flock, }, interface::{AppInterface, Interface, MobileOptions, Options as InterfaceOptions}, @@ -45,7 +45,7 @@ use std::{env::set_current_dir, net::Ipv4Addr, path::PathBuf}; pub struct Options { /// List of cargo features to activate #[clap(short, long, action = ArgAction::Append, num_args(0..))] - pub features: Option>, + pub features: Vec, /// Exit on panic #[clap(short, long)] exit_on_panic: bool, @@ -131,16 +131,16 @@ impl From for DevOptions { } pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> { - crate::helpers::app_paths::resolve(); + let dirs = crate::helpers::app_paths::resolve_dirs(); - let result = run_command(options, noise_level); + let result = run_command(options, noise_level, dirs); if result.is_err() { crate::dev::kill_before_dev_process(); } result } -fn run_command(options: Options, noise_level: NoiseLevel) -> Result<()> { +fn run_command(options: Options, noise_level: NoiseLevel, dirs: Dirs) -> Result<()> { delete_codegen_vars(); // setup env additions before calling env() if let Some(root_certificate_path) = &options.root_certificate_path { @@ -160,6 +160,7 @@ fn run_command(options: Options, noise_level: NoiseLevel) -> Result<()> { .iter() .map(|conf| &conf.0) .collect::>(), + dirs.tauri, )?; let env = env(false)?; @@ -183,23 +184,19 @@ fn run_command(options: Options, noise_level: NoiseLevel) -> Result<()> { dev_options.target = Some(target_triple); let (interface, config, metadata) = { - let tauri_config_guard = tauri_config.lock().unwrap(); - let tauri_config_ = tauri_config_guard.as_ref().unwrap(); + let interface = AppInterface::new(&tauri_config, dev_options.target.clone(), dirs.tauri)?; - let interface = AppInterface::new(tauri_config_, dev_options.target.clone())?; - - let app = get_app(MobileTarget::Android, tauri_config_, &interface); + let app = get_app(MobileTarget::Android, &tauri_config, &interface, dirs.tauri); let (config, metadata) = get_config( &app, - tauri_config_, + &tauri_config, dev_options.features.as_ref(), &Default::default(), ); (interface, config, metadata) }; - let tauri_path = tauri_dir(); - set_current_dir(tauri_path).context("failed to set current directory to Tauri directory")?; + set_current_dir(dirs.tauri).context("failed to set current directory to Tauri directory")?; ensure_init( &tauri_config, @@ -218,6 +215,7 @@ fn run_command(options: Options, noise_level: NoiseLevel) -> Result<()> { &config, &metadata, noise_level, + &dirs, ) } @@ -226,12 +224,13 @@ fn run_dev( mut interface: AppInterface, options: Options, mut dev_options: DevOptions, - tauri_config: ConfigHandle, + mut tauri_config: ConfigMetadata, device: Option, mut env: Env, config: &AndroidConfig, metadata: &AndroidMetadata, noise_level: NoiseLevel, + dirs: &Dirs, ) -> Result<()> { // when --host is provided or running on a physical device or resolving 0.0.0.0 we must use the network IP if options.host.0.is_some() @@ -239,25 +238,22 @@ fn run_dev( .as_ref() .map(|device| !device.serial_no().starts_with("emulator")) .unwrap_or(false) - || tauri_config - .lock() - .unwrap() - .as_ref() - .unwrap() - .build - .dev_url - .as_ref() - .is_some_and(|url| { - matches!( - url.host(), - Some(Host::Ipv4(i)) if i == Ipv4Addr::UNSPECIFIED - ) - }) + || tauri_config.build.dev_url.as_ref().is_some_and(|url| { + matches!( + url.host(), + Some(Host::Ipv4(i)) if i == Ipv4Addr::UNSPECIFIED + ) + }) { - use_network_address_for_dev_url(&tauri_config, &mut dev_options, options.force_ip_prompt)?; + use_network_address_for_dev_url( + &mut tauri_config, + &mut dev_options, + options.force_ip_prompt, + dirs.tauri, + )?; } - crate::dev::setup(&interface, &mut dev_options, tauri_config)?; + crate::dev::setup(&interface, &mut dev_options, &mut tauri_config, dirs)?; let interface_options = InterfaceOptions { debug: !dev_options.release_mode, @@ -266,12 +262,12 @@ fn run_dev( }; let app_settings = interface.app_settings(); - let out_dir = app_settings.out_dir(&interface_options)?; + let out_dir = app_settings.out_dir(&interface_options, dirs.tauri)?; let _lock = flock::open_rw(out_dir.join("lock").with_extension("android"), "Android")?; configure_cargo(&mut env, config)?; - generate_tauri_properties(config, tauri_config.lock().unwrap().as_ref().unwrap(), true)?; + generate_tauri_properties(config, &tauri_config, true)?; let installed_targets = crate::interface::rust::installation::installed_targets().unwrap_or_default(); @@ -307,6 +303,7 @@ fn run_dev( let open = options.open; interface.mobile_dev( + &mut tauri_config, MobileOptions { debug: !options.release_mode, features: options.features, @@ -315,7 +312,7 @@ fn run_dev( no_watch: options.no_watch, additional_watch_folders: options.additional_watch_folders, }, - |options| { + |options, tauri_config| { let cli_options = CliOptions { dev: true, features: options.features.clone(), @@ -329,9 +326,9 @@ fn run_dev( }), }; - let _handle = write_options(tauri_config.lock().unwrap().as_ref().unwrap(), cli_options)?; + let _handle = write_options(tauri_config, cli_options)?; - inject_resources(config, tauri_config.lock().unwrap().as_ref().unwrap())?; + inject_resources(config, tauri_config)?; if open { open_and_wait(config, &env) @@ -347,6 +344,7 @@ fn run_dev( open_and_wait(config, &env) } }, + dirs, ) } diff --git a/crates/tauri-cli/src/mobile/android/mod.rs b/crates/tauri-cli/src/mobile/android/mod.rs index 1990cf2f4..002ed73ac 100644 --- a/crates/tauri-cli/src/mobile/android/mod.rs +++ b/crates/tauri-cli/src/mobile/android/mod.rs @@ -104,18 +104,17 @@ enum Commands { } pub fn command(cli: Cli, verbosity: u8) -> Result<()> { + let dirs = crate::helpers::app_paths::resolve_dirs(); let noise_level = NoiseLevel::from_occurrences(verbosity as u64); match cli.command { - Commands::Init(options) => { - crate::helpers::app_paths::resolve(); - init_command( - MobileTarget::Android, - options.ci, - false, - options.skip_targets_install, - options.config, - )? - } + Commands::Init(options) => init_command( + MobileTarget::Android, + options.ci, + false, + options.skip_targets_install, + options.config, + &dirs, + )?, Commands::Dev(options) => dev::command(options, noise_level)?, Commands::Build(options) => build::command(options, noise_level).map(|_| ())?, Commands::Run(options) => run::command(options, noise_level)?, @@ -128,19 +127,14 @@ pub fn command(cli: Cli, verbosity: u8) -> Result<()> { pub fn get_config( app: &App, config: &TauriConfig, - features: Option<&Vec>, + features: &[String], cli_options: &CliOptions, ) -> (AndroidConfig, AndroidMetadata) { let mut android_options = cli_options.clone(); - if let Some(features) = features { - android_options - .features - .get_or_insert(Vec::new()) - .extend_from_slice(features); - } + android_options.features.extend_from_slice(features); let raw = RawAndroidConfig { - features: android_options.features.clone(), + features: Some(android_options.features.clone()), logcat_filter_specs: vec![ "RustStdoutStderr".into(), format!( @@ -161,7 +155,7 @@ pub fn get_config( let metadata = AndroidMetadata { supported: true, cargo_args: Some(android_options.args), - features: android_options.features, + features: Some(android_options.features), ..Default::default() }; @@ -257,8 +251,8 @@ fn ensure_java() -> Result<()> { fn ensure_sdk(non_interactive: bool) -> Result<()> { let android_home = std::env::var_os("ANDROID_HOME") - .map(PathBuf::from) - .or_else(|| std::env::var_os("ANDROID_SDK_ROOT").map(PathBuf::from)); + .or_else(|| std::env::var_os("ANDROID_SDK_ROOT")) + .map(PathBuf::from); if !android_home.as_ref().is_some_and(|v| v.exists()) { log::info!( "ANDROID_HOME {}, trying to locate Android SDK...", @@ -354,8 +348,8 @@ fn ensure_sdk(non_interactive: bool) -> Result<()> { fn ensure_ndk(non_interactive: bool) -> Result<()> { // re-evaluate ANDROID_HOME let android_home = std::env::var_os("ANDROID_HOME") + .or_else(|| std::env::var_os("ANDROID_SDK_ROOT")) .map(PathBuf::from) - .or_else(|| std::env::var_os("ANDROID_SDK_ROOT").map(PathBuf::from)) .context("Failed to locate Android SDK")?; let mut installed_ndks = read_dir(android_home.join("ndk")) .map(|dir| { diff --git a/crates/tauri-cli/src/mobile/android/run.rs b/crates/tauri-cli/src/mobile/android/run.rs index 9c276c4d9..c07c6ba6d 100644 --- a/crates/tauri-cli/src/mobile/android/run.rs +++ b/crates/tauri-cli/src/mobile/android/run.rs @@ -13,6 +13,7 @@ use std::path::PathBuf; use super::{configure_cargo, device_prompt, env}; use crate::{ error::Context, + helpers::config::ConfigMetadata, interface::{DevProcess, Interface, WatcherOptions}, mobile::{DevChild, TargetDevice}, ConfigValue, Result, @@ -29,7 +30,7 @@ pub struct Options { pub release: bool, /// List of cargo features to activate #[clap(short, long, action = ArgAction::Append, num_args(0..))] - pub features: Option>, + pub features: Vec, /// JSON strings or paths to JSON, JSON5 or TOML files to merge with the default configuration file /// /// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts. @@ -77,7 +78,17 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> { } }; - let mut built_application = super::build::command( + let dirs = crate::helpers::app_paths::resolve_dirs(); + let mut tauri_config = crate::helpers::config::get_config( + tauri_utils::platform::Target::Android, + &options + .config + .iter() + .map(|conf| &conf.0) + .collect::>(), + dirs.tauri, + )?; + let mut built_application = super::build::run( super::build::Options { debug: !options.release, targets: device.as_ref().map(|d| { @@ -90,8 +101,9 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> { features: options.features, config: options.config.clone(), split_per_abi: true, - apk: Some(false), - aab: Some(false), + apk: false, + aab: false, + skip_bundle: false, open: options.open, ci: false, args: options.args, @@ -102,6 +114,8 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> { }), }, noise_level, + &dirs, + &tauri_config, )?; configure_cargo(&mut env, &built_application.config)?; @@ -111,7 +125,7 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> { if let Some(device) = device { let config = built_application.config.clone(); let release = options.release; - let runner = move || { + let runner = move |_tauri_config: &ConfigMetadata| { device .run( &config, @@ -136,14 +150,16 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> { }; if options.no_watch { - runner()?; + runner(&tauri_config)?; } else { built_application.interface.watch( + &mut tauri_config, WatcherOptions { config: options.config, additional_watch_folders: options.additional_watch_folders, }, runner, + &dirs, )?; } } diff --git a/crates/tauri-cli/src/mobile/init.rs b/crates/tauri-cli/src/mobile/init.rs index 2a3cab0b4..0a7324970 100644 --- a/crates/tauri-cli/src/mobile/init.rs +++ b/crates/tauri-cli/src/mobile/init.rs @@ -4,7 +4,8 @@ use super::{get_app, Target}; use crate::{ - helpers::{config::get as get_tauri_config, template::JsonMap}, + helpers::app_paths::Dirs, + helpers::{config::get_config as get_tauri_config, template::JsonMap}, interface::{AppInterface, Interface}, ConfigValue, Result, }; @@ -28,6 +29,7 @@ pub fn command( reinstall_deps: bool, skip_targets_install: bool, config: Vec, + dirs: &Dirs, ) -> Result<()> { let wrapper = TextWrapper::default(); @@ -38,6 +40,7 @@ pub fn command( reinstall_deps, skip_targets_install, config, + dirs, )?; Ok(()) } @@ -49,19 +52,19 @@ pub fn exec( #[allow(unused_variables)] reinstall_deps: bool, skip_targets_install: bool, config: Vec, + dirs: &Dirs, ) -> Result { let tauri_config = get_tauri_config( target.platform_target(), &config.iter().map(|conf| &conf.0).collect::>(), + dirs.tauri, )?; - let tauri_config_guard = tauri_config.lock().unwrap(); - let tauri_config_ = tauri_config_guard.as_ref().unwrap(); - let app = get_app( target, - tauri_config_, - &AppInterface::new(tauri_config_, None)?, + &tauri_config, + &AppInterface::new(&tauri_config, None, dirs.tauri)?, + dirs.tauri, ); let (handlebars, mut map) = handlebars(&app); @@ -135,7 +138,7 @@ pub fn exec( Target::Android => { let _env = super::android::env(non_interactive)?; let (config, metadata) = - super::android::get_config(&app, tauri_config_, None, &Default::default()); + super::android::get_config(&app, &tauri_config, &[], &Default::default()); map.insert("android", &config); super::android::project::gen( &config, @@ -150,10 +153,10 @@ pub fn exec( // Generate Xcode project Target::Ios => { let (config, metadata) = - super::ios::get_config(&app, tauri_config_, None, &Default::default())?; + super::ios::get_config(&app, &tauri_config, &[], &Default::default(), dirs.tauri)?; map.insert("apple", &config); super::ios::project::gen( - tauri_config_, + &tauri_config, &config, &metadata, (handlebars, map), diff --git a/crates/tauri-cli/src/mobile/ios/build.rs b/crates/tauri-cli/src/mobile/ios/build.rs index 1b00011e0..36cdb3be3 100644 --- a/crates/tauri-cli/src/mobile/ios/build.rs +++ b/crates/tauri-cli/src/mobile/ios/build.rs @@ -11,8 +11,8 @@ use crate::{ build::Options as BuildOptions, error::{Context, ErrorExt}, helpers::{ - app_paths::tauri_dir, - config::{get as get_tauri_config, ConfigHandle}, + app_paths::Dirs, + config::{get_config as get_tauri_config, ConfigMetadata}, flock, plist::merge_plist, }, @@ -60,7 +60,7 @@ pub struct Options { pub targets: Option>, /// List of cargo features to activate #[clap(short, long, action = ArgAction::Append, num_args(0..))] - pub features: Option>, + pub features: Vec, /// JSON strings or paths to JSON, JSON5 or TOML files to merge with the default configuration file /// /// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts. @@ -169,9 +169,7 @@ pub struct BuiltApplication { export_options_tmp: Option, } -pub fn command(options: Options, noise_level: NoiseLevel) -> Result { - crate::helpers::app_paths::resolve(); - +pub fn command(options: Options, noise_level: NoiseLevel, dirs: &Dirs) -> Result { let mut build_options: BuildOptions = options.clone().into(); build_options.target = Some( Target::all() @@ -191,26 +189,24 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result>(), + dirs.tauri, )?; let (interface, mut config) = { - let tauri_config_guard = tauri_config.lock().unwrap(); - let tauri_config_ = tauri_config_guard.as_ref().unwrap(); - - let interface = AppInterface::new(tauri_config_, build_options.target.clone())?; + let interface = AppInterface::new(&tauri_config, build_options.target.clone(), dirs.tauri)?; interface.build_options(&mut Vec::new(), &mut build_options.features, true); - let app = get_app(MobileTarget::Ios, tauri_config_, &interface); + let app = get_app(MobileTarget::Ios, &tauri_config, &interface, dirs.tauri); let (config, _metadata) = get_config( &app, - tauri_config_, - build_options.features.as_ref(), + &tauri_config, + &build_options.features, &Default::default(), + dirs.tauri, )?; (interface, config) }; - let tauri_path = tauri_dir(); - set_current_dir(tauri_path).context("failed to set current directory")?; + set_current_dir(dirs.tauri).context("failed to set current directory")?; ensure_init( &tauri_config, @@ -219,7 +215,7 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result Result Result Result { let profile = if options.debug { Profile::Debug @@ -370,20 +360,18 @@ fn run_build( Profile::Release }; - crate::build::setup( - interface, - &mut build_options, - tauri_config.lock().unwrap().as_ref().unwrap(), - true, - )?; + crate::build::setup(interface, &mut build_options, &tauri_config, dirs, true)?; let app_settings = interface.app_settings(); - let out_dir = app_settings.out_dir(&InterfaceOptions { - debug: build_options.debug, - target: build_options.target.clone(), - args: build_options.args.clone(), - ..Default::default() - })?; + let out_dir = app_settings.out_dir( + &InterfaceOptions { + debug: build_options.debug, + target: build_options.target.clone(), + args: build_options.args.clone(), + ..Default::default() + }, + dirs.tauri, + )?; let _lock = flock::open_rw(out_dir.join("lock").with_extension("ios"), "iOS")?; let cli_options = CliOptions { @@ -395,7 +383,7 @@ fn run_build( config: build_options.config.clone(), target_device: options.target_device.clone(), }; - let handle = write_options(tauri_config.lock().unwrap().as_ref().unwrap(), cli_options)?; + let handle = write_options(&tauri_config, cli_options)?; if options.open { return Ok(handle); diff --git a/crates/tauri-cli/src/mobile/ios/dev.rs b/crates/tauri-cli/src/mobile/ios/dev.rs index 45cd44f5b..397875ac2 100644 --- a/crates/tauri-cli/src/mobile/ios/dev.rs +++ b/crates/tauri-cli/src/mobile/ios/dev.rs @@ -10,8 +10,8 @@ use crate::{ dev::Options as DevOptions, error::{Context, ErrorExt}, helpers::{ - app_paths::tauri_dir, - config::{get as get_tauri_config, ConfigHandle}, + app_paths::Dirs, + config::{get_config as get_tauri_config, ConfigMetadata}, flock, plist::merge_plist, }, @@ -54,7 +54,7 @@ environment variable to determine whether the public network should be used or n pub struct Options { /// List of cargo features to activate #[clap(short, long, action = ArgAction::Append, num_args(0..))] - pub features: Option>, + pub features: Vec, /// Exit on panic #[clap(short, long)] exit_on_panic: bool, @@ -138,16 +138,16 @@ impl From for DevOptions { } pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> { - crate::helpers::app_paths::resolve(); + let dirs = crate::helpers::app_paths::resolve_dirs(); - let result = run_command(options, noise_level); + let result = run_command(options, noise_level, dirs); if result.is_err() { crate::dev::kill_before_dev_process(); } result } -fn run_command(options: Options, noise_level: NoiseLevel) -> Result<()> { +fn run_command(options: Options, noise_level: NoiseLevel, dirs: Dirs) -> Result<()> { // setup env additions before calling env() if let Some(root_certificate_path) = &options.root_certificate_path { std::env::set_var( @@ -186,26 +186,24 @@ fn run_command(options: Options, noise_level: NoiseLevel) -> Result<()> { let tauri_config = get_tauri_config( tauri_utils::platform::Target::Ios, &options.config.iter().map(|c| &c.0).collect::>(), + dirs.tauri, )?; let (interface, config) = { - let tauri_config_guard = tauri_config.lock().unwrap(); - let tauri_config_ = tauri_config_guard.as_ref().unwrap(); + let interface = AppInterface::new(&tauri_config, Some(target_triple), dirs.tauri)?; - let interface = AppInterface::new(tauri_config_, Some(target_triple))?; - - let app = get_app(MobileTarget::Ios, tauri_config_, &interface); + let app = get_app(MobileTarget::Ios, &tauri_config, &interface, dirs.tauri); let (config, _metadata) = get_config( &app, - tauri_config_, - dev_options.features.as_ref(), + &tauri_config, + &dev_options.features, &Default::default(), + dirs.tauri, )?; (interface, config) }; - let tauri_path = tauri_dir(); - set_current_dir(tauri_path).context("failed to set current directory to Tauri directory")?; + set_current_dir(dirs.tauri).context("failed to set current directory to Tauri directory")?; ensure_init( &tauri_config, @@ -214,28 +212,20 @@ fn run_command(options: Options, noise_level: NoiseLevel) -> Result<()> { MobileTarget::Ios, false, )?; - inject_resources(&config, tauri_config.lock().unwrap().as_ref().unwrap())?; + inject_resources(&config, &tauri_config)?; let info_plist_path = config .project_dir() .join(config.scheme()) .join("Info.plist"); let mut src_plists = vec![info_plist_path.clone().into()]; - if tauri_path.join("Info.plist").exists() { - src_plists.push(tauri_path.join("Info.plist").into()); + if dirs.tauri.join("Info.plist").exists() { + src_plists.push(dirs.tauri.join("Info.plist").into()); } - if tauri_path.join("Info.ios.plist").exists() { - src_plists.push(tauri_path.join("Info.ios.plist").into()); + if dirs.tauri.join("Info.ios.plist").exists() { + src_plists.push(dirs.tauri.join("Info.ios.plist").into()); } - if let Some(info_plist) = &tauri_config - .lock() - .unwrap() - .as_ref() - .unwrap() - .bundle - .ios - .info_plist - { + if let Some(info_plist) = &tauri_config.bundle.ios.info_plist { src_plists.push(info_plist.clone().into()); } let merged_info_plist = merge_plist(src_plists)?; @@ -274,6 +264,7 @@ fn run_command(options: Options, noise_level: NoiseLevel) -> Result<()> { env, &config, noise_level, + &dirs, ) } @@ -282,11 +273,12 @@ fn run_dev( mut interface: AppInterface, options: Options, mut dev_options: DevOptions, - tauri_config: ConfigHandle, + mut tauri_config: ConfigMetadata, device: Option, env: Env, config: &AppleConfig, noise_level: NoiseLevel, + dirs: &Dirs, ) -> Result<()> { // when --host is provided or running on a physical device or resolving 0.0.0.0 we must use the network IP if options.host.0.is_some() @@ -294,38 +286,39 @@ fn run_dev( .as_ref() .map(|device| !matches!(device.kind(), DeviceKind::Simulator)) .unwrap_or(false) - || tauri_config - .lock() - .unwrap() - .as_ref() - .unwrap() - .build - .dev_url - .as_ref() - .is_some_and(|url| { - matches!( + || tauri_config.build.dev_url.as_ref().is_some_and(|url| { + matches!( url.host(), Some(Host::Ipv4(i)) if i == Ipv4Addr::UNSPECIFIED - ) - }) + ) + }) { - use_network_address_for_dev_url(&tauri_config, &mut dev_options, options.force_ip_prompt)?; + use_network_address_for_dev_url( + &mut tauri_config, + &mut dev_options, + options.force_ip_prompt, + dirs.tauri, + )?; } - crate::dev::setup(&interface, &mut dev_options, tauri_config.clone())?; + crate::dev::setup(&interface, &mut dev_options, &mut tauri_config, &dirs)?; let app_settings = interface.app_settings(); - let out_dir = app_settings.out_dir(&InterfaceOptions { - debug: !dev_options.release_mode, - target: dev_options.target.clone(), - ..Default::default() - })?; + let out_dir = app_settings.out_dir( + &InterfaceOptions { + debug: !dev_options.release_mode, + target: dev_options.target.clone(), + ..Default::default() + }, + dirs.tauri, + )?; let _lock = flock::open_rw(out_dir.join("lock").with_extension("ios"), "iOS")?; let set_host = options.host.0.is_some(); let open = options.open; interface.mobile_dev( + &mut tauri_config, MobileOptions { debug: true, features: options.features, @@ -334,7 +327,7 @@ fn run_dev( no_watch: options.no_watch, additional_watch_folders: options.additional_watch_folders, }, - |options| { + |options, tauri_config| { let cli_options = CliOptions { dev: true, features: options.features.clone(), @@ -344,7 +337,7 @@ fn run_dev( config: dev_options.config.clone(), target_device: None, }; - let _handle = write_options(tauri_config.lock().unwrap().as_ref().unwrap(), cli_options)?; + let _handle = write_options(tauri_config, cli_options)?; let open_xcode = || { if !set_host { @@ -371,6 +364,7 @@ fn run_dev( open_xcode() } }, + &dirs, ) } diff --git a/crates/tauri-cli/src/mobile/ios/mod.rs b/crates/tauri-cli/src/mobile/ios/mod.rs index ca4aade41..0590e772a 100644 --- a/crates/tauri-cli/src/mobile/ios/mod.rs +++ b/crates/tauri-cli/src/mobile/ios/mod.rs @@ -30,8 +30,7 @@ use super::{ use crate::{ error::{Context, ErrorExt}, helpers::{ - app_paths::tauri_dir, - config::{BundleResources, Config as TauriConfig, ConfigHandle}, + config::{BundleResources, Config as TauriConfig, ConfigMetadata}, pbxproj, strip_semver_prerelease_tag, }, ConfigValue, Error, Result, @@ -40,7 +39,7 @@ use crate::{ use std::{ env::{set_var, var_os}, fs::create_dir_all, - path::PathBuf, + path::Path, str::FromStr, thread::sleep, time::Duration, @@ -103,19 +102,18 @@ enum Commands { pub fn command(cli: Cli, verbosity: u8) -> Result<()> { let noise_level = NoiseLevel::from_occurrences(verbosity as u64); + let dirs = crate::helpers::app_paths::resolve_dirs(); match cli.command { - Commands::Init(options) => { - crate::helpers::app_paths::resolve(); - init_command( - MobileTarget::Ios, - options.ci, - options.reinstall_deps, - options.skip_targets_install, - options.config, - )? - } + Commands::Init(options) => init_command( + MobileTarget::Ios, + options.ci, + options.reinstall_deps, + options.skip_targets_install, + options.config, + &dirs, + )?, Commands::Dev(options) => dev::command(options, noise_level)?, - Commands::Build(options) => build::command(options, noise_level).map(|_| ())?, + Commands::Build(options) => build::command(options, noise_level, &dirs).map(|_| ())?, Commands::Run(options) => run::command(options, noise_level)?, Commands::XcodeScript(options) => xcode_script::command(options)?, } @@ -126,16 +124,12 @@ pub fn command(cli: Cli, verbosity: u8) -> Result<()> { pub fn get_config( app: &App, tauri_config: &TauriConfig, - features: Option<&Vec>, + features: &[String], cli_options: &CliOptions, + tauri_dir: &Path, ) -> Result<(AppleConfig, AppleMetadata)> { let mut ios_options = cli_options.clone(); - if let Some(features) = features { - ios_options - .features - .get_or_insert(Vec::new()) - .extend_from_slice(features); - } + ios_options.features.extend_from_slice(features); let bundle_version = if let Some(bundle_version) = tauri_config .bundle @@ -232,7 +226,7 @@ pub fn get_config( } } }), - ios_features: ios_options.features.clone(), + ios_features: Some(ios_options.features.clone()), bundle_version, bundle_version_short, ios_version: Some(tauri_config.bundle.ios.minimum_system_version.clone()), @@ -241,8 +235,6 @@ pub fn get_config( let config = AppleConfig::from_raw(app.clone(), Some(raw)) .context("failed to create Apple configuration")?; - let tauri_dir = tauri_dir(); - let mut vendor_frameworks = Vec::new(); let mut frameworks = Vec::new(); for framework in tauri_config @@ -252,7 +244,7 @@ pub fn get_config( .clone() .unwrap_or_default() { - let framework_path = PathBuf::from(&framework); + let framework_path = Path::new(&framework); let ext = framework_path.extension().unwrap_or_default(); if ext.is_empty() { frameworks.push(framework); @@ -277,7 +269,7 @@ pub fn get_config( supported: true, ios: ApplePlatform { cargo_args: Some(ios_options.args), - features: ios_options.features, + features: Some(ios_options.features), frameworks: Some(frameworks), vendor_frameworks: Some(vendor_frameworks), ..Default::default() @@ -554,26 +546,14 @@ pub fn load_pbxproj(config: &AppleConfig) -> Result { pub fn synchronize_project_config( config: &AppleConfig, - tauri_config: &ConfigHandle, + tauri_config: &ConfigMetadata, pbxproj: &mut pbxproj::Pbxproj, export_options_plist: &mut plist::Dictionary, project_config: &ProjectConfig, debug: bool, ) -> Result<()> { - let identifier = tauri_config - .lock() - .unwrap() - .as_ref() - .unwrap() - .identifier - .clone(); - let product_name = tauri_config - .lock() - .unwrap() - .as_ref() - .unwrap() - .product_name - .clone(); + let identifier = tauri_config.identifier.clone(); + let product_name = tauri_config.product_name.clone(); let manual_signing = project_config.code_sign_identity.is_some() || project_config.provisioning_profile_uuid.is_some(); diff --git a/crates/tauri-cli/src/mobile/ios/run.rs b/crates/tauri-cli/src/mobile/ios/run.rs index 0ae1cef12..6c75c5acc 100644 --- a/crates/tauri-cli/src/mobile/ios/run.rs +++ b/crates/tauri-cli/src/mobile/ios/run.rs @@ -10,6 +10,7 @@ use clap::{ArgAction, Parser}; use super::{device_prompt, env}; use crate::{ error::Context, + helpers::config::{get_config as get_tauri_config, ConfigMetadata}, interface::{DevProcess, Interface, WatcherOptions}, mobile::{DevChild, TargetDevice}, ConfigValue, Result, @@ -26,7 +27,7 @@ pub struct Options { pub release: bool, /// List of cargo features to activate #[clap(short, long, action = ArgAction::Append, num_args(0..))] - pub features: Option>, + pub features: Vec, /// JSON strings or paths to JSON, JSON5 or TOML files to merge with the default configuration file /// /// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts. @@ -73,11 +74,13 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> { } }; + let dirs = crate::helpers::app_paths::resolve_dirs(); + let mut built_application = super::build::command( super::build::Options { debug: !options.release, targets: Some(vec![]), /* skips IPA build since there's no target */ - features: None, + features: Vec::new(), config: options.config.clone(), build_number: None, open: options.open, @@ -91,12 +94,19 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> { }), }, noise_level, + &dirs, + )?; + + let mut tauri_config = get_tauri_config( + tauri_utils::platform::Target::Ios, + &options.config.iter().map(|c| &c.0).collect::>(), + dirs.tauri, )?; // options.open is handled by the build command // so all we need to do here is run the app on the selected device if let Some(device) = device { - let runner = move || { + let runner = move |_tauri_config: &ConfigMetadata| { device .run( &built_application.config, @@ -114,14 +124,16 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> { }; if options.no_watch { - runner()?; + runner(&tauri_config)?; } else { built_application.interface.watch( + &mut tauri_config, WatcherOptions { config: options.config, additional_watch_folders: options.additional_watch_folders, }, runner, + &dirs, )?; } } diff --git a/crates/tauri-cli/src/mobile/ios/xcode_script.rs b/crates/tauri-cli/src/mobile/ios/xcode_script.rs index 34d697614..0b30ba2f4 100644 --- a/crates/tauri-cli/src/mobile/ios/xcode_script.rs +++ b/crates/tauri-cli/src/mobile/ios/xcode_script.rs @@ -5,7 +5,7 @@ use super::{ensure_init, env, get_app, get_config, read_options, MobileTarget}; use crate::{ error::{Context, ErrorExt}, - helpers::config::{get as get_tauri_config, reload as reload_tauri_config}, + helpers::config::{get_config as get_tauri_config, reload_config as reload_tauri_config}, interface::{AppInterface, Interface, Options as InterfaceOptions}, mobile::ios::LIB_OUTPUT_FILE_NAME, Error, Result, @@ -89,47 +89,43 @@ pub fn command(options: Options) -> Result<()> { .unwrap(); } - crate::helpers::app_paths::resolve(); + let dirs = crate::helpers::app_paths::resolve_dirs(); let profile = profile_from_configuration(&options.configuration); let macos = macos_from_platform(&options.platform); - let (tauri_config, cli_options) = { - let tauri_config = get_tauri_config(tauri_utils::platform::Target::Ios, &[])?; - let cli_options = { - let tauri_config_guard = tauri_config.lock().unwrap(); - let tauri_config_ = tauri_config_guard.as_ref().unwrap(); - read_options(tauri_config_) - }; - let tauri_config = if cli_options.config.is_empty() { - tauri_config - } else { + let mut tauri_config = get_tauri_config(tauri_utils::platform::Target::Ios, &[], dirs.tauri)?; + let cli_options = { + let cli_options = { read_options(&tauri_config) }; + if !cli_options.config.is_empty() { // reload config with merges from the ios dev|build script reload_tauri_config( + &mut tauri_config, &cli_options .config .iter() .map(|conf| &conf.0) .collect::>(), + dirs.tauri, )? }; - (tauri_config, cli_options) + cli_options }; let (config, metadata) = { - let tauri_config_guard = tauri_config.lock().unwrap(); - let tauri_config_ = tauri_config_guard.as_ref().unwrap(); - let cli_options = read_options(tauri_config_); + let cli_options = read_options(&tauri_config); let (config, metadata) = get_config( &get_app( MobileTarget::Ios, - tauri_config_, - &AppInterface::new(tauri_config_, None)?, + &tauri_config, + &AppInterface::new(&tauri_config, None, dirs.tauri)?, + dirs.tauri, ), - tauri_config_, - None, + &tauri_config, + &[], &cli_options, + dirs.tauri, )?; (config, metadata) }; @@ -142,7 +138,8 @@ pub fn command(options: Options) -> Result<()> { )?; if !cli_options.config.is_empty() { - crate::helpers::config::merge_with( + crate::helpers::config::merge_config_with( + &mut tauri_config, &cli_options .config .iter() @@ -236,10 +233,7 @@ pub fn command(options: Options) -> Result<()> { } }; - let interface = AppInterface::new( - tauri_config.lock().unwrap().as_ref().unwrap(), - Some(rust_triple.into()), - )?; + let interface = AppInterface::new(&tauri_config, Some(rust_triple.into()), dirs.tauri)?; let cflags = format!("CFLAGS_{env_triple}"); let cxxflags = format!("CFLAGS_{env_triple}"); @@ -280,11 +274,14 @@ pub fn command(options: Options) -> Result<()> { ) .context("failed to compile iOS app")?; - let out_dir = interface.app_settings().out_dir(&InterfaceOptions { - debug: matches!(profile, Profile::Debug), - target: Some(rust_triple.into()), - ..Default::default() - })?; + let out_dir = interface.app_settings().out_dir( + &InterfaceOptions { + debug: matches!(profile, Profile::Debug), + target: Some(rust_triple.into()), + ..Default::default() + }, + dirs.tauri, + )?; let lib_path = out_dir.join(format!("lib{}.a", config.app().lib_name())); if !lib_path.exists() { diff --git a/crates/tauri-cli/src/mobile/mod.rs b/crates/tauri-cli/src/mobile/mod.rs index 2a20c66b6..6b06e0cac 100644 --- a/crates/tauri-cli/src/mobile/mod.rs +++ b/crates/tauri-cli/src/mobile/mod.rs @@ -4,10 +4,7 @@ use crate::{ error::{Context, ErrorExt}, - helpers::{ - app_paths::tauri_dir, - config::{reload as reload_config, Config as TauriConfig, ConfigHandle, ConfigMetadata}, - }, + helpers::config::{reload_config, Config as TauriConfig, ConfigMetadata}, interface::{AppInterface, AppSettings, DevProcess, Interface, Options as InterfaceOptions}, ConfigValue, Error, Result, }; @@ -31,7 +28,7 @@ use std::{ fmt::{Display, Write}, fs::{read_to_string, write}, net::{AddrParseError, IpAddr, Ipv4Addr, SocketAddr}, - path::PathBuf, + path::{Path, PathBuf}, process::{exit, ExitStatus}, str::FromStr, sync::{ @@ -181,7 +178,7 @@ impl Default for DevHost { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CliOptions { pub dev: bool, - pub features: Option>, + pub features: Vec, pub args: Vec, pub noise_level: NoiseLevel, pub vars: HashMap, @@ -193,7 +190,7 @@ impl Default for CliOptions { fn default() -> Self { Self { dev: false, - features: None, + features: Vec::new(), args: vec!["--lib".into()], noise_level: Default::default(), vars: Default::default(), @@ -217,12 +214,9 @@ fn local_ip_address(force: bool) -> &'static IpAddr { }) .collect(); - match addresses.len() { - 0 => panic!("No external IP detected."), - 1 => { - let ipaddr = addresses.first().unwrap(); - *ipaddr - } + match addresses.as_slice() { + [] => panic!("No external IP detected."), + [ipaddr] => *ipaddr, _ => { let selected = dialoguer::Select::with_theme(&dialoguer::theme::ColorfulTheme::default()) .with_prompt( @@ -252,18 +246,12 @@ struct DevUrlConfig { } fn use_network_address_for_dev_url( - config: &ConfigHandle, + config: &mut ConfigMetadata, dev_options: &mut crate::dev::Options, force_ip_prompt: bool, + tauri_dir: &Path, ) -> crate::Result { - let mut dev_url = config - .lock() - .unwrap() - .as_ref() - .unwrap() - .build - .dev_url - .clone(); + let mut dev_url = config.build.dev_url.clone(); let ip = if let Some(url) = &mut dev_url { let localhost = match url.host() { @@ -299,11 +287,13 @@ fn use_network_address_for_dev_url( }))); reload_config( + config, &dev_options .config .iter() .map(|conf| &conf.0) .collect::>(), + tauri_dir, )?; Some(ip) @@ -441,7 +431,12 @@ fn read_options(config: &ConfigMetadata) -> CliOptions { options } -pub fn get_app(target: Target, config: &TauriConfig, interface: &AppInterface) -> App { +pub fn get_app( + target: Target, + config: &TauriConfig, + interface: &AppInterface, + tauri_dir: &Path, +) -> App { let identifier = match target { Target::Android => config.identifier.replace('-', "_"), #[cfg(target_os = "macos")] @@ -478,22 +473,26 @@ pub fn get_app(target: Target, config: &TauriConfig, interface: &AppInterface) - }; let app_settings = interface.app_settings(); - App::from_raw(tauri_dir().to_path_buf(), raw) + let tauri_dir = tauri_dir.to_path_buf(); + App::from_raw(tauri_dir.to_path_buf(), raw) .unwrap() .with_target_dir_resolver(move |target, profile| { app_settings - .out_dir(&InterfaceOptions { - debug: matches!(profile, Profile::Debug), - target: Some(target.into()), - ..Default::default() - }) + .out_dir( + &InterfaceOptions { + debug: matches!(profile, Profile::Debug), + target: Some(target.into()), + ..Default::default() + }, + &tauri_dir, + ) .expect("failed to resolve target directory") }) } #[allow(unused_variables)] fn ensure_init( - tauri_config: &ConfigHandle, + tauri_config: &ConfigMetadata, app: &App, project_dir: PathBuf, target: Target, @@ -508,16 +507,13 @@ fn ensure_init( ) } - let tauri_config_guard = tauri_config.lock().unwrap(); - let tauri_config_ = tauri_config_guard.as_ref().unwrap(); - let mut project_outdated_reasons = Vec::new(); match target { Target::Android => { let java_folder = project_dir .join("app/src/main/java") - .join(tauri_config_.identifier.replace('.', "/").replace('-', "_")); + .join(tauri_config.identifier.replace('.', "/").replace('-', "_")); if java_folder.exists() { #[cfg(unix)] ensure_gradlew(&project_dir)?; diff --git a/crates/tauri-cli/src/remove.rs b/crates/tauri-cli/src/remove.rs index 319f60e82..ba20ae778 100644 --- a/crates/tauri-cli/src/remove.rs +++ b/crates/tauri-cli/src/remove.rs @@ -6,11 +6,7 @@ use clap::Parser; use crate::{ acl, - helpers::{ - app_paths::{resolve_frontend_dir, tauri_dir}, - cargo, - npm::PackageManager, - }, + helpers::{app_paths::resolve_frontend_dir, cargo, npm::PackageManager}, Result, }; @@ -22,11 +18,7 @@ pub struct Options { } pub fn command(options: Options) -> Result<()> { - crate::helpers::app_paths::resolve(); - run(options) -} - -pub fn run(options: Options) -> Result<()> { + let dirs = crate::helpers::app_paths::resolve_dirs(); let plugin = options.plugin; let crate_name = format!("tauri-plugin-{plugin}"); @@ -35,7 +27,6 @@ pub fn run(options: Options) -> Result<()> { let metadata = plugins.remove(plugin.as_str()).unwrap_or_default(); let frontend_dir = resolve_frontend_dir(); - let tauri_dir = tauri_dir(); let target_str = metadata .desktop_only @@ -48,14 +39,14 @@ pub fn run(options: Options) -> Result<()> { cargo::uninstall_one(cargo::CargoUninstallOptions { name: &crate_name, - cwd: Some(tauri_dir), + cwd: Some(dirs.tauri), target: target_str, })?; if !metadata.rust_only { if let Some(manager) = frontend_dir.map(PackageManager::from_project) { let npm_name = format!("@tauri-apps/plugin-{plugin}"); - manager.remove(&[npm_name], tauri_dir)?; + manager.remove(&[npm_name], dirs.tauri)?; } acl::permission::rm::command(acl::permission::rm::Options { diff --git a/crates/tauri-cli/src/signer/generate.rs b/crates/tauri-cli/src/signer/generate.rs index 97f3b3708..6ca4244c7 100644 --- a/crates/tauri-cli/src/signer/generate.rs +++ b/crates/tauri-cli/src/signer/generate.rs @@ -39,26 +39,29 @@ pub fn command(mut options: Options) -> Result<()> { save_keypair(options.force, output_path, &keypair.sk, &keypair.pk) .expect("Unable to write keypair"); - println!( - "\nYour keypair was generated successfully\nPrivate: {} (Keep it secret!)\nPublic: {}\n---------------------------", - display_path(secret_path), - display_path(public_path) - ) + println!(); + println!("Your keypair was generated successfully:"); + println!("Private: {} (Keep it secret!)", display_path(secret_path)); + println!("Public: {}", display_path(public_path)); + println!("---------------------------") } else { - println!( - "\nYour secret key was generated successfully - Keep it secret!\n{}\n\n", - keypair.sk - ); - println!( - "Your public key was generated successfully:\n{}\n\nAdd the public key in your tauri.conf.json\n---------------------------\n", - keypair.pk - ); + println!(); + println!("Your keys were generated successfully!",); + println!(); + println!("Private: (Keep it secret!)"); + println!("{}", keypair.sk); + println!(); + println!("Public:"); + println!("{}", keypair.pk); } - println!("\nEnvironment variables used to sign:"); - println!("`TAURI_SIGNING_PRIVATE_KEY` Path or String of your private key"); - println!("`TAURI_SIGNING_PRIVATE_KEY_PASSWORD` Your private key password (optional)"); - println!("\nATTENTION: If you lose your private key OR password, you'll not be able to sign your update package and updates will not work.\n---------------------------\n"); + println!(); + println!("Environment variables used to sign:"); + println!("- `TAURI_SIGNING_PRIVATE_KEY`: String of your private key"); + println!("- `TAURI_SIGNING_PRIVATE_KEY_PATH`: Path to your private key file"); + println!("- `TAURI_SIGNING_PRIVATE_KEY_PASSWORD`: Your private key password (optional if key has no password)"); + println!(); + println!("ATTENTION: If you lose your private key OR password, you'll not be able to sign your update package and updates will not work"); Ok(()) } diff --git a/crates/tauri-cli/src/signer/sign.rs b/crates/tauri-cli/src/signer/sign.rs index 44eee5d57..a50e4e083 100644 --- a/crates/tauri-cli/src/signer/sign.rs +++ b/crates/tauri-cli/src/signer/sign.rs @@ -21,7 +21,7 @@ pub struct Options { short = 'k', long, conflicts_with("private_key_path"), - env = "TAURI_PRIVATE_KEY" + env = "TAURI_SIGNING_PRIVATE_KEY" )] private_key: Option, /// Load the private key from a file @@ -29,17 +29,50 @@ pub struct Options { short = 'f', long, conflicts_with("private_key"), - env = "TAURI_PRIVATE_KEY_PATH" + env = "TAURI_SIGNING_PRIVATE_KEY_PATH" )] private_key_path: Option, /// Set private key password when signing - #[clap(short, long, env = "TAURI_PRIVATE_KEY_PASSWORD")] + #[clap(short, long, env = "TAURI_SIGNING_PRIVATE_KEY_PASSWORD")] password: Option, /// Sign the specified file file: PathBuf, } +// Backwards compatibility with old env vars +// TODO: remove in v3.0 +fn backward_env_vars(mut options: Options) -> Options { + let get_env = |old, new| { + if let Ok(old_value) = std::env::var(old) { + println!( + "\x1b[33mWarning: The environment variable '{old}' is deprecated. Please use '{new}' instead.\x1b[0m", + ); + Some(old_value) + } else { + None + } + }; + + options.private_key = options + .private_key + .or_else(|| get_env("TAURI_PRIVATE_KEY", "TAURI_SIGNING_PRIVATE_KEY")); + + options.private_key_path = options.private_key_path.or_else(|| { + get_env("TAURI_PRIVATE_KEY_PATH", "TAURI_SIGNING_PRIVATE_KEY_PATH").map(PathBuf::from) + }); + + options.password = options.password.or_else(|| { + get_env( + "TAURI_PRIVATE_KEY_PASSWORD", + "TAURI_SIGNING_PRIVATE_KEY_PASSWORD", + ) + }); + options +} + pub fn command(mut options: Options) -> Result<()> { + options = backward_env_vars(options); + options.private_key = if let Some(private_key) = options.private_key_path { Some(std::fs::read_to_string(Path::new(&private_key)).expect("Unable to extract private key")) } else { diff --git a/crates/tauri-driver/Cargo.toml b/crates/tauri-driver/Cargo.toml index ad44b8a89..9b35fe111 100644 --- a/crates/tauri-driver/Cargo.toml +++ b/crates/tauri-driver/Cargo.toml @@ -31,8 +31,8 @@ tokio = { version = "1", features = ["macros"] } which = "8" [target."cfg(unix)".dependencies] -signal-hook = "0.3" -signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] } +signal-hook = "0.4" +signal-hook-tokio = { version = "0.4", features = ["futures-v0_3"] } [target."cfg(windows)".dependencies] win32job = "2" diff --git a/crates/tauri-macos-sign/Cargo.toml b/crates/tauri-macos-sign/Cargo.toml index 9fc874364..309938c27 100644 --- a/crates/tauri-macos-sign/Cargo.toml +++ b/crates/tauri-macos-sign/Cargo.toml @@ -15,7 +15,8 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" tempfile = "3" x509-certificate = "0.23" -once-cell-regex = "0.2" +once_cell = "1" +regex = "1" os_pipe = "1" plist = "1" rand = "0.9" diff --git a/crates/tauri-macos-sign/src/keychain/identity.rs b/crates/tauri-macos-sign/src/keychain/identity.rs index a39c48423..6ac146915 100644 --- a/crates/tauri-macos-sign/src/keychain/identity.rs +++ b/crates/tauri-macos-sign/src/keychain/identity.rs @@ -2,7 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use once_cell_regex::regex; +use once_cell::sync::OnceCell; +use regex::Regex; use std::{collections::BTreeSet, path::Path, process::Command}; use x509_certificate::certificate::X509Certificate; @@ -49,9 +50,10 @@ impl Team { organization } else { println!( - "found cert {common_name:?} but failed to get organization; falling back to displaying common name" - ); - regex!(r"Apple Develop\w+: (.*) \(.+\)") + "found cert {common_name:?} but failed to get organization; falling back to displaying common name" + ); + static APPLE_DEV: OnceCell = OnceCell::new(); + APPLE_DEV.get_or_init(|| Regex::new(r"Apple Develop\w+: (.*) \(.+\)").unwrap()) .captures(&common_name) .map(|caps| caps[1].to_owned()) .unwrap_or_else(|| { diff --git a/crates/tauri-runtime-wry/Cargo.toml b/crates/tauri-runtime-wry/Cargo.toml index 339b07f4a..3abe48253 100644 --- a/crates/tauri-runtime-wry/Cargo.toml +++ b/crates/tauri-runtime-wry/Cargo.toml @@ -13,7 +13,7 @@ edition.workspace = true rust-version.workspace = true [dependencies] -wry = { version = "0.53.4", default-features = false, features = [ +wry = { version = "0.54.0", default-features = false, features = [ "drag-drop", "protocol", "os-webview", diff --git a/crates/tauri-runtime-wry/src/lib.rs b/crates/tauri-runtime-wry/src/lib.rs index 821fbacec..61799d039 100644 --- a/crates/tauri-runtime-wry/src/lib.rs +++ b/crates/tauri-runtime-wry/src/lib.rs @@ -37,7 +37,13 @@ use tauri_runtime::{ use objc2::rc::Retained; #[cfg(target_os = "macos")] use tao::platform::macos::{EventLoopWindowTargetExtMacOS, WindowBuilderExtMacOS}; -#[cfg(target_os = "linux")] +#[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] use tao::platform::unix::{WindowBuilderExtUnix, WindowExtUnix}; #[cfg(windows)] use tao::platform::windows::{WindowBuilderExtWindows, WindowExtWindows}; @@ -862,7 +868,13 @@ impl WindowBuilder for WindowBuilderWrapper { "); } - #[cfg(target_os = "linux")] + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] { // Mouse event is disabled on Linux since sudden event bursts could block event loop. window.inner = window.inner.with_cursor_moved_event(false); @@ -1194,7 +1206,14 @@ impl WindowBuilder for WindowBuilderWrapper { self } - #[cfg(any(windows, target_os = "linux"))] + #[cfg(any( + windows, + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] fn skip_taskbar(mut self, skip: bool) -> Self { self.inner = self.inner.with_skip_taskbar(skip); self @@ -3417,7 +3436,14 @@ fn handle_user_message( } #[allow(unused_variables)] WindowMessage::SetSkipTaskbar(skip) => { - #[cfg(any(windows, target_os = "linux"))] + #[cfg(any( + windows, + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] let _ = window.set_skip_taskbar(skip); } WindowMessage::SetCursorGrab(grab) => { diff --git a/crates/tauri-runtime-wry/src/window/mod.rs b/crates/tauri-runtime-wry/src/window/mod.rs index 6fb2dbe17..b18543e5d 100644 --- a/crates/tauri-runtime-wry/src/window/mod.rs +++ b/crates/tauri-runtime-wry/src/window/mod.rs @@ -39,7 +39,7 @@ pub trait WindowExt { /// - **Android / iOS**: Unsupported. fn center(&self) {} - /// Clears the window surface. i.e make it it transparent. + /// Clears the window surface. i.e make it transparent. #[cfg(windows)] fn draw_surface( &self, diff --git a/crates/tauri-runtime/src/lib.rs b/crates/tauri-runtime/src/lib.rs index 2874d16b9..5bf869dcd 100644 --- a/crates/tauri-runtime/src/lib.rs +++ b/crates/tauri-runtime/src/lib.rs @@ -399,8 +399,25 @@ pub trait Runtime: Debug + Sized + 'static { fn new(args: RuntimeInitArgs) -> Result; /// Creates a new webview runtime on any thread. - #[cfg(any(windows, target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(any(windows, target_os = "linux"))))] + #[cfg(any( + windows, + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg_attr( + docsrs, + doc(cfg(any( + windows, + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))) + )] fn new_any_thread(args: RuntimeInitArgs) -> Result; /// Creates an `EventLoopProxy` that can be used to dispatch user events to the main event loop. diff --git a/crates/tauri-runtime/src/webview.rs b/crates/tauri-runtime/src/webview.rs index efce7d814..80f7edf0e 100644 --- a/crates/tauri-runtime/src/webview.rs +++ b/crates/tauri-runtime/src/webview.rs @@ -363,7 +363,7 @@ pub struct WebviewAttributes { /// see https://docs.rs/objc2-web-kit/latest/objc2_web_kit/struct.WKWebView.html#method.allowsLinkPreview pub allow_link_preview: bool, pub scroll_bar_style: ScrollBarStyle, - /// Allows overriding the the keyboard accessory view on iOS. + /// Allows overriding the keyboard accessory view on iOS. /// Returning `None` effectively removes the view. /// /// The closure parameter is the webview instance. diff --git a/crates/tauri-schema-generator/schemas/config.schema.json b/crates/tauri-schema-generator/schemas/config.schema.json index 02c0bf908..58fab551a 100644 --- a/crates/tauri-schema-generator/schemas/config.schema.json +++ b/crates/tauri-schema-generator/schemas/config.schema.json @@ -165,7 +165,7 @@ "type": "object", "properties": { "windows": { - "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 ```", + "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": { diff --git a/crates/tauri-utils/Cargo.toml b/crates/tauri-utils/Cargo.toml index 25debb2db..6ce1271a0 100644 --- a/crates/tauri-utils/Cargo.toml +++ b/crates/tauri-utils/Cargo.toml @@ -57,6 +57,7 @@ swift-rs = { version = "1", optional = true, features = ["build"] } [dev-dependencies] getrandom = { version = "0.3", features = ["std"] } serial_test = "3" +tauri = { path = "../tauri" } [features] build = [ diff --git a/crates/tauri-utils/src/config.rs b/crates/tauri-utils/src/config.rs index ada9041a6..4350398d0 100644 --- a/crates/tauri-utils/src/config.rs +++ b/crates/tauri-utils/src/config.rs @@ -2742,7 +2742,7 @@ pub struct AppConfig { /// ```rust /// tauri::Builder::default() /// .setup(|app| { - /// tauri::WebviewWindowBuilder::from_config(app.handle(), app.config().app.windows[0])?.build()?; + /// tauri::WebviewWindowBuilder::from_config(app.handle(), &app.config().app.windows[0])?.build()?; /// Ok(()) /// }); /// ``` diff --git a/crates/tauri-utils/src/platform.rs b/crates/tauri-utils/src/platform.rs index 2ace45790..6eaaf3ffe 100644 --- a/crates/tauri-utils/src/platform.rs +++ b/crates/tauri-utils/src/platform.rs @@ -344,23 +344,20 @@ fn resource_dir_from>( // Variable holding the type of bundle the executable is stored in. This is modified by binary // patching during build #[used] -#[no_mangle] -#[cfg_attr(not(target_vendor = "apple"), link_section = ".taubndl")] -#[cfg_attr(target_vendor = "apple", link_section = "__DATA,taubndl")] // Marked as `mut` because it could get optimized away without it, // see https://github.com/tauri-apps/tauri/pull/13812 -static mut __TAURI_BUNDLE_TYPE: &str = "UNK"; +static mut __TAURI_BUNDLE_TYPE: &str = "__TAURI_BUNDLE_TYPE_VAR_UNK"; /// Get the type of the bundle current binary is packaged in. /// If the bundle type is unknown, it returns [`Option::None`]. pub fn bundle_type() -> Option { unsafe { match __TAURI_BUNDLE_TYPE { - "DEB" => Some(BundleType::Deb), - "RPM" => Some(BundleType::Rpm), - "APP" => Some(BundleType::AppImage), - "MSI" => Some(BundleType::Msi), - "NSS" => Some(BundleType::Nsis), + "__TAURI_BUNDLE_TYPE_VAR_DEB" => Some(BundleType::Deb), + "__TAURI_BUNDLE_TYPE_VAR_RPM" => Some(BundleType::Rpm), + "__TAURI_BUNDLE_TYPE_VAR_APP" => Some(BundleType::AppImage), + "__TAURI_BUNDLE_TYPE_VAR_MSI" => Some(BundleType::Msi), + "__TAURI_BUNDLE_TYPE_VAR_NSS" => Some(BundleType::Nsis), _ => { if cfg!(target_os = "macos") { Some(BundleType::App) diff --git a/crates/tauri/Cargo.toml b/crates/tauri/Cargo.toml index 4da7736cd..6d3c9137c 100644 --- a/crates/tauri/Cargo.toml +++ b/crates/tauri/Cargo.toml @@ -97,7 +97,7 @@ tray-icon = { version = "0.21", default-features = false, features = [ # linux [target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies] gtk = { version = "0.18", features = ["v3_24"] } -webkit2gtk = { version = "=2.0.1", features = ["v2_40"], optional = true } +webkit2gtk = { version = "=2.0.2", features = ["v2_40"], optional = true } # darwin [target.'cfg(target_vendor = "apple")'.dependencies] @@ -141,11 +141,13 @@ windows = { version = "0.61", features = [ # mobile [target.'cfg(any(target_os = "android", all(target_vendor = "apple", not(target_os = "macos"))))'.dependencies] bytes = { version = "1", features = ["serde"] } -reqwest = { version = "0.12", default-features = false, features = [ +reqwest = { version = "0.13", default-features = false, features = [ "json", "stream", - ] } +rustls = { version = "0.23", default-features = false, features = [ + "ring", +], optional = true } # android [target.'cfg(target_os = "android")'.dependencies] @@ -198,10 +200,9 @@ linux-libxdo = ["tray-icon/libxdo", "muda/libxdo"] isolation = ["tauri-utils/isolation", "tauri-macros/isolation", "uuid"] custom-protocol = ["tauri-macros/custom-protocol"] # TODO: Remove these flags in v3 and/or enable them by default behind a mobile flag https://github.com/tauri-apps/tauri/issues/12384 -# For now those feature flags keep enabling reqwest features in case some users depend on that by accident. native-tls = ["reqwest/native-tls"] native-tls-vendored = ["reqwest/native-tls-vendored"] -rustls-tls = ["reqwest/rustls-tls"] +rustls-tls = ["reqwest/rustls-no-provider", "dep:rustls"] devtools = ["tauri-runtime/devtools", "tauri-runtime-wry?/devtools"] process-relaunch-dangerous-allow-symlink-macos = [ "tauri-utils/process-relaunch-dangerous-allow-symlink-macos", diff --git a/crates/tauri/src/manager/mod.rs b/crates/tauri/src/manager/mod.rs index 9973ef46d..25ae8eaab 100644 --- a/crates/tauri/src/manager/mod.rs +++ b/crates/tauri/src/manager/mod.rs @@ -129,14 +129,7 @@ fn replace_csp_nonce( ) { let mut nonces = Vec::new(); *asset = replace_with_callback(asset, token, || { - #[cfg(target_pointer_width = "64")] - let mut raw = [0u8; 8]; - #[cfg(target_pointer_width = "32")] - let mut raw = [0u8; 4]; - #[cfg(target_pointer_width = "16")] - let mut raw = [0u8; 2]; - getrandom::fill(&mut raw).expect("failed to get random bytes"); - let nonce = usize::from_ne_bytes(raw); + let nonce = getrandom::u64().expect("failed to get random bytes"); nonces.push(nonce); nonce.to_string() }); diff --git a/crates/tauri/src/protocol/asset.rs b/crates/tauri/src/protocol/asset.rs index a5007d294..e7c713324 100644 --- a/crates/tauri/src/protocol/asset.rs +++ b/crates/tauri/src/protocol/asset.rs @@ -48,9 +48,29 @@ fn get_response( return resp.status(403).body(Vec::new().into()).map_err(Into::into); } - let (mut file, len, mime_type, read_bytes) = crate::async_runtime::safe_block_on(async move { - let mut file = File::open(&path).await?; + // Separate block for easier error handling + let mut file = match crate::async_runtime::safe_block_on(File::open(path.clone())) { + Ok(file) => file, + Err(e) => { + #[cfg(target_os = "android")] + { + if path.starts_with("/storage/emulated/0/Android/data/") { + log::error!("Failed to open Android external storage file '{}': {}. This may be due to missing storage permissions.", path, e); + } + } + return if e.kind() == std::io::ErrorKind::NotFound { + log::error!("File does not exist at path: {}", path); + return resp.status(404).body(Vec::new().into()).map_err(Into::into); + } else if e.kind() == std::io::ErrorKind::PermissionDenied { + log::error!("Missing OS permission to access path \"{}\": {}", path, e); + return resp.status(403).body(Vec::new().into()).map_err(Into::into); + } else { + Err(e.into()) + }; + } + }; + let (mut file, len, mime_type, read_bytes) = crate::async_runtime::safe_block_on(async move { // get file length let len = { let old_pos = file.stream_position().await?; diff --git a/crates/tauri/src/protocol/tauri.rs b/crates/tauri/src/protocol/tauri.rs index 10cab2ced..a346754f0 100644 --- a/crates/tauri/src/protocol/tauri.rs +++ b/crates/tauri/src/protocol/tauri.rs @@ -114,6 +114,11 @@ fn get_response( decoded_path.trim_start_matches('/') ); + #[cfg(feature = "rustls-tls")] + if rustls::crypto::CryptoProvider::get_default().is_none() { + let _ = rustls::crypto::ring::default_provider().install_default(); + } + let mut client = reqwest::ClientBuilder::new(); if url.starts_with("https://") { @@ -126,10 +131,9 @@ fn get_response( ))] { log::info!("adding dev server root certificate"); - client = client.add_root_certificate( - reqwest::Certificate::from_pem(cert_pem.as_bytes()) - .expect("failed to parse TAURI_DEV_ROOT_CERTIFICATE"), - ); + let certificate = reqwest::Certificate::from_pem(cert_pem.as_bytes()) + .expect("failed to parse TAURI_DEV_ROOT_CERTIFICATE"); + client = client.tls_certs_merge([certificate]); } #[cfg(not(any( diff --git a/crates/tauri/src/test/mock_runtime.rs b/crates/tauri/src/test/mock_runtime.rs index 16b136ef7..5fbedd013 100644 --- a/crates/tauri/src/test/mock_runtime.rs +++ b/crates/tauri/src/test/mock_runtime.rs @@ -1146,7 +1146,14 @@ impl Runtime for MockRuntime { Ok(Self::init()) } - #[cfg(any(windows, target_os = "linux"))] + #[cfg(any( + windows, + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] fn new_any_thread(_args: RuntimeInitArgs) -> Result { Ok(Self::init()) } diff --git a/crates/tauri/src/webview/mod.rs b/crates/tauri/src/webview/mod.rs index 44d9e0a36..8842382bf 100644 --- a/crates/tauri/src/webview/mod.rs +++ b/crates/tauri/src/webview/mod.rs @@ -1202,7 +1202,7 @@ fn main() { self } - /// Allows overriding the the keyboard accessory view on iOS. + /// Allows overriding the keyboard accessory view on iOS. /// Returning `None` effectively removes the view. /// /// The closure parameter is the webview instance. @@ -2312,4 +2312,25 @@ mod tests { crate::test_utils::assert_send::(); crate::test_utils::assert_sync::(); } + + #[cfg(target_os = "macos")] + #[test] + fn test_webview_window_has_set_simple_fullscreen_method() { + use crate::test::{mock_builder, mock_context, noop_assets}; + + // Create a mock app with proper context + let app = mock_builder().build(mock_context(noop_assets())).unwrap(); + + // Get or create a webview window + let webview_window = + crate::WebviewWindowBuilder::new(&app, "test", crate::WebviewUrl::default()) + .build() + .unwrap(); + + // This should compile if set_simple_fullscreen exists + let result = webview_window.set_simple_fullscreen(true); + + // We expect this to work without panicking + assert!(result.is_ok(), "set_simple_fullscreen should succeed"); + } } diff --git a/crates/tauri/src/webview/webview_window.rs b/crates/tauri/src/webview/webview_window.rs index f3c8665c3..290730067 100644 --- a/crates/tauri/src/webview/webview_window.rs +++ b/crates/tauri/src/webview/webview_window.rs @@ -1230,7 +1230,7 @@ impl> WebviewWindowBuilder<'_, R, M> { self } - /// Allows overriding the the keyboard accessory view on iOS. + /// Allows overriding the keyboard accessory view on iOS. /// Returning `None` effectively removes the view. /// /// The closure parameter is the webview instance. @@ -2057,6 +2057,20 @@ impl WebviewWindow { self.window.set_fullscreen(fullscreen) } + /// Toggles a fullscreen mode that doesn't require a new macOS space. + /// Returns a boolean indicating whether the transition was successful (this won't work if the window was already in the native fullscreen). + /// + /// This is how fullscreen used to work on macOS in versions before Lion. + /// And allows the user to have a fullscreen window without using another space or taking control over the entire monitor. + /// + /// ## Platform-specific + /// + /// - **macOS:** Uses native simple fullscreen mode. + /// - **Other platforms:** Falls back to [`Self::set_fullscreen`]. + pub fn set_simple_fullscreen(&self, enable: bool) -> crate::Result<()> { + self.window.set_simple_fullscreen(enable) + } + /// Bring the window to front and focus. pub fn set_focus(&self) -> crate::Result<()> { self.window.set_focus() diff --git a/crates/tauri/src/window/mod.rs b/crates/tauri/src/window/mod.rs index a67784cc3..47c95628b 100644 --- a/crates/tauri/src/window/mod.rs +++ b/crates/tauri/src/window/mod.rs @@ -1968,25 +1968,27 @@ tauri::Builder::default() .map_err(Into::into) } - /// Toggles a fullscreen mode that doesn’t require a new macOS space. Returns a boolean indicating whether the transition was successful (this won’t work if the window was already in the native fullscreen). + /// Toggles a fullscreen mode that doesn't require a new macOS space. + /// Returns a boolean indicating whether the transition was successful (this won't work if the window was already in the native fullscreen). /// - /// This is how fullscreen used to work on macOS in versions before Lion. And allows the user to have a fullscreen window without using another space or taking control over the entire monitor. - #[cfg(target_os = "macos")] + /// This is how fullscreen used to work on macOS in versions before Lion. + /// And allows the user to have a fullscreen window without using another space or taking control over the entire monitor. + /// + /// ## Platform-specific + /// + /// - **macOS:** Uses native simple fullscreen mode. + /// - **Other platforms:** Falls back to [`Self::set_fullscreen`]. pub fn set_simple_fullscreen(&self, enable: bool) -> crate::Result<()> { - self - .window - .dispatcher - .set_simple_fullscreen(enable) - .map_err(Into::into) - } - - /// On macOS, Toggles a fullscreen mode that doesn’t require a new macOS space. Returns a boolean indicating whether the transition was successful (this won’t work if the window was already in the native fullscreen). - /// This is how fullscreen used to work on macOS in versions before Lion. And allows the user to have a fullscreen window without using another space or taking control over the entire monitor. - /// - /// On other platforms, this is the same as [`Window#method.set_fullscreen`]. - #[cfg(not(target_os = "macos"))] - pub fn set_simple_fullscreen(&self, fullscreen: bool) -> crate::Result<()> { - self.set_fullscreen(fullscreen) + #[cfg(target_os = "macos")] + { + self + .window + .dispatcher + .set_simple_fullscreen(enable) + .map_err(Into::into) + } + #[cfg(not(target_os = "macos"))] + self.set_fullscreen(enable) } /// Bring the window to front and focus. diff --git a/crates/tests/acl/fixtures/snapshots/windows/acl_tests__tests__platform-specific-permissions.snap b/crates/tests/acl/fixtures/snapshots/windows/acl_tests__tests__platform-specific-permissions.snap index 17498f928..3c1197d81 100644 --- a/crates/tests/acl/fixtures/snapshots/windows/acl_tests__tests__platform-specific-permissions.snap +++ b/crates/tests/acl/fixtures/snapshots/windows/acl_tests__tests__platform-specific-permissions.snap @@ -3,6 +3,7 @@ source: crates/tests/acl/src/lib.rs expression: resolved --- Resolved { + has_app_acl: false, allowed_commands: { "plugin:os|spawn": [ ResolvedCommand { diff --git a/packages/api/package.json b/packages/api/package.json index 35b0db2b0..6a08a7dd3 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -54,8 +54,8 @@ "eslint-config-prettier": "10.1.8", "eslint-plugin-security": "3.0.1", "fast-glob": "3.3.3", - "globals": "^16.2.0", - "rollup": "4.54.0", + "globals": "^17.0.0", + "rollup": "4.55.1", "tslib": "^2.8.1", "typescript": "^5.8.3", "typescript-eslint": "^8.34.1" diff --git a/packages/cli/tauri.js b/packages/cli/tauri.js index d0460573b..452202cbb 100644 --- a/packages/cli/tauri.js +++ b/packages/cli/tauri.js @@ -20,7 +20,7 @@ if (globalThis.navigator?.userAgent?.includes('Deno')) { } // Even if started by a package manager, the binary will be NodeJS. // Some distribution still use "nodejs" as the binary name. -else if (binStem.match(/(nodejs|node|bun)\-?([0-9]*)*$/g)) { +else if (binStem.match(/(nodejs|node|bun|electron)\-?([0-9]*)*$/g)) { const managerStem = process.env.npm_execpath ? path.parse(process.env.npm_execpath).name.toLowerCase() : null diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 55b351c20..0acf16256 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -63,10 +63,10 @@ importers: version: 9.29.0 '@rollup/plugin-terser': specifier: 0.4.4 - version: 0.4.4(rollup@4.54.0) + version: 0.4.4(rollup@4.55.1) '@rollup/plugin-typescript': specifier: 12.3.0 - version: 12.3.0(rollup@4.54.0)(tslib@2.8.1)(typescript@5.8.3) + version: 12.3.0(rollup@4.55.1)(tslib@2.8.1)(typescript@5.8.3) '@types/eslint': specifier: ^9.6.1 version: 9.6.1 @@ -86,11 +86,11 @@ importers: specifier: 3.3.3 version: 3.3.3 globals: - specifier: ^16.2.0 - version: 16.2.0 + specifier: ^17.0.0 + version: 17.0.0 rollup: - specifier: 4.54.0 - version: 4.54.0 + specifier: 4.55.1 + version: 4.55.1 tslib: specifier: ^2.8.1 version: 2.8.1 @@ -1125,113 +1125,128 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.54.0': - resolution: {integrity: sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==} + '@rollup/rollup-android-arm-eabi@4.55.1': + resolution: {integrity: sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.54.0': - resolution: {integrity: sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw==} + '@rollup/rollup-android-arm64@4.55.1': + resolution: {integrity: sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.54.0': - resolution: {integrity: sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw==} + '@rollup/rollup-darwin-arm64@4.55.1': + resolution: {integrity: sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.54.0': - resolution: {integrity: sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A==} + '@rollup/rollup-darwin-x64@4.55.1': + resolution: {integrity: sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.54.0': - resolution: {integrity: sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA==} + '@rollup/rollup-freebsd-arm64@4.55.1': + resolution: {integrity: sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.54.0': - resolution: {integrity: sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ==} + '@rollup/rollup-freebsd-x64@4.55.1': + resolution: {integrity: sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.54.0': - resolution: {integrity: sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==} + '@rollup/rollup-linux-arm-gnueabihf@4.55.1': + resolution: {integrity: sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.54.0': - resolution: {integrity: sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==} + '@rollup/rollup-linux-arm-musleabihf@4.55.1': + resolution: {integrity: sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.54.0': - resolution: {integrity: sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==} + '@rollup/rollup-linux-arm64-gnu@4.55.1': + resolution: {integrity: sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.54.0': - resolution: {integrity: sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==} + '@rollup/rollup-linux-arm64-musl@4.55.1': + resolution: {integrity: sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loong64-gnu@4.54.0': - resolution: {integrity: sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==} + '@rollup/rollup-linux-loong64-gnu@4.55.1': + resolution: {integrity: sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-ppc64-gnu@4.54.0': - resolution: {integrity: sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==} + '@rollup/rollup-linux-loong64-musl@4.55.1': + resolution: {integrity: sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.55.1': + resolution: {integrity: sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.54.0': - resolution: {integrity: sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==} + '@rollup/rollup-linux-ppc64-musl@4.55.1': + resolution: {integrity: sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.55.1': + resolution: {integrity: sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.54.0': - resolution: {integrity: sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==} + '@rollup/rollup-linux-riscv64-musl@4.55.1': + resolution: {integrity: sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.54.0': - resolution: {integrity: sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==} + '@rollup/rollup-linux-s390x-gnu@4.55.1': + resolution: {integrity: sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.54.0': - resolution: {integrity: sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==} + '@rollup/rollup-linux-x64-gnu@4.55.1': + resolution: {integrity: sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.54.0': - resolution: {integrity: sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==} + '@rollup/rollup-linux-x64-musl@4.55.1': + resolution: {integrity: sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==} cpu: [x64] os: [linux] - '@rollup/rollup-openharmony-arm64@4.54.0': - resolution: {integrity: sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==} + '@rollup/rollup-openbsd-x64@4.55.1': + resolution: {integrity: sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.55.1': + resolution: {integrity: sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.54.0': - resolution: {integrity: sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw==} + '@rollup/rollup-win32-arm64-msvc@4.55.1': + resolution: {integrity: sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.54.0': - resolution: {integrity: sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ==} + '@rollup/rollup-win32-ia32-msvc@4.55.1': + resolution: {integrity: sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.54.0': - resolution: {integrity: sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ==} + '@rollup/rollup-win32-x64-gnu@4.55.1': + resolution: {integrity: sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.54.0': - resolution: {integrity: sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg==} + '@rollup/rollup-win32-x64-msvc@4.55.1': + resolution: {integrity: sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==} cpu: [x64] os: [win32] @@ -1858,8 +1873,8 @@ packages: resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} engines: {node: '>=18'} - globals@16.2.0: - resolution: {integrity: sha512-O+7l9tPdHCU320IigZZPj5zmRCFG9xHmx9cU8FqU2Rp+JN714seHV+2S9+JslCpY4gJwU2vOGox0wzgae/MCEg==} + globals@17.0.0: + resolution: {integrity: sha512-gv5BeD2EssA793rlFWVPMMCqefTlpusw6/2TbAVMy0FzcG8wKJn4O+NqJ4+XWmmwrayJgw5TzrmWjFgmz1XPqw==} engines: {node: '>=18'} graphemer@1.4.0: @@ -2151,8 +2166,8 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rollup@4.54.0: - resolution: {integrity: sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==} + rollup@4.55.1: + resolution: {integrity: sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -3290,95 +3305,104 @@ snapshots: dependencies: quansync: 0.2.10 - '@rollup/plugin-terser@0.4.4(rollup@4.54.0)': + '@rollup/plugin-terser@0.4.4(rollup@4.55.1)': dependencies: serialize-javascript: 6.0.2 smob: 1.5.0 terser: 5.43.1 optionalDependencies: - rollup: 4.54.0 + rollup: 4.55.1 - '@rollup/plugin-typescript@12.3.0(rollup@4.54.0)(tslib@2.8.1)(typescript@5.8.3)': + '@rollup/plugin-typescript@12.3.0(rollup@4.55.1)(tslib@2.8.1)(typescript@5.8.3)': dependencies: - '@rollup/pluginutils': 5.2.0(rollup@4.54.0) + '@rollup/pluginutils': 5.2.0(rollup@4.55.1) resolve: 1.22.10 typescript: 5.8.3 optionalDependencies: - rollup: 4.54.0 + rollup: 4.55.1 tslib: 2.8.1 - '@rollup/pluginutils@5.2.0(rollup@4.54.0)': + '@rollup/pluginutils@5.2.0(rollup@4.55.1)': dependencies: '@types/estree': 1.0.8 estree-walker: 2.0.2 picomatch: 4.0.3 optionalDependencies: - rollup: 4.54.0 + rollup: 4.55.1 - '@rollup/rollup-android-arm-eabi@4.54.0': + '@rollup/rollup-android-arm-eabi@4.55.1': optional: true - '@rollup/rollup-android-arm64@4.54.0': + '@rollup/rollup-android-arm64@4.55.1': optional: true - '@rollup/rollup-darwin-arm64@4.54.0': + '@rollup/rollup-darwin-arm64@4.55.1': optional: true - '@rollup/rollup-darwin-x64@4.54.0': + '@rollup/rollup-darwin-x64@4.55.1': optional: true - '@rollup/rollup-freebsd-arm64@4.54.0': + '@rollup/rollup-freebsd-arm64@4.55.1': optional: true - '@rollup/rollup-freebsd-x64@4.54.0': + '@rollup/rollup-freebsd-x64@4.55.1': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.54.0': + '@rollup/rollup-linux-arm-gnueabihf@4.55.1': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.54.0': + '@rollup/rollup-linux-arm-musleabihf@4.55.1': optional: true - '@rollup/rollup-linux-arm64-gnu@4.54.0': + '@rollup/rollup-linux-arm64-gnu@4.55.1': optional: true - '@rollup/rollup-linux-arm64-musl@4.54.0': + '@rollup/rollup-linux-arm64-musl@4.55.1': optional: true - '@rollup/rollup-linux-loong64-gnu@4.54.0': + '@rollup/rollup-linux-loong64-gnu@4.55.1': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.54.0': + '@rollup/rollup-linux-loong64-musl@4.55.1': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.54.0': + '@rollup/rollup-linux-ppc64-gnu@4.55.1': optional: true - '@rollup/rollup-linux-riscv64-musl@4.54.0': + '@rollup/rollup-linux-ppc64-musl@4.55.1': optional: true - '@rollup/rollup-linux-s390x-gnu@4.54.0': + '@rollup/rollup-linux-riscv64-gnu@4.55.1': optional: true - '@rollup/rollup-linux-x64-gnu@4.54.0': + '@rollup/rollup-linux-riscv64-musl@4.55.1': optional: true - '@rollup/rollup-linux-x64-musl@4.54.0': + '@rollup/rollup-linux-s390x-gnu@4.55.1': optional: true - '@rollup/rollup-openharmony-arm64@4.54.0': + '@rollup/rollup-linux-x64-gnu@4.55.1': optional: true - '@rollup/rollup-win32-arm64-msvc@4.54.0': + '@rollup/rollup-linux-x64-musl@4.55.1': optional: true - '@rollup/rollup-win32-ia32-msvc@4.54.0': + '@rollup/rollup-openbsd-x64@4.55.1': optional: true - '@rollup/rollup-win32-x64-gnu@4.54.0': + '@rollup/rollup-openharmony-arm64@4.55.1': optional: true - '@rollup/rollup-win32-x64-msvc@4.54.0': + '@rollup/rollup-win32-arm64-msvc@4.55.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.55.1': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.55.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.55.1': optional: true '@standard-schema/spec@1.0.0': {} @@ -4128,7 +4152,7 @@ snapshots: globals@15.15.0: {} - globals@16.2.0: {} + globals@17.0.0: {} graphemer@1.4.0: {} @@ -4385,32 +4409,35 @@ snapshots: reusify@1.1.0: {} - rollup@4.54.0: + rollup@4.55.1: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.54.0 - '@rollup/rollup-android-arm64': 4.54.0 - '@rollup/rollup-darwin-arm64': 4.54.0 - '@rollup/rollup-darwin-x64': 4.54.0 - '@rollup/rollup-freebsd-arm64': 4.54.0 - '@rollup/rollup-freebsd-x64': 4.54.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.54.0 - '@rollup/rollup-linux-arm-musleabihf': 4.54.0 - '@rollup/rollup-linux-arm64-gnu': 4.54.0 - '@rollup/rollup-linux-arm64-musl': 4.54.0 - '@rollup/rollup-linux-loong64-gnu': 4.54.0 - '@rollup/rollup-linux-ppc64-gnu': 4.54.0 - '@rollup/rollup-linux-riscv64-gnu': 4.54.0 - '@rollup/rollup-linux-riscv64-musl': 4.54.0 - '@rollup/rollup-linux-s390x-gnu': 4.54.0 - '@rollup/rollup-linux-x64-gnu': 4.54.0 - '@rollup/rollup-linux-x64-musl': 4.54.0 - '@rollup/rollup-openharmony-arm64': 4.54.0 - '@rollup/rollup-win32-arm64-msvc': 4.54.0 - '@rollup/rollup-win32-ia32-msvc': 4.54.0 - '@rollup/rollup-win32-x64-gnu': 4.54.0 - '@rollup/rollup-win32-x64-msvc': 4.54.0 + '@rollup/rollup-android-arm-eabi': 4.55.1 + '@rollup/rollup-android-arm64': 4.55.1 + '@rollup/rollup-darwin-arm64': 4.55.1 + '@rollup/rollup-darwin-x64': 4.55.1 + '@rollup/rollup-freebsd-arm64': 4.55.1 + '@rollup/rollup-freebsd-x64': 4.55.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.55.1 + '@rollup/rollup-linux-arm-musleabihf': 4.55.1 + '@rollup/rollup-linux-arm64-gnu': 4.55.1 + '@rollup/rollup-linux-arm64-musl': 4.55.1 + '@rollup/rollup-linux-loong64-gnu': 4.55.1 + '@rollup/rollup-linux-loong64-musl': 4.55.1 + '@rollup/rollup-linux-ppc64-gnu': 4.55.1 + '@rollup/rollup-linux-ppc64-musl': 4.55.1 + '@rollup/rollup-linux-riscv64-gnu': 4.55.1 + '@rollup/rollup-linux-riscv64-musl': 4.55.1 + '@rollup/rollup-linux-s390x-gnu': 4.55.1 + '@rollup/rollup-linux-x64-gnu': 4.55.1 + '@rollup/rollup-linux-x64-musl': 4.55.1 + '@rollup/rollup-openbsd-x64': 4.55.1 + '@rollup/rollup-openharmony-arm64': 4.55.1 + '@rollup/rollup-win32-arm64-msvc': 4.55.1 + '@rollup/rollup-win32-ia32-msvc': 4.55.1 + '@rollup/rollup-win32-x64-gnu': 4.55.1 + '@rollup/rollup-win32-x64-msvc': 4.55.1 fsevents: 2.3.3 run-parallel@1.2.0: @@ -4652,7 +4679,7 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.54.0 + rollup: 4.55.1 tinyglobby: 0.2.15 optionalDependencies: '@types/node': 24.10.0