Compare commits

..
Author SHA1 Message Date
purvsinojiya-inventyvandGitHub cc9ec9b4ad fix(dialog): use parsed MIME type for Android save dialog (#3519) 2026-07-29 17:05:43 +02:00
paul valladaresandGitHub dc7f87bc7e chore: exclude dev-only files from published crates (#3518)
* chore: exclude dev-only files from published crates

* format
2026-07-29 09:35:15 +02:00
Michael KadzielaandGitHub 304292d740 feat(notification): add rust Action/ActionType api. fix action registration in Android (#2805) 2026-07-27 13:03:43 +02:00
TonyandGitHub 622f02bf21 fix(updater): check ShellExecuteW results (#3516)
* fix(updater): check `ShellExecuteW` results

* document the exit on windows
2026-07-27 16:38:50 +08:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>Tony
abc903c240 chore(deps): update dependency rollup to v4.62.2 (#3423)
* chore(deps): update dependency rollup to v4.62.2

* Fix audit

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Tony <legendmastertony@gmail.com>
2026-07-27 13:29:36 +08:00
TonyandGitHub ab7489c964 feat(updater): option to not restart after install (#3299)
* feat(updater): option to not restart after install

* More platform cfg

* Add the same function for `UpdaterBuilder`

* Fix missing `#[cfg(windows)]`

* Mark `current_exe_args` cfg(windows)

* Mark `on_before_exit` cfg(windows)

* Mark `installer_args` cfg(windows)

* Note about `installer_arg` apply to both

* Remove current args and restart together

* Only build needed bundle on windows as well

* Remove `/NS` since it breaks the msi updater

* Add launch updater debug log

* Add a folder to test updates

* Remove unused `#[allow(unused)]`

* Format

* messed up git stage

* Add change file

* Clean up

* Disable NSIS compression for API example

* Bump wry for v1 test to pull in https://github.com/tauri-apps/wry/pull/1703

* format

* Make typescript happy

* Close new update on destroy
2026-07-21 18:25:16 +08:00
3fb27bf13a fix(docs): deep link platform specific and fs scope (#3504)
* deep link fix Platform Specific heading

* fix fs permission toml

* .changes

* Update change files

Co-authored-by: Tony <68118705+Legend-Master@users.noreply.github.com>

* Update .changes/fs-deny-scope.md

* fix eol

---------

Co-authored-by: Tony <68118705+Legend-Master@users.noreply.github.com>
2026-07-21 14:56:52 +08:00
TonyandGitHub 7e45774610 refactor(fs): cfg gate some ios code (#3510) 2026-07-21 14:42:45 +08:00
526726162f fix(nfc): adapt backend invoke to frontend (#3419)
* fix(nfc): adapt backend invoke to frontend

* Revert "fix(nfc): adapt backend invoke to frontend"

This reverts commit e34306e083.

* fix:  enforce nfc function return type

* Update .changes/nfc.md

Co-authored-by: Fabian-Lars <30730186+FabianLars@users.noreply.github.com>

---------

Co-authored-by: Fabian-Lars <30730186+FabianLars@users.noreply.github.com>
2026-07-20 13:11:20 +02:00
68 changed files with 1039 additions and 517 deletions
+6
View File
@@ -0,0 +1,6 @@
---
"deep-link": patch
"deep-link-js": patch
---
adds proper Platform-specific sections to the docs for `register`, `unregister`, `isRegistered`, and `onOpenUrl`
+6
View File
@@ -0,0 +1,6 @@
---
"dialog": patch
"dialog-js": patch
---
Fix Android save dialog MIME type when a single MIME type is provided.
+6
View File
@@ -0,0 +1,6 @@
---
"fs": patch
"fs-js": patch
---
Fixed `deny-webview-data` has no effect
+6
View File
@@ -0,0 +1,6 @@
---
nfc: patch
nfc-js: patch
---
Expect a `NFCTag` as input and return a `ScanResponse` in the `scan` function.
+7
View File
@@ -0,0 +1,7 @@
---
notification: minor
notification-js: minor
---
- Add Rust builders for mobile notification action types.
- Fix Android action-group entries being stored under the action type ID instead of sequential indices, causing later actions to overwrite earlier ones.
@@ -0,0 +1,6 @@
---
"updater": patch
"updater-js": patch
---
On Windows, returns an error if failed to spawn the installer in `install` through `ShellExecuteW`
+6
View File
@@ -0,0 +1,6 @@
---
"updater": minor
"updater-js": minor
---
On Windows, add a new option `restartAfterInstall`/`restart_after_install` to install an update without the installer re-launching the app
Generated
+3 -23
View File
@@ -916,17 +916,6 @@ dependencies = [
"uuid", "uuid",
] ]
[[package]]
name = "cfb"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a347dcabdae9c31b0825fd6a8bed285ec9c2acb89c47827126d52fa4f59cece3"
dependencies = [
"fnv",
"uuid",
"web-time",
]
[[package]] [[package]]
name = "cfg-expr" name = "cfg-expr"
version = "0.15.8" version = "0.15.8"
@@ -3036,16 +3025,7 @@ version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7"
dependencies = [ dependencies = [
"cfb 0.7.3", "cfb",
]
[[package]]
name = "infer"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4200d433cbd5178df7797c9c2e75b348b728e39631cf14520d1e2fc424201f4"
dependencies = [
"cfb 0.14.0",
] ]
[[package]] [[package]]
@@ -6879,7 +6859,7 @@ dependencies = [
"flate2", "flate2",
"futures-util", "futures-util",
"http", "http",
"infer 0.22.0", "infer",
"log", "log",
"minisign-verify", "minisign-verify",
"osakit", "osakit",
@@ -7016,7 +6996,7 @@ dependencies = [
"getrandom 0.3.2", "getrandom 0.3.2",
"glob", "glob",
"http", "http",
"infer 0.19.0", "infer",
"json-patch", "json-patch",
"log", "log",
"memchr", "memchr",
+4 -4
View File
@@ -15,7 +15,7 @@
"@tauri-apps/plugin-biometric": "workspace:*", "@tauri-apps/plugin-biometric": "workspace:*",
"@tauri-apps/plugin-cli": "workspace:*", "@tauri-apps/plugin-cli": "workspace:*",
"@tauri-apps/plugin-clipboard-manager": "workspace:*", "@tauri-apps/plugin-clipboard-manager": "workspace:*",
"@tauri-apps/plugin-dialog": "2", "@tauri-apps/plugin-dialog": "workspace:*",
"@tauri-apps/plugin-fs": "workspace:*", "@tauri-apps/plugin-fs": "workspace:*",
"@tauri-apps/plugin-geolocation": "workspace:*", "@tauri-apps/plugin-geolocation": "workspace:*",
"@tauri-apps/plugin-global-shortcut": "workspace:*", "@tauri-apps/plugin-global-shortcut": "workspace:*",
@@ -27,7 +27,7 @@
"@tauri-apps/plugin-os": "workspace:*", "@tauri-apps/plugin-os": "workspace:*",
"@tauri-apps/plugin-process": "workspace:*", "@tauri-apps/plugin-process": "workspace:*",
"@tauri-apps/plugin-shell": "workspace:*", "@tauri-apps/plugin-shell": "workspace:*",
"@tauri-apps/plugin-store": "2", "@tauri-apps/plugin-store": "workspace:*",
"@tauri-apps/plugin-updater": "workspace:*", "@tauri-apps/plugin-updater": "workspace:*",
"@tauri-apps/plugin-upload": "workspace:*", "@tauri-apps/plugin-upload": "workspace:*",
"@zerodevx/svelte-json-view": "1.0.11" "@zerodevx/svelte-json-view": "1.0.11"
@@ -38,8 +38,8 @@
"@sveltejs/vite-plugin-svelte": "^7.0.0", "@sveltejs/vite-plugin-svelte": "^7.0.0",
"@tauri-apps/cli": "2.11.4", "@tauri-apps/cli": "2.11.4",
"@unocss/extractor-svelte": "^66.6.7", "@unocss/extractor-svelte": "^66.6.7",
"svelte": "^5.54.0", "svelte": "^5.55.7",
"unocss": "^66.6.7", "unocss": "^66.6.7",
"vite": "^8.0.1" "vite": "^8.0.16"
} }
} }
+5
View File
@@ -77,7 +77,9 @@
}, },
"updater": { "updater": {
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDE5QzMxNjYwNTM5OEUwNTgKUldSWTRKaFRZQmJER1h4d1ZMYVA3dnluSjdpN2RmMldJR09hUFFlZDY0SlFqckkvRUJhZDJVZXAK", "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDE5QzMxNjYwNTM5OEUwNTgKUldSWTRKaFRZQmJER1h4d1ZMYVA3dnluSjdpN2RmMldJR09hUFFlZDY0SlFqckkvRUJhZDJVZXAK",
"dangerousInsecureTransportProtocol": true,
"endpoints": [ "endpoints": [
"http://localhost:5173/updater-test/updater.json",
"https://tauri-update-server.vercel.app/update/{{target}}/{{current_version}}" "https://tauri-update-server.vercel.app/update/{{target}}/{{current_version}}"
] ]
} }
@@ -99,6 +101,9 @@
"localePath": "locales/pt-BR.wxl" "localePath": "locales/pt-BR.wxl"
} }
} }
},
"nsis": {
"compression": "none"
} }
}, },
"iOS": { "iOS": {
+19 -11
View File
@@ -1,12 +1,18 @@
<script> <script lang="ts">
import { check } from '@tauri-apps/plugin-updater' import { check, Update } from '@tauri-apps/plugin-updater'
import { relaunch } from '@tauri-apps/plugin-process' import { relaunch } from '@tauri-apps/plugin-process'
import { onDestroy } from 'svelte'
export let onMessage let { onMessage } = $props()
let isChecking, isInstalling, newUpdate let isChecking = $state(false)
let totalSize = 0, let isInstalling = $state(false)
downloadedSize = 0 let newUpdate = $state<Update | undefined>()
let totalSize = $state(0)
let downloadedSize = $state(0)
let progress = $derived(
totalSize ? Math.round((downloadedSize / totalSize) * 100) : 0
)
async function checkUpdate() { async function checkUpdate() {
isChecking = true isChecking = true
@@ -31,10 +37,10 @@
isInstalling = true isInstalling = true
downloadedSize = 0 downloadedSize = 0
try { try {
await newUpdate.downloadAndInstall((downloadProgress) => { await newUpdate!.downloadAndInstall((downloadProgress) => {
switch (downloadProgress.event) { switch (downloadProgress.event) {
case 'Started': case 'Started':
totalSize = downloadProgress.data.contentLength totalSize = downloadProgress.data.contentLength!
break break
case 'Progress': case 'Progress':
downloadedSize += downloadProgress.data.chunkLength downloadedSize += downloadProgress.data.chunkLength
@@ -54,14 +60,16 @@
} }
} }
$: progress = totalSize ? Math.round((downloadedSize / totalSize) * 100) : 0 onDestroy(() => {
newUpdate?.close()
})
</script> </script>
<div class="flex children:grow children:h10"> <div class="flex children:grow children:h10">
{#if !isChecking && !newUpdate} {#if !isChecking && !newUpdate}
<button class="btn" on:click={checkUpdate}>Check update</button> <button class="btn" onclick={checkUpdate}>Check update</button>
{:else if !isInstalling && newUpdate} {:else if !isInstalling && newUpdate}
<button class="btn" on:click={install}>Install update</button> <button class="btn" onclick={install}>Install update</button>
{:else} {:else}
<div class="progress"> <div class="progress">
<span>{progress}%</span> <span>{progress}%</span>
+5
View File
@@ -0,0 +1,5 @@
*
!.gitignore
!README.md
!updater.json
+10
View File
@@ -0,0 +1,10 @@
To test the updater:
1. Comment out `verify_signature` inside `plugins/updater/src/updater.rs`
2. Run `pnpm tauri build --debug` to build the bundles
3. Copy the bundle you want to test to this folder (`examples/api/updater-test/`)
4. Run `pnpm tauri dev` and open the Updater tab to check
If the bundle you're testing doesn't exist in the `updater.json` yet, add it manually and welcome to open an PR
> The `updater.json` and debug bundles will be served by `vite`
+11
View File
@@ -0,0 +1,11 @@
{
"version": "2.1.0",
"notes": "Test update!",
"pub_date": "2026-03-01T14:04:20+00:00",
"platforms": {
"windows-x86_64": {
"signature": "",
"url": "http://localhost:5173/updater-test/Tauri API_2.0.0_x64_en-US.msi"
}
}
}
+1 -1
View File
@@ -27,7 +27,7 @@ export default defineConfig(async () => {
port: 5173, port: 5173,
strictPort: true, strictPort: true,
fs: { fs: {
allow: ['.', '../../tooling/api/dist'] allow: ['.']
} }
} }
} }
+1 -1
View File
@@ -19,7 +19,7 @@
"eslint-config-prettier": "10.1.8", "eslint-config-prettier": "10.1.8",
"eslint-plugin-security": "4.0.1", "eslint-plugin-security": "4.0.1",
"prettier": "3.8.3", "prettier": "3.8.3",
"rollup": "4.60.3", "rollup": "4.62.2",
"tslib": "2.8.1", "tslib": "2.8.1",
"typescript": "6.0.3", "typescript": "6.0.3",
"typescript-eslint": "8.58.2" "typescript-eslint": "8.58.2"
+7
View File
@@ -8,6 +8,13 @@ edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }
repository = { workspace = true } repository = { workspace = true }
links = "tauri-plugin-autostart" links = "tauri-plugin-autostart"
exclude = [
"/banner.png",
"/guest-js",
"/package.json",
"/rollup.config.js",
"/tsconfig.json",
]
[package.metadata.platforms.support] [package.metadata.platforms.support]
windows = { level = "full", notes = "" } windows = { level = "full", notes = "" }
+7 -1
View File
@@ -8,7 +8,13 @@ license = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }
repository = { workspace = true } repository = { workspace = true }
links = "tauri-plugin-barcode-scanner" links = "tauri-plugin-barcode-scanner"
exclude = [
"/banner.png",
"/guest-js",
"/package.json",
"/rollup.config.js",
"/tsconfig.json",
]
[package.metadata.docs.rs] [package.metadata.docs.rs]
targets = ["x86_64-linux-android"] targets = ["x86_64-linux-android"]
+1
View File
@@ -7,6 +7,7 @@ authors = { workspace = true }
license = { workspace = true } license = { workspace = true }
repository = { workspace = true } repository = { workspace = true }
links = "tauri-plugin-biometric" links = "tauri-plugin-biometric"
exclude = ["/guest-js", "/package.json", "/rollup.config.js", "/tsconfig.json"]
[package.metadata.docs.rs] [package.metadata.docs.rs]
targets = ["x86_64-linux-android"] targets = ["x86_64-linux-android"]
+7
View File
@@ -8,6 +8,13 @@ license = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }
repository = { workspace = true } repository = { workspace = true }
links = "tauri-plugin-cli" links = "tauri-plugin-cli"
exclude = [
"/banner.png",
"/guest-js",
"/package.json",
"/rollup.config.js",
"/tsconfig.json",
]
[package.metadata.platforms.support] [package.metadata.platforms.support]
windows = { level = "full", notes = "" } windows = { level = "full", notes = "" }
+7
View File
@@ -8,6 +8,13 @@ license = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }
repository = { workspace = true } repository = { workspace = true }
links = "tauri-plugin-clipboard-manager" links = "tauri-plugin-clipboard-manager"
exclude = [
"/banner.png",
"/guest-js",
"/package.json",
"/rollup.config.js",
"/tsconfig.json",
]
[package.metadata.docs.rs] [package.metadata.docs.rs]
+8
View File
@@ -8,6 +8,14 @@ edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }
repository = { workspace = true } repository = { workspace = true }
links = "tauri-plugin-deep-link" links = "tauri-plugin-deep-link"
exclude = [
"/banner.png",
"/examples",
"/guest-js",
"/package.json",
"/rollup.config.js",
"/tsconfig.json",
]
[package.metadata.docs.rs] [package.metadata.docs.rs]
targets = ["x86_64-linux-android"] targets = ["x86_64-linux-android"]
+2 -2
View File
@@ -15,7 +15,7 @@
}, },
"devDependencies": { "devDependencies": {
"@tauri-apps/cli": "2.11.4", "@tauri-apps/cli": "2.11.4",
"typescript": "^6.0.0", "typescript": "^6.0.3",
"vite": "^8.0.1" "vite": "^8.0.16"
} }
} }
+17 -7
View File
@@ -14,9 +14,11 @@ import { type UnlistenFn, listen } from '@tauri-apps/api/event'
* const urls = await getCurrent(); * const urls = await getCurrent();
* ``` * ```
* *
* #### - **Windows / Linux**: This function reads the command line arguments and checks if there's only one value, which must be an URL with scheme matching one of the configured values. * #### Platform-specific
* Note that you must manually check the arguments when registering deep link schemes dynamically with [`Self::register`]. *
* Additionally, the deep link might have been provided as a CLI argument so you should check if its format matches what you expect.. * - **Windows / Linux:** This function reads the command line arguments and checks if there's only one value, which must be an URL with scheme matching one of the configured values.
* Note that you must manually check the arguments when registering deep link schemes dynamically with [`Self::register`].
* Additionally, the deep link might have been provided as a CLI argument so you should check if its format matches what you expect.
* *
* @since 2.0.0 * @since 2.0.0
*/ */
@@ -35,7 +37,9 @@ export async function getCurrent(): Promise<string[] | null> {
* await register("my-scheme"); * await register("my-scheme");
* ``` * ```
* *
* #### - **macOS / Android / iOS**: Unsupported. * #### Platform-specific
*
* - **macOS / Android / iOS:** Unsupported.
* *
* @since 2.0.0 * @since 2.0.0
*/ */
@@ -54,7 +58,9 @@ export async function register(protocol: string): Promise<null> {
* await unregister("my-scheme"); * await unregister("my-scheme");
* ``` * ```
* *
* #### - **macOS / Linux / Android / iOS**: Unsupported. * #### Platform-specific
*
* - **macOS / Linux / Android / iOS:** Unsupported.
* *
* @since 2.0.0 * @since 2.0.0
*/ */
@@ -73,7 +79,9 @@ export async function unregister(protocol: string): Promise<null> {
* await isRegistered("my-scheme"); * await isRegistered("my-scheme");
* ``` * ```
* *
* #### - **macOS / Android / iOS**: Unsupported. * #### Platform-specific
*
* - **macOS / Android / iOS:** Unsupported.
* *
* @since 2.0.0 * @since 2.0.0
*/ */
@@ -92,7 +100,9 @@ export async function isRegistered(protocol: string): Promise<boolean> {
* await onOpenUrl((urls) => { console.log(urls) }); * await onOpenUrl((urls) => { console.log(urls) });
* ``` * ```
* *
* #### - **Windows / Linux**: Unsupported without the single-instance plugin. The OS will spawn a new app instance passing the URL as a CLI argument. * #### Platform-specific
*
* - **Windows / Linux:** Unsupported without the single-instance plugin. The OS will spawn a new app instance passing the URL as a CLI argument.
* *
* @since 2.0.0 * @since 2.0.0
*/ */
+8
View File
@@ -8,6 +8,14 @@ license = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }
repository = { workspace = true } repository = { workspace = true }
links = "tauri-plugin-dialog" links = "tauri-plugin-dialog"
exclude = [
"/banner.png",
"/guest-js",
"/package.json",
"/rollup.config.js",
"/test",
"/tsconfig.json",
]
[features] [features]
default = ["gtk3"] default = ["gtk3"]
@@ -204,9 +204,10 @@ class DialogPlugin(private val activity: Activity): Plugin(activity) {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT) val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
intent.addCategory(Intent.CATEGORY_OPENABLE) intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.putExtra(Intent.EXTRA_TITLE, args.fileName ?: "") intent.putExtra(Intent.EXTRA_TITLE, args.fileName ?: "")
intent.type = "*/*" if (parsedTypes.size == 1) {
intent.type = parsedTypes.first()
if (parsedTypes.isNotEmpty()) { } else {
intent.type = "*/*"
intent.putExtra(Intent.EXTRA_MIME_TYPES, parsedTypes) intent.putExtra(Intent.EXTRA_MIME_TYPES, parsedTypes)
} }
+7
View File
@@ -8,6 +8,13 @@ edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }
repository = { workspace = true } repository = { workspace = true }
links = "tauri-plugin-fs" links = "tauri-plugin-fs"
exclude = [
"/banner.png",
"/guest-js",
"/package.json",
"/rollup.config.js",
"/tsconfig.json",
]
[package.metadata.platforms.support] [package.metadata.platforms.support]
windows = { level = "full", notes = "Apps installed via MSI or NSIS in `perMachine` and `both` mode require admin permissions for write access in `$RESOURCES` folder" } windows = { level = "full", notes = "Apps installed via MSI or NSIS in `perMachine` and `both` mode require admin permissions for write access in `$RESOURCES` folder" }
@@ -6,7 +6,7 @@ description = """This denies read access to the
`$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here. `$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here.
Allowing access can lead to sensitive information disclosure and should be well considered.""" Allowing access can lead to sensitive information disclosure and should be well considered."""
[[scope.deny]] [[permission.scope.deny]]
path = "$APPLOCALDATA/**" path = "$APPLOCALDATA/**"
[[permission]] [[permission]]
@@ -15,5 +15,5 @@ description = """This denies read access to the
`$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here. `$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here.
Allowing access can lead to sensitive information disclosure and should be well considered.""" Allowing access can lead to sensitive information disclosure and should be well considered."""
[[scope.deny]] [[permission.scope.deny]]
path = "$APPLOCALDATA/EBWebView/**" path = "$APPLOCALDATA/EBWebView/**"
+106 -109
View File
@@ -74,7 +74,7 @@ pub type CommandResult<T> = std::result::Result<T, CommandError>;
/// Represents either a plain PathBuf or a PathHandle that manages security-scoped resources. /// Represents either a plain PathBuf or a PathHandle that manages security-scoped resources.
pub enum PathKind<R: Runtime> { pub enum PathKind<R: Runtime> {
/// A plain path that doesn't manage security-scoped resources. /// A plain path that doesn't manage security-scoped resources.
#[allow(dead_code)] // only used on mobile #[cfg(mobile)] // only used on mobile
Path(PathBuf), Path(PathBuf),
/// A path handle that manages security-scoped resources and will clean them up on drop. /// A path handle that manages security-scoped resources and will clean them up on drop.
Handle(PathHandle<R>), Handle(PathHandle<R>),
@@ -84,6 +84,7 @@ impl<R: Runtime> PathKind<R> {
/// Get a reference to the underlying path. /// Get a reference to the underlying path.
pub fn as_path(&self) -> &Path { pub fn as_path(&self) -> &Path {
match self { match self {
#[cfg(mobile)]
PathKind::Path(p) => p.as_ref(), PathKind::Path(p) => p.as_ref(),
PathKind::Handle(h) => h.as_ref(), PathKind::Handle(h) => h.as_ref(),
} }
@@ -92,6 +93,7 @@ impl<R: Runtime> PathKind<R> {
/// Get a reference to the underlying PathBuf. /// Get a reference to the underlying PathBuf.
pub fn as_path_buf(&self) -> &PathBuf { pub fn as_path_buf(&self) -> &PathBuf {
match self { match self {
#[cfg(mobile)]
PathKind::Path(p) => p, PathKind::Path(p) => p,
PathKind::Handle(h) => h, PathKind::Handle(h) => h,
} }
@@ -114,27 +116,13 @@ impl<R: Runtime> AsRef<PathBuf> for PathKind<R> {
pub struct FileHandle<R: Runtime> { pub struct FileHandle<R: Runtime> {
file: File, file: File,
path: PathKind<R>, path: PathKind<R>,
#[allow(dead_code)] // Used in Drop implementation #[cfg(target_os = "ios")]
path_: SafeFilePath, path_: SafeFilePath,
#[allow(dead_code)] // Used in Drop implementation #[cfg(target_os = "ios")]
app_handle: tauri::AppHandle<R>, app_handle: tauri::AppHandle<R>,
} }
impl<R: Runtime> FileHandle<R> { impl<R: Runtime> FileHandle<R> {
fn new(
file: File,
path: PathKind<R>,
path_: SafeFilePath,
app_handle: tauri::AppHandle<R>,
) -> Self {
Self {
file,
path,
path_,
app_handle,
}
}
/// Get the resolved path. /// Get the resolved path.
pub fn path(&self) -> &Path { pub fn path(&self) -> &Path {
self.path.as_path() self.path.as_path()
@@ -155,41 +143,39 @@ impl<R: Runtime> DerefMut for FileHandle<R> {
} }
} }
#[cfg(target_os = "ios")]
impl<R: Runtime> Drop for FileHandle<R> { impl<R: Runtime> Drop for FileHandle<R> {
fn drop(&mut self) { fn drop(&mut self) {
#[cfg(target_os = "ios")] // Only clean up if we have a plain PathBuf, not a PathHandle
{ // PathHandle will handle its own cleanup when it's dropped
// Only clean up if we have a plain PathBuf, not a PathHandle if let PathKind::Path(_) = &self.path {
// PathHandle will handle its own cleanup when it's dropped use crate::{FilePath, FsExt};
if let PathKind::Path(_) = &self.path { // Convert SafeFilePath to FilePath
use crate::{FilePath, FsExt}; let file_path: FilePath = match &self.path_ {
// Convert SafeFilePath to FilePath SafeFilePath::Url(url) => FilePath::Url(url.clone()),
let file_path: FilePath = match &self.path_ { SafeFilePath::Path(safe_path) => FilePath::Path(safe_path.as_ref().to_owned()),
SafeFilePath::Url(url) => FilePath::Url(url.clone()), };
SafeFilePath::Path(safe_path) => FilePath::Path(safe_path.as_ref().to_owned()),
};
// Only clean up if we're tracking this resource // Only clean up if we're tracking this resource
// If start_accessing_security_scoped_resource was used, it won't be in our tracking // If start_accessing_security_scoped_resource was used, it won't be in our tracking
// and we shouldn't interfere // and we shouldn't interfere
if let FilePath::Url(url) = file_path { if let FilePath::Url(url) = file_path {
if url.scheme() == "file" { if url.scheme() == "file" {
let security_scoped_resources = let security_scoped_resources =
self.app_handle.state::<crate::SecurityScopedResources>(); self.app_handle.state::<crate::SecurityScopedResources>();
// Only clean up if it's not tracked manually // Only clean up if it's not tracked manually
if !security_scoped_resources.is_tracked_manually(url.as_str()) { if !security_scoped_resources.is_tracked_manually(url.as_str()) {
log::debug!("Stopping accessing security-scoped resource for URL: {url} on drop"); log::debug!(
let _ = self "Stopping accessing security-scoped resource for URL: {url} on drop"
.app_handle );
.fs() let _ = self
.stop_accessing_security_scoped_resource(FilePath::Url( .app_handle
url.clone(), .fs()
)); .stop_accessing_security_scoped_resource(FilePath::Url(url.clone()));
security_scoped_resources.remove(url.as_str()); security_scoped_resources.remove(url.as_str());
} else { } else {
log::debug!("Not cleaning up security-scoped resource for URL: {url} on drop (manually tracked via start_accessing_security_scoped_resource)"); log::debug!("Not cleaning up security-scoped resource for URL: {url} on drop (manually tracked via start_accessing_security_scoped_resource)");
}
} }
} }
} }
@@ -236,38 +222,36 @@ impl<R: Runtime> AsRef<PathBuf> for PathHandle<R> {
} }
} }
#[cfg(target_os = "ios")]
impl<R: Runtime> Drop for PathHandle<R> { impl<R: Runtime> Drop for PathHandle<R> {
fn drop(&mut self) { fn drop(&mut self) {
#[cfg(target_os = "ios")] use crate::{FilePath, FsExt};
{ // Convert SafeFilePath to FilePath
use crate::{FilePath, FsExt}; let file_path: FilePath = match &self.path_ {
// Convert SafeFilePath to FilePath SafeFilePath::Url(url) => FilePath::Url(url.clone()),
let file_path: FilePath = match &self.path_ { SafeFilePath::Path(safe_path) => FilePath::Path(safe_path.as_ref().to_owned()),
SafeFilePath::Url(url) => FilePath::Url(url.clone()), };
SafeFilePath::Path(safe_path) => FilePath::Path(safe_path.as_ref().to_owned()),
};
// Only clean up if we're tracking this resource (i.e., resolve_path started it) // Only clean up if we're tracking this resource (i.e., resolve_path started it)
// If start_accessing_security_scoped_resource was used, it won't be in our tracking // If start_accessing_security_scoped_resource was used, it won't be in our tracking
// and we shouldn't interfere // and we shouldn't interfere
if let FilePath::Url(url) = file_path { if let FilePath::Url(url) = file_path {
if url.scheme() == "file" { if url.scheme() == "file" {
let security_scoped_resources = let security_scoped_resources =
self.app_handle.state::<crate::SecurityScopedResources>(); self.app_handle.state::<crate::SecurityScopedResources>();
// Only clean up if it's not tracked manually // Only clean up if it's not tracked manually
if !security_scoped_resources.is_tracked_manually(url.as_str()) { if !security_scoped_resources.is_tracked_manually(url.as_str()) {
log::debug!( log::debug!(
"Stopping accessing security-scoped resource for URL: {url} on drop" "Stopping accessing security-scoped resource for URL: {url} on drop"
); );
let _ = self let _ = self
.app_handle .app_handle
.fs() .fs()
.stop_accessing_security_scoped_resource(FilePath::Url(url.clone())); .stop_accessing_security_scoped_resource(FilePath::Url(url.clone()));
security_scoped_resources.remove(url.as_str()); security_scoped_resources.remove(url.as_str());
} else { } else {
log::debug!("Not cleaning up security-scoped resource for URL: {url} on drop (manually tracked via start_accessing_security_scoped_resource)"); log::debug!("Not cleaning up security-scoped resource for URL: {url} on drop (manually tracked via start_accessing_security_scoped_resource)");
}
} }
} }
} }
@@ -288,6 +272,7 @@ pub fn create<R: Runtime>(
path: SafeFilePath, path: SafeFilePath,
options: Option<BaseOptions>, options: Option<BaseOptions>,
) -> CommandResult<ResourceId> { ) -> CommandResult<ResourceId> {
#[cfg(target_os = "ios")]
let path_ = path.clone(); let path_ = path.clone();
let resolved_path_handle = resolve_path( let resolved_path_handle = resolve_path(
"create", "create",
@@ -303,13 +288,16 @@ pub fn create<R: Runtime>(
resolved_path_handle.display() resolved_path_handle.display()
) )
})?; })?;
#[cfg(target_os = "ios")]
let app_handle = webview.app_handle().clone(); let app_handle = webview.app_handle().clone();
let file_handle = FileHandle::new( let file_handle = FileHandle {
file, file,
PathKind::Handle(resolved_path_handle), path: PathKind::Handle(resolved_path_handle),
#[cfg(target_os = "ios")]
path_, path_,
#[cfg(target_os = "ios")]
app_handle, app_handle,
); };
let rid = webview let rid = webview
.resources_table() .resources_table()
.add(StdFileResource::new(file_handle)); .add(StdFileResource::new(file_handle));
@@ -1417,6 +1405,7 @@ fn resolve_file_in_fs<R: Runtime>(
path: SafeFilePath, path: SafeFilePath,
open_options: OpenOptions, open_options: OpenOptions,
) -> CommandResult<FileHandle<R>> { ) -> CommandResult<FileHandle<R>> {
#[cfg(target_os = "ios")]
let path_ = path.clone(); let path_ = path.clone();
let resolved_path_handle = resolve_path( let resolved_path_handle = resolve_path(
permission, permission,
@@ -1436,13 +1425,16 @@ fn resolve_file_in_fs<R: Runtime>(
) )
})?; })?;
#[cfg(target_os = "ios")]
let app_handle = webview.app_handle().clone(); let app_handle = webview.app_handle().clone();
Ok(FileHandle::new( Ok(FileHandle {
file, file,
PathKind::Handle(resolved_path_handle), path: PathKind::Handle(resolved_path_handle),
#[cfg(target_os = "ios")]
path_, path_,
#[cfg(target_os = "ios")]
app_handle, app_handle,
)) })
} }
#[cfg(mobile)] #[cfg(mobile)]
@@ -1456,6 +1448,7 @@ pub fn resolve_file<R: Runtime>(
) -> CommandResult<FileHandle<R>> { ) -> CommandResult<FileHandle<R>> {
use crate::FsExt; use crate::FsExt;
#[cfg(target_os = "ios")]
let path_ = path.clone(); let path_ = path.clone();
match path { match path {
SafeFilePath::Url(url) => { SafeFilePath::Url(url) => {
@@ -1463,13 +1456,16 @@ pub fn resolve_file<R: Runtime>(
let file = webview let file = webview
.fs() .fs()
.open(SafeFilePath::Url(url.clone()), open_options.options)?; .open(SafeFilePath::Url(url.clone()), open_options.options)?;
#[cfg(target_os = "ios")]
let app_handle = webview.app_handle().clone(); let app_handle = webview.app_handle().clone();
Ok(FileHandle::new( Ok(FileHandle {
file, file,
PathKind::Path(resolved_path), path: PathKind::Path(resolved_path),
#[cfg(target_os = "ios")]
path_, path_,
#[cfg(target_os = "ios")]
app_handle, app_handle,
)) })
} }
SafeFilePath::Path(path) => resolve_file_in_fs( SafeFilePath::Path(path) => resolve_file_in_fs(
permission, permission,
@@ -1494,36 +1490,37 @@ pub fn resolve_path<R: Runtime>(
// On iOS, start accessing security-scoped resource if the path is a file URL // On iOS, start accessing security-scoped resource if the path is a file URL
// Only if it hasn't been started already via start_accessing_security_scoped_resource // Only if it hasn't been started already via start_accessing_security_scoped_resource
#[cfg(target_os = "ios")] #[cfg(target_os = "ios")]
{ if let SafeFilePath::Url(url) = &path {
if let SafeFilePath::Url(url) = &path { if url.scheme() == "file" {
if url.scheme() == "file" { use objc2_foundation::{NSString, NSURL};
use objc2_foundation::{NSString, NSURL};
let security_scoped_resources = webview.state::<crate::SecurityScopedResources>(); let security_scoped_resources = webview.state::<crate::SecurityScopedResources>();
// Check if already active (started via start_accessing_security_scoped_resource) // Check if already active (started via start_accessing_security_scoped_resource)
if !security_scoped_resources.is_tracked_manually(url.as_str()) { if !security_scoped_resources.is_tracked_manually(url.as_str()) {
let url_nsstring = NSString::from_str(url.as_str()); let url_nsstring = NSString::from_str(url.as_str());
let ns_url = unsafe { NSURL::URLWithString(&url_nsstring) }; let ns_url = unsafe { NSURL::URLWithString(&url_nsstring) };
if let Some(ns_url) = ns_url { if let Some(ns_url) = ns_url {
// Start accessing the security-scoped resource // Start accessing the security-scoped resource
// This is required for files outside the app's sandbox (e.g., from file picker) // This is required for files outside the app's sandbox (e.g., from file picker)
unsafe { unsafe {
let success = ns_url.startAccessingSecurityScopedResource(); let success = ns_url.startAccessingSecurityScopedResource();
if success { if success {
log::debug!("Started accessing security-scoped resource for URL: {} (via resolve_path)", url.as_str()); log::debug!("Started accessing security-scoped resource for URL: {} (via resolve_path)", url.as_str());
// Track it so we know to clean it up // Track it so we know to clean it up
security_scoped_resources.track_manually(url.as_str().to_string()); security_scoped_resources.track_manually(url.as_str().to_string());
} else { } else {
log::warn!("Failed to start accessing security-scoped resource for URL: {}", url.as_str()); log::warn!(
} "Failed to start accessing security-scoped resource for URL: {}",
url.as_str()
);
} }
} else {
log::debug!("Failed to create NSURL from URL: {}, ignoring security-scoped resource access request", url.as_str());
} }
} else { } else {
log::debug!("Security-scoped resource already active for URL: {} (started via start_accessing_security_scoped_resource), skipping", url.as_str()); log::debug!("Failed to create NSURL from URL: {}, ignoring security-scoped resource access request", url.as_str());
} }
} else {
log::debug!("Security-scoped resource already active for URL: {} (started via start_accessing_security_scoped_resource), skipping", url.as_str());
} }
} }
} }
+1
View File
@@ -7,6 +7,7 @@ authors = { workspace = true }
license = { workspace = true } license = { workspace = true }
repository = { workspace = true } repository = { workspace = true }
links = "tauri-plugin-geolocation" links = "tauri-plugin-geolocation"
exclude = ["/guest-js", "/package.json", "/rollup.config.js", "/tsconfig.json"]
[package.metadata.docs.rs] [package.metadata.docs.rs]
targets = ["x86_64-linux-android"] targets = ["x86_64-linux-android"]
+7
View File
@@ -8,6 +8,13 @@ license = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }
repository = { workspace = true } repository = { workspace = true }
links = "tauri-plugin-global-shortcut" links = "tauri-plugin-global-shortcut"
exclude = [
"/banner.png",
"/guest-js",
"/package.json",
"/rollup.config.js",
"/tsconfig.json",
]
[package.metadata.platforms.support] [package.metadata.platforms.support]
windows = { level = "full", notes = "" } windows = { level = "full", notes = "" }
+1
View File
@@ -7,6 +7,7 @@ authors = { workspace = true }
license = { workspace = true } license = { workspace = true }
repository = { workspace = true } repository = { workspace = true }
links = "tauri-plugin-haptics" links = "tauri-plugin-haptics"
exclude = ["/guest-js", "/package.json", "/rollup.config.js", "/tsconfig.json"]
[package.metadata.docs.rs] [package.metadata.docs.rs]
targets = ["x86_64-linux-android"] targets = ["x86_64-linux-android"]
+7
View File
@@ -8,6 +8,13 @@ license = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }
repository = { workspace = true } repository = { workspace = true }
links = "tauri-plugin-http" links = "tauri-plugin-http"
exclude = [
"/banner.png",
"/guest-js",
"/package.json",
"/rollup.config.js",
"/tsconfig.json",
]
[package.metadata.platforms.support] [package.metadata.platforms.support]
windows = { level = "full", notes = "" } windows = { level = "full", notes = "" }
+1
View File
@@ -7,6 +7,7 @@ license = { workspace = true }
edition = { workspace = true } edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }
repository = { workspace = true } repository = { workspace = true }
exclude = ["/banner.png"]
[package.metadata.platforms.support] [package.metadata.platforms.support]
windows = { level = "full", notes = "" } windows = { level = "full", notes = "" }
+7
View File
@@ -8,6 +8,13 @@ edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }
repository = { workspace = true } repository = { workspace = true }
links = "tauri-plugin-log" links = "tauri-plugin-log"
exclude = [
"/banner.png",
"/guest-js",
"/package.json",
"/rollup.config.js",
"/tsconfig.json",
]
[package.metadata.platforms.support] [package.metadata.platforms.support]
windows = { level = "full", notes = "" } windows = { level = "full", notes = "" }
+1
View File
@@ -7,6 +7,7 @@ authors = { workspace = true }
license = { workspace = true } license = { workspace = true }
repository = { workspace = true } repository = { workspace = true }
links = "tauri-plugin-nfc" links = "tauri-plugin-nfc"
exclude = ["/guest-js", "/package.json", "/rollup.config.js", "/tsconfig.json"]
[package.metadata.docs.rs] [package.metadata.docs.rs]
targets = ["x86_64-linux-android"] targets = ["x86_64-linux-android"]
+1
View File
@@ -47,6 +47,7 @@ impl<R: Runtime> Nfc<R> {
pub fn scan(&self, payload: ScanRequest) -> crate::Result<ScanResponse> { pub fn scan(&self, payload: ScanRequest) -> crate::Result<ScanResponse> {
self.0 self.0
.run_mobile_plugin("scan", payload) .run_mobile_plugin("scan", payload)
.map(|v| ScanResponse { tag: v })
.map_err(Into::into) .map_err(Into::into)
} }
+8
View File
@@ -8,6 +8,14 @@ license = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }
repository = { workspace = true } repository = { workspace = true }
links = "tauri-plugin-notification" links = "tauri-plugin-notification"
exclude = [
"/banner.png",
"/guest-js",
"/package.json",
"/rollup.config.js",
"/test",
"/tsconfig.json",
]
[package.metadata.docs.rs] [package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu", "x86_64-linux-android"] targets = ["x86_64-unknown-linux-gnu", "x86_64-linux-android"]
@@ -80,11 +80,11 @@ class NotificationStorage(private val context: Context, private val jsonMapper:
fun writeActionGroup(actions: List<ActionType>) { fun writeActionGroup(actions: List<ActionType>) {
for (type in actions) { for (type in actions) {
val i = type.id
val editor = getStorage(ACTION_TYPES_ID + type.id).edit() val editor = getStorage(ACTION_TYPES_ID + type.id).edit()
editor.clear() editor.clear()
editor.putInt("count", type.actions.size) editor.putInt("count", type.actions.size)
for (action in type.actions) { for (i in 0 until type.actions.size) {
val action = type.actions[i]
editor.putString("id$i", action.id) editor.putString("id$i", action.id)
editor.putString("title$i", action.title) editor.putString("title$i", action.title)
editor.putBoolean("input$i", action.input ?: false) editor.putBoolean("input$i", action.input ?: false)
+2 -1
View File
@@ -73,7 +73,8 @@ interface Options {
/** /**
* The sound resource name or file path for the notification. * The sound resource name or file path for the notification.
* *
* Platform specific behavior: * ## Platform-specific behavior:
*
* - On macOS: use system sounds (e.g., "Ping", "Blow") or sound files in the app bundle * - On macOS: use system sounds (e.g., "Ping", "Blow") or sound files in the app bundle
* - On Linux: use XDG theme sounds (e.g., "message-new-instant") or file paths * - On Linux: use XDG theme sounds (e.g., "message-new-instant") or file paths
* - On Windows: use file paths to sound files (.wav format) * - On Windows: use file paths to sound files (.wav format)
+178
View File
@@ -320,6 +320,95 @@ pub struct ActionType {
hidden_previews_show_subtitle: bool, hidden_previews_show_subtitle: bool,
} }
#[cfg(mobile)]
#[derive(Debug)]
pub struct ActionTypeBuilder(ActionType);
#[cfg(mobile)]
impl ActionType {
pub fn builder(id: impl Into<String>) -> ActionTypeBuilder {
ActionTypeBuilder(Self {
id: id.into(),
actions: Vec::new(),
hidden_previews_body_placeholder: None,
custom_dismiss_action: false,
allow_in_car_play: false,
hidden_previews_show_title: false,
hidden_previews_show_subtitle: false,
})
}
pub fn id(&self) -> &str {
&self.id
}
pub fn actions(&self) -> &[Action] {
&self.actions
}
pub fn hidden_previews_body_placeholder(&self) -> Option<&str> {
self.hidden_previews_body_placeholder.as_deref()
}
pub fn custom_dismiss_action(&self) -> bool {
self.custom_dismiss_action
}
pub fn allow_in_car_play(&self) -> bool {
self.allow_in_car_play
}
pub fn hidden_previews_show_title(&self) -> bool {
self.hidden_previews_show_title
}
pub fn hidden_previews_show_subtitle(&self) -> bool {
self.hidden_previews_show_subtitle
}
}
#[cfg(mobile)]
impl ActionTypeBuilder {
pub fn actions(mut self, actions: Vec<Action>) -> Self {
self.0.actions = actions;
self
}
pub fn hidden_previews_body_placeholder(
mut self,
hidden_previews_body_placeholder: impl Into<String>,
) -> Self {
self.0
.hidden_previews_body_placeholder
.replace(hidden_previews_body_placeholder.into());
self
}
pub fn custom_dismiss_action(mut self, custom_dismiss_action: bool) -> Self {
self.0.custom_dismiss_action = custom_dismiss_action;
self
}
pub fn allow_in_car_play(mut self, allow_in_car_play: bool) -> Self {
self.0.allow_in_car_play = allow_in_car_play;
self
}
pub fn hidden_previews_show_title(mut self, hidden_previews_show_title: bool) -> Self {
self.0.hidden_previews_show_title = hidden_previews_show_title;
self
}
pub fn hidden_previews_show_subtitle(mut self, hidden_previews_show_subtitle: bool) -> Self {
self.0.hidden_previews_show_subtitle = hidden_previews_show_subtitle;
self
}
pub fn build(self) -> ActionType {
self.0
}
}
#[cfg(mobile)] #[cfg(mobile)]
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@@ -334,6 +423,95 @@ pub struct Action {
input_placeholder: Option<String>, input_placeholder: Option<String>,
} }
#[cfg(mobile)]
#[derive(Debug)]
pub struct ActionBuilder(Action);
#[cfg(mobile)]
impl Action {
pub fn builder(id: impl Into<String>, title: impl Into<String>) -> ActionBuilder {
ActionBuilder(Self {
id: id.into(),
title: title.into(),
requires_authentication: false,
foreground: false,
destructive: false,
input: false,
input_button_title: None,
input_placeholder: None,
})
}
pub fn id(&self) -> &str {
&self.id
}
pub fn title(&self) -> &str {
&self.title
}
pub fn requires_authentication(&self) -> bool {
self.requires_authentication
}
pub fn foreground(&self) -> bool {
self.foreground
}
pub fn destructive(&self) -> bool {
self.destructive
}
pub fn input(&self) -> bool {
self.input
}
pub fn input_button_title(&self) -> Option<&str> {
self.input_button_title.as_deref()
}
pub fn input_placeholder(&self) -> Option<&str> {
self.input_placeholder.as_deref()
}
}
#[cfg(mobile)]
impl ActionBuilder {
pub fn requires_authentication(mut self, requires_authentication: bool) -> Self {
self.0.requires_authentication = requires_authentication;
self
}
pub fn foreground(mut self, foreground: bool) -> Self {
self.0.foreground = foreground;
self
}
pub fn destructive(mut self, destructive: bool) -> Self {
self.0.destructive = destructive;
self
}
pub fn input(mut self, input: bool) -> Self {
self.0.input = input;
self
}
pub fn input_button_title(mut self, input_button_title: impl Into<String>) -> Self {
self.0.input_button_title.replace(input_button_title.into());
self
}
pub fn input_placeholder(mut self, input_placeholder: impl Into<String>) -> Self {
self.0.input_placeholder.replace(input_placeholder.into());
self
}
pub fn build(self) -> Action {
self.0
}
}
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
pub use android::*; pub use android::*;
+2
View File
@@ -11,6 +11,8 @@ links = "tauri-plugin-opener"
# Platforms supported by the plugin # Platforms supported by the plugin
# Support levels are "full", "partial", "none", "unknown" # Support levels are "full", "partial", "none", "unknown"
# Details of the support level are left to plugin maintainer # Details of the support level are left to plugin maintainer
exclude = ["/guest-js", "/package.json", "/rollup.config.js", "/tsconfig.json"]
[package.metadata.platforms.support] [package.metadata.platforms.support]
windows = { level = "full", notes = "" } windows = { level = "full", notes = "" }
linux = { level = "full", notes = "" } linux = { level = "full", notes = "" }
+7
View File
@@ -8,6 +8,13 @@ license = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }
repository = { workspace = true } repository = { workspace = true }
links = "tauri-plugin-os" links = "tauri-plugin-os"
exclude = [
"/banner.png",
"/guest-js",
"/package.json",
"/rollup.config.js",
"/tsconfig.json",
]
[package.metadata.platforms.support] [package.metadata.platforms.support]
windows = { level = "full", notes = "" } windows = { level = "full", notes = "" }
+1
View File
@@ -7,6 +7,7 @@ license = { workspace = true }
edition = { workspace = true } edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }
repository = { workspace = true } repository = { workspace = true }
exclude = ["/banner.png"]
[package.metadata.platforms.support] [package.metadata.platforms.support]
windows = { level = "full", notes = "" } windows = { level = "full", notes = "" }
+7
View File
@@ -8,6 +8,13 @@ edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }
repository = { workspace = true } repository = { workspace = true }
links = "tauri-plugin-positioner" links = "tauri-plugin-positioner"
exclude = [
"/banner.png",
"/guest-js",
"/package.json",
"/rollup.config.js",
"/tsconfig.json",
]
[package.metadata.platforms.support] [package.metadata.platforms.support]
windows = { level = "full", notes = "" } windows = { level = "full", notes = "" }
+7
View File
@@ -8,6 +8,13 @@ license = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }
repository = { workspace = true } repository = { workspace = true }
links = "tauri-plugin-process" links = "tauri-plugin-process"
exclude = [
"/banner.png",
"/guest-js",
"/package.json",
"/rollup.config.js",
"/tsconfig.json",
]
[package.metadata.platforms.support] [package.metadata.platforms.support]
windows = { level = "full", notes = "" } windows = { level = "full", notes = "" }
+8
View File
@@ -8,6 +8,14 @@ license = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }
repository = { workspace = true } repository = { workspace = true }
links = "tauri-plugin-shell" links = "tauri-plugin-shell"
exclude = [
"/banner.png",
"/guest-js",
"/package.json",
"/rollup.config.js",
"/test",
"/tsconfig.json",
]
[package.metadata.platforms.support] [package.metadata.platforms.support]
windows = { level = "full", notes = "" } windows = { level = "full", notes = "" }
+1 -1
View File
@@ -7,7 +7,7 @@ license = { workspace = true }
edition = { workspace = true } edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }
repository = { workspace = true } repository = { workspace = true }
exclude = ["/examples"] exclude = ["/banner.png", "/examples"]
[package.metadata.platforms.support] [package.metadata.platforms.support]
windows = { level = "full", notes = "" } windows = { level = "full", notes = "" }
+7
View File
@@ -8,6 +8,13 @@ edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }
repository = { workspace = true } repository = { workspace = true }
links = "tauri-plugin-sql" links = "tauri-plugin-sql"
exclude = [
"/banner.png",
"/guest-js",
"/package.json",
"/rollup.config.js",
"/tsconfig.json",
]
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["sqlite"] features = ["sqlite"]
+8
View File
@@ -8,6 +8,14 @@ edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }
repository = { workspace = true } repository = { workspace = true }
links = "tauri-plugin-store" links = "tauri-plugin-store"
exclude = [
"/banner.png",
"/examples",
"/guest-js",
"/package.json",
"/rollup.config.js",
"/tsconfig.json",
]
[package.metadata.platforms.support] [package.metadata.platforms.support]
windows = { level = "full", notes = "" } windows = { level = "full", notes = "" }
@@ -9,7 +9,7 @@
}, },
"devDependencies": { "devDependencies": {
"@tauri-apps/cli": "2.11.4", "@tauri-apps/cli": "2.11.4",
"typescript": "^6.0.0", "typescript": "^6.0.3",
"vite": "^8.0.1" "vite": "^8.0.16"
} }
} }
+7
View File
@@ -8,6 +8,13 @@ edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }
repository = { workspace = true } repository = { workspace = true }
links = "tauri-plugin-stronghold" links = "tauri-plugin-stronghold"
exclude = [
"/banner.png",
"/guest-js",
"/package.json",
"/rollup.config.js",
"/tsconfig.json",
]
[package.metadata.platforms.support] [package.metadata.platforms.support]
windows = { level = "full", notes = "" } windows = { level = "full", notes = "" }
+9 -1
View File
@@ -8,6 +8,14 @@ license = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }
repository = { workspace = true } repository = { workspace = true }
links = "tauri-plugin-updater" links = "tauri-plugin-updater"
exclude = [
"/banner.png",
"/guest-js",
"/package.json",
"/rollup.config.js",
"/tests",
"/tsconfig.json",
]
[package.metadata.docs.rs] [package.metadata.docs.rs]
no-default-features = true no-default-features = true
@@ -45,7 +53,7 @@ base64 = "0.22"
semver = { version = "1", features = ["serde"] } semver = { version = "1", features = ["serde"] }
futures-util = "0.3" futures-util = "0.3"
tempfile = "3.20" tempfile = "3.20"
infer = "0.22" infer = "0.19"
percent-encoding = "2.3" percent-encoding = "2.3"
[target."cfg(target_os = \"windows\")".dependencies] [target."cfg(target_os = \"windows\")".dependencies]
+1
View File
@@ -69,6 +69,7 @@ import { relaunch } from '@tauri-apps/plugin-process'
const update = await check() const update = await check()
if (update) { if (update) {
await update.downloadAndInstall() await update.downloadAndInstall()
// Relaunch the app on macOS and Linux to run the newly install version
await relaunch() await relaunch()
} }
``` ```
+1 -1
View File
@@ -1 +1 @@
if("__TAURI__"in window){var __TAURI_PLUGIN_UPDATER__=function(t){"use strict";function e(t,e,s,n){if("function"==typeof e?t!==e||!n:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===s?n:"a"===s?n.call(t):n?n.value:e.get(t)}function s(t,e,s,n,i){if("function"==typeof e||!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");return e.set(t,s),s}var n,i,a,r,o;"function"==typeof SuppressedError&&SuppressedError;const d="__TAURI_TO_IPC_KEY__";class c{constructor(t){n.set(this,void 0),i.set(this,0),a.set(this,[]),r.set(this,void 0),s(this,n,t||(()=>{})),this.id=function(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}(t=>{const o=t.index;if("end"in t)return void(o==e(this,i,"f")?this.cleanupCallback():s(this,r,o));const d=t.message;if(o==e(this,i,"f")){for(e(this,n,"f").call(this,d),s(this,i,e(this,i,"f")+1);e(this,i,"f")in e(this,a,"f");){const t=e(this,a,"f")[e(this,i,"f")];e(this,n,"f").call(this,t),delete e(this,a,"f")[e(this,i,"f")],s(this,i,e(this,i,"f")+1)}e(this,i,"f")===e(this,r,"f")&&this.cleanupCallback()}else e(this,a,"f")[o]=d})}cleanupCallback(){window.__TAURI_INTERNALS__.unregisterCallback(this.id)}set onmessage(t){s(this,n,t)}get onmessage(){return e(this,n,"f")}[(n=new WeakMap,i=new WeakMap,a=new WeakMap,r=new WeakMap,d)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[d]()}}async function l(t,e={},s){return window.__TAURI_INTERNALS__.invoke(t,e,s)}class h{get rid(){return e(this,o,"f")}constructor(t){o.set(this,void 0),s(this,o,t)}async close(){return l("plugin:resources|close",{rid:this.rid})}}o=new WeakMap;class u extends h{constructor(t){super(t.rid),this.available=!0,this.currentVersion=t.currentVersion,this.version=t.version,this.date=t.date,this.body=t.body,this.rawJson=t.rawJson}async download(t,e){_(e);const s=new c;t&&(s.onmessage=t);const n=await l("plugin:updater|download",{onEvent:s,rid:this.rid,...e});this.downloadedBytes=new h(n)}async install(){if(!this.downloadedBytes)throw new Error("Update.install called before Update.download");await l("plugin:updater|install",{updateRid:this.rid,bytesRid:this.downloadedBytes.rid}),this.downloadedBytes=void 0}async downloadAndInstall(t,e){_(e);const s=new c;t&&(s.onmessage=t),await l("plugin:updater|download_and_install",{onEvent:s,rid:this.rid,...e})}async close(){await(this.downloadedBytes?.close()),await super.close()}}function _(t){t?.headers&&(t.headers=Array.from(new Headers(t.headers).entries()))}return t.Update=u,t.check=async function(t){_(t);const e=await l("plugin:updater|check",{...t});return e?new u(e):null},t}({});Object.defineProperty(window.__TAURI__,"updater",{value:__TAURI_PLUGIN_UPDATER__})} if("__TAURI__"in window){var __TAURI_PLUGIN_UPDATER__=function(t){"use strict";function e(t,e,s,n){if("function"==typeof e?t!==e||!n:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===s?n:"a"===s?n.call(t):n?n.value:e.get(t)}function s(t,e,s,n,i){if("function"==typeof e||!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");return e.set(t,s),s}var n,i,a,r,o;"function"==typeof SuppressedError&&SuppressedError;const d="__TAURI_TO_IPC_KEY__";class c{constructor(t){n.set(this,void 0),i.set(this,0),a.set(this,[]),r.set(this,void 0),s(this,n,t||(()=>{})),this.id=function(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}(t=>{const o=t.index;if("end"in t)return void(o==e(this,i,"f")?this.cleanupCallback():s(this,r,o));const d=t.message;if(o==e(this,i,"f")){for(e(this,n,"f").call(this,d),s(this,i,e(this,i,"f")+1);e(this,i,"f")in e(this,a,"f");){const t=e(this,a,"f")[e(this,i,"f")];e(this,n,"f").call(this,t),delete e(this,a,"f")[e(this,i,"f")],s(this,i,e(this,i,"f")+1)}e(this,i,"f")===e(this,r,"f")&&this.cleanupCallback()}else e(this,a,"f")[o]=d})}cleanupCallback(){window.__TAURI_INTERNALS__.unregisterCallback(this.id)}set onmessage(t){s(this,n,t)}get onmessage(){return e(this,n,"f")}[(n=new WeakMap,i=new WeakMap,a=new WeakMap,r=new WeakMap,d)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[d]()}}async function l(t,e={},s){return window.__TAURI_INTERNALS__.invoke(t,e,s)}class h{get rid(){return e(this,o,"f")}constructor(t){o.set(this,void 0),s(this,o,t)}async close(){return l("plugin:resources|close",{rid:this.rid})}}o=new WeakMap;class u extends h{constructor(t){super(t.rid),this.available=!0,this.currentVersion=t.currentVersion,this.version=t.version,this.date=t.date,this.body=t.body,this.rawJson=t.rawJson}async download(t,e){_(e);const s=new c;t&&(s.onmessage=t);const n=await l("plugin:updater|download",{onEvent:s,rid:this.rid,...e});this.downloadedBytes=new h(n)}async install(t){if(!this.downloadedBytes)throw new Error("Update.install called before Update.download");await l("plugin:updater|install",{updateRid:this.rid,bytesRid:this.downloadedBytes.rid,...t}),this.downloadedBytes=void 0}async downloadAndInstall(t,e){_(e);const s=new c;t&&(s.onmessage=t),await l("plugin:updater|download_and_install",{onEvent:s,rid:this.rid,...e})}async close(){await(this.downloadedBytes?.close()),await super.close()}}function _(t){t?.headers&&(t.headers=Array.from(new Headers(t.headers).entries()))}return t.Update=u,t.check=async function(t){_(t);const e=await l("plugin:updater|check",{...t});return e?new u(e):null},t}({});Object.defineProperty(window.__TAURI__,"updater",{value:__TAURI_PLUGIN_UPDATER__})}
+29 -6
View File
@@ -40,6 +40,14 @@ interface DownloadOptions {
timeout?: number timeout?: number
} }
/** Options used when installing an update */
interface InstallOptions {
/**
* If the Windows installer should restart the app after installed, default is `true`
*/
restartAfterInstall?: boolean
}
interface UpdateMetadata { interface UpdateMetadata {
rid: number rid: number
currentVersion: string currentVersion: string
@@ -76,7 +84,7 @@ class Update extends Resource {
this.rawJson = metadata.rawJson this.rawJson = metadata.rawJson
} }
/** Download the updater package */ /** Download the updater package. Call {@linkcode install} later to install it */
async download( async download(
onEvent?: (progress: DownloadEvent) => void, onEvent?: (progress: DownloadEvent) => void,
options?: DownloadOptions options?: DownloadOptions
@@ -94,25 +102,40 @@ class Update extends Resource {
this.downloadedBytes = new Resource(downloadedBytesRid) this.downloadedBytes = new Resource(downloadedBytesRid)
} }
/** Install downloaded updater package */ /**
async install(): Promise<void> { * Install downloaded updater package. Must be called after {@linkcode download}.
*
* ## Platform-specific:
*
* - **Windows:** This function exits the app after launching the updater installer successfully
* - **macOS / Linux:** You need to relaunch the app to run the newly install version
*/
async install(options?: InstallOptions): Promise<void> {
if (!this.downloadedBytes) { if (!this.downloadedBytes) {
throw new Error('Update.install called before Update.download') throw new Error('Update.install called before Update.download')
} }
await invoke('plugin:updater|install', { await invoke('plugin:updater|install', {
updateRid: this.rid, updateRid: this.rid,
bytesRid: this.downloadedBytes.rid bytesRid: this.downloadedBytes.rid,
...options
}) })
// Don't need to call close, we did it in rust side already // Don't need to call close, we did it in rust side already
this.downloadedBytes = undefined this.downloadedBytes = undefined
} }
/** Downloads the updater package and installs it */ /**
* Downloads the updater package and installs it
*
* ## Platform-specific:
*
* - **Windows:** This function exits the app after launching the updater installer successfully
* - **macOS / Linux:** You need to relaunch the app to run the newly install version
*/
async downloadAndInstall( async downloadAndInstall(
onEvent?: (progress: DownloadEvent) => void, onEvent?: (progress: DownloadEvent) => void,
options?: DownloadOptions options?: DownloadOptions & InstallOptions
): Promise<void> { ): Promise<void> {
convertToRustHeaders(options) convertToRustHeaders(options)
const channel = new Channel<DownloadEvent>() const channel = new Channel<DownloadEvent>()
+16 -1
View File
@@ -142,12 +142,22 @@ pub(crate) async fn install<R: Runtime>(
webview: Webview<R>, webview: Webview<R>,
update_rid: ResourceId, update_rid: ResourceId,
bytes_rid: ResourceId, bytes_rid: ResourceId,
restart_after_install: Option<bool>,
) -> Result<()> { ) -> Result<()> {
let update = webview.resources_table().get::<Update>(update_rid)?; let update = webview.resources_table().get::<Update>(update_rid)?;
let bytes = webview let bytes = webview
.resources_table() .resources_table()
.get::<DownloadedBytes>(bytes_rid)?; .get::<DownloadedBytes>(bytes_rid)?;
update.install(&bytes.0)?;
if let Some(restart_after_install) = restart_after_install {
let update = (*update).clone();
update
.restart_after_install(restart_after_install)
.install(&bytes.0)?;
} else {
update.install(&bytes.0)?;
}
let _ = webview.resources_table().close(bytes_rid); let _ = webview.resources_table().close(bytes_rid);
Ok(()) Ok(())
} }
@@ -159,6 +169,7 @@ pub(crate) async fn download_and_install<R: Runtime>(
on_event: Channel<DownloadEvent>, on_event: Channel<DownloadEvent>,
headers: Option<Vec<(String, String)>>, headers: Option<Vec<(String, String)>>,
timeout: Option<u64>, timeout: Option<u64>,
restart_after_install: Option<bool>,
) -> Result<()> { ) -> Result<()> {
let update = webview.resources_table().get::<Update>(rid)?; let update = webview.resources_table().get::<Update>(rid)?;
@@ -176,6 +187,10 @@ pub(crate) async fn download_and_install<R: Runtime>(
update.timeout = Some(Duration::from_millis(timeout)); update.timeout = Some(Duration::from_millis(timeout));
} }
if let Some(restart_after_install) = restart_after_install {
update = update.restart_after_install(restart_after_install);
}
let mut first_chunk = true; let mut first_chunk = true;
update update
+17 -2
View File
@@ -32,17 +32,30 @@ impl WindowsUpdateInstallMode {
} }
} }
#[cfg(windows)]
pub(crate) fn msi_restart_after_install_args(&self) -> &'static [&'static str] {
&["AUTOLAUNCHAPP=True"]
}
/// Returns the associated nsis arguments. /// Returns the associated nsis arguments.
pub fn nsis_args(&self) -> &'static [&'static str] { pub fn nsis_args(&self) -> &'static [&'static str] {
// `/P`: Passive // `/P`: Passive
// `/S`: Silent // `/S`: Silent
// `/R`: Restart // `/R`: Restart
match self { match self {
Self::Passive => &["/P", "/R"], Self::Passive => &["/P"],
Self::Quiet => &["/S", "/R"], Self::Quiet => &["/S"],
_ => &[], _ => &[],
} }
} }
#[cfg(windows)]
pub(crate) fn nsis_restart_after_install_args(&self) -> &'static [&'static str] {
match self {
Self::BasicUi => &[],
_ => &["/R"],
}
}
} }
impl Display for WindowsUpdateInstallMode { impl Display for WindowsUpdateInstallMode {
@@ -63,6 +76,8 @@ impl Display for WindowsUpdateInstallMode {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct WindowsConfig { pub struct WindowsConfig {
/// Additional arguments given to the NSIS or WiX installer. /// Additional arguments given to the NSIS or WiX installer.
///
/// Note: this applies to both WiX and NSIS installers
#[serde( #[serde(
default, default,
alias = "installer-args", alias = "installer-args",
+3 -3
View File
@@ -83,9 +83,9 @@ impl<R: Runtime, T: Manager<R>> UpdaterExt<R> for T {
builder = builder.target(target); builder = builder.target(target);
} }
let args = self.env().args_os; #[cfg(windows)]
if !args.is_empty() { {
builder = builder.current_exe_args(args); builder = builder.current_exe_args(self.env().args_os);
} }
builder.version_comparator = version_comparator.clone(); builder.version_comparator = version_comparator.clone();
+226 -149
View File
@@ -129,16 +129,34 @@ impl RemoteRelease {
pub type OnBeforeExit = Arc<dyn Fn() + Send + Sync + 'static>; pub type OnBeforeExit = Arc<dyn Fn() + Send + Sync + 'static>;
pub type OnBeforeRequest = Arc<dyn Fn(ClientBuilder) -> ClientBuilder + Send + Sync + 'static>; pub type OnBeforeRequest = Arc<dyn Fn(ClientBuilder) -> ClientBuilder + Send + Sync + 'static>;
pub type VersionComparator = Arc<dyn Fn(Version, RemoteRelease) -> bool + Send + Sync>; pub type VersionComparator = Arc<dyn Fn(Version, RemoteRelease) -> bool + Send + Sync>;
#[cfg(target_os = "macos")]
type MainThreadClosure = Box<dyn FnOnce() + Send + Sync + 'static>; type MainThreadClosure = Box<dyn FnOnce() + Send + Sync + 'static>;
type RunOnMainThread = #[cfg(target_os = "macos")]
Box<dyn Fn(MainThreadClosure) -> std::result::Result<(), tauri::Error> + Send + Sync + 'static>; type RunOnMainThread = Arc<dyn Fn(MainThreadClosure) -> tauri::Result<()> + Send + Sync + 'static>;
// TODO: Move more fields to this in v3 if we can mark those fields non `pub`
/// Updater context shared between [`UpdaterBuilder`], [`Updater`] and [`Update`]
#[derive(Clone)]
struct UpdaterContext {
config: Config,
configure_client: Option<OnBeforeRequest>,
#[cfg(target_os = "macos")]
run_on_main_thread: RunOnMainThread,
/// App name, used for creating named tempfiles
#[cfg(windows)]
app_name: String,
#[cfg(windows)]
installer_args: Vec<OsString>,
#[cfg(windows)]
current_exe_args: Vec<OsString>,
#[cfg(windows)]
on_before_exit: Option<OnBeforeExit>,
#[cfg(windows)]
restart_after_install: bool,
}
pub struct UpdaterBuilder { pub struct UpdaterBuilder {
#[allow(dead_code)]
run_on_main_thread: RunOnMainThread,
app_name: String,
current_version: Version, current_version: Version,
config: Config,
pub(crate) version_comparator: Option<VersionComparator>, pub(crate) version_comparator: Option<VersionComparator>,
executable_path: Option<PathBuf>, executable_path: Option<PathBuf>,
target: Option<String>, target: Option<String>,
@@ -147,27 +165,38 @@ pub struct UpdaterBuilder {
timeout: Option<Duration>, timeout: Option<Duration>,
proxy: Option<Url>, proxy: Option<Url>,
no_proxy: bool, no_proxy: bool,
installer_args: Vec<OsString>, context: UpdaterContext,
current_exe_args: Vec<OsString>,
on_before_exit: Option<OnBeforeExit>,
configure_client: Option<OnBeforeRequest>,
} }
impl UpdaterBuilder { impl UpdaterBuilder {
pub(crate) fn new<R: Runtime>(app: &AppHandle<R>, config: crate::Config) -> Self { pub(crate) fn new<R: Runtime>(app: &AppHandle<R>, config: crate::Config) -> Self {
let app_ = app.clone(); #[cfg(target_os = "macos")]
let run_on_main_thread = move |f| app_.run_on_main_thread(f); let run_on_main_thread = {
let app_ = app.clone();
Arc::new(move |f| app_.run_on_main_thread(f))
};
Self { Self {
run_on_main_thread: Box::new(run_on_main_thread), context: UpdaterContext {
installer_args: config #[cfg(windows)]
.windows installer_args: config
.as_ref() .windows
.map(|w| w.installer_args.clone()) .as_ref()
.unwrap_or_default(), .map(|w| w.installer_args.clone())
current_exe_args: Vec::new(), .unwrap_or_default(),
app_name: app.package_info().name.clone(), config,
configure_client: None,
#[cfg(target_os = "macos")]
run_on_main_thread,
#[cfg(windows)]
app_name: app.package_info().name.clone(),
#[cfg(windows)]
current_exe_args: Vec::new(),
#[cfg(windows)]
on_before_exit: None,
#[cfg(windows)]
restart_after_install: true,
},
current_version: app.package_info().version.clone(), current_version: app.package_info().version.clone(),
config,
version_comparator: None, version_comparator: None,
executable_path: None, executable_path: None,
target: None, target: None,
@@ -176,8 +205,6 @@ impl UpdaterBuilder {
timeout: None, timeout: None,
proxy: None, proxy: None,
no_proxy: false, no_proxy: false,
on_before_exit: None,
configure_client: None,
} }
} }
@@ -197,7 +224,7 @@ impl UpdaterBuilder {
pub fn endpoints(mut self, endpoints: Vec<Url>) -> Result<Self> { pub fn endpoints(mut self, endpoints: Vec<Url>) -> Result<Self> {
crate::config::validate_endpoints( crate::config::validate_endpoints(
&endpoints, &endpoints,
self.config.dangerous_insecure_transport_protocol, self.context.config.dangerous_insecure_transport_protocol,
)?; )?;
self.endpoints.replace(endpoints); self.endpoints.replace(endpoints);
@@ -251,26 +278,40 @@ impl UpdaterBuilder {
} }
pub fn pubkey<S: Into<String>>(mut self, pubkey: S) -> Self { pub fn pubkey<S: Into<String>>(mut self, pubkey: S) -> Self {
self.config.pubkey = pubkey.into(); self.context.config.pubkey = pubkey.into();
self self
} }
/// Adds an argument to pass to the Windows installer. /// Adds an argument to pass to the Windows installer.
///
/// Note: this applies to both WiX and NSIS installers
#[cfg_attr(not(windows), allow(unused))]
pub fn installer_arg<S>(mut self, arg: S) -> Self pub fn installer_arg<S>(mut self, arg: S) -> Self
where where
S: Into<OsString>, S: Into<OsString>,
{ {
self.installer_args.push(arg.into()); #[cfg(windows)]
{
self.context.installer_args.push(arg.into());
}
self self
} }
/// Adds multiple arguments to pass to the Windows installer. /// Adds multiple arguments to pass to the Windows installer.
///
/// Note: this applies to both WiX and NSIS installers
#[cfg_attr(not(windows), allow(unused))]
pub fn installer_args<I, S>(mut self, args: I) -> Self pub fn installer_args<I, S>(mut self, args: I) -> Self
where where
I: IntoIterator<Item = S>, I: IntoIterator<Item = S>,
S: Into<OsString>, S: Into<OsString>,
{ {
self.installer_args.extend(args.into_iter().map(Into::into)); #[cfg(windows)]
{
self.context
.installer_args
.extend(args.into_iter().map(Into::into));
}
self self
} }
@@ -280,14 +321,32 @@ impl UpdaterBuilder {
/// [`Self::installer_arg`], [`crate::Builder::installer_arg`] /// [`Self::installer_arg`], [`crate::Builder::installer_arg`]
/// and the `plugins > updater > windows > installerArgs` config, /// and the `plugins > updater > windows > installerArgs` config,
/// not the ones managed by us (e.g. `/UPDATER` flag passed to the NSIS installer) /// not the ones managed by us (e.g. `/UPDATER` flag passed to the NSIS installer)
#[cfg_attr(not(windows), allow(unused))]
pub fn clear_installer_args(mut self) -> Self { pub fn clear_installer_args(mut self) -> Self {
self.installer_args.clear(); #[cfg(windows)]
{
self.context.installer_args.clear();
}
self self
} }
/// Function to run before we run the installer and exit the app through `std::process::exit(0)` on Windows /// Function to run before we run the installer and exit the app through `std::process::exit(0)` on Windows
#[cfg_attr(not(windows), allow(unused))]
pub fn on_before_exit<F: Fn() + Send + Sync + 'static>(mut self, f: F) -> Self { pub fn on_before_exit<F: Fn() + Send + Sync + 'static>(mut self, f: F) -> Self {
self.on_before_exit.replace(Arc::new(f)); #[cfg(windows)]
{
self.context.on_before_exit.replace(Arc::new(f));
}
self
}
/// If the Windows installer should restart the app after installed, default is `true`
#[cfg_attr(not(windows), allow(unused))]
pub fn restart_after_install(mut self, restart_after_install: bool) -> Self {
#[cfg(windows)]
{
self.context.restart_after_install = restart_after_install;
}
self self
} }
@@ -299,14 +358,14 @@ impl UpdaterBuilder {
mut self, mut self,
f: F, f: F,
) -> Self { ) -> Self {
self.configure_client.replace(Arc::new(f)); self.context.configure_client.replace(Arc::new(f));
self self
} }
pub fn build(self) -> Result<Updater> { pub fn build(self) -> Result<Updater> {
let endpoints = self let endpoints = self
.endpoints .endpoints
.unwrap_or_else(|| self.config.endpoints.clone()); .unwrap_or_else(|| self.context.config.endpoints.clone());
if endpoints.is_empty() { if endpoints.is_empty() {
return Err(Error::EmptyEndpoints); return Err(Error::EmptyEndpoints);
@@ -324,44 +383,36 @@ impl UpdaterBuilder {
}; };
Ok(Updater { Ok(Updater {
run_on_main_thread: Arc::new(self.run_on_main_thread),
config: self.config,
app_name: self.app_name,
current_version: self.current_version, current_version: self.current_version,
version_comparator: self.version_comparator, version_comparator: self.version_comparator,
timeout: self.timeout, timeout: self.timeout,
proxy: self.proxy, proxy: self.proxy,
no_proxy: self.no_proxy, no_proxy: self.no_proxy,
endpoints, endpoints,
installer_args: self.installer_args,
current_exe_args: self.current_exe_args,
arch, arch,
target: self.target, target: self.target,
headers: self.headers, headers: self.headers,
extract_path, extract_path,
on_before_exit: self.on_before_exit, context: self.context.clone(),
configure_client: self.configure_client,
}) })
} }
} }
#[cfg(windows)]
impl UpdaterBuilder { impl UpdaterBuilder {
pub(crate) fn current_exe_args<I, S>(mut self, args: I) -> Self pub(crate) fn current_exe_args<I, S>(mut self, args: I) -> Self
where where
I: IntoIterator<Item = S>, I: IntoIterator<Item = S>,
S: Into<OsString>, S: Into<OsString>,
{ {
self.current_exe_args self.context
.current_exe_args
.extend(args.into_iter().map(Into::into)); .extend(args.into_iter().map(Into::into));
self self
} }
} }
pub struct Updater { pub struct Updater {
#[allow(dead_code)]
run_on_main_thread: Arc<RunOnMainThread>,
config: Config,
app_name: String,
current_version: Version, current_version: Version,
version_comparator: Option<VersionComparator>, version_comparator: Option<VersionComparator>,
timeout: Option<Duration>, timeout: Option<Duration>,
@@ -374,12 +425,7 @@ pub struct Updater {
target: Option<String>, target: Option<String>,
headers: HeaderMap, headers: HeaderMap,
extract_path: PathBuf, extract_path: PathBuf,
on_before_exit: Option<OnBeforeExit>, context: UpdaterContext,
configure_client: Option<OnBeforeRequest>,
#[allow(unused)]
installer_args: Vec<OsString>,
#[allow(unused)]
current_exe_args: Vec<OsString>,
} }
impl Updater { impl Updater {
@@ -449,10 +495,10 @@ impl Updater {
} }
let mut request = ClientBuilder::new().user_agent(UPDATER_USER_AGENT); let mut request = ClientBuilder::new().user_agent(UPDATER_USER_AGENT);
if self.config.dangerous_accept_invalid_certs { if self.context.config.dangerous_accept_invalid_certs {
request = request.danger_accept_invalid_certs(true); request = request.danger_accept_invalid_certs(true);
} }
if self.config.dangerous_accept_invalid_hostnames { if self.context.config.dangerous_accept_invalid_hostnames {
request = request.danger_accept_invalid_hostnames(true); request = request.danger_accept_invalid_hostnames(true);
} }
if let Some(timeout) = self.timeout { if let Some(timeout) = self.timeout {
@@ -467,7 +513,7 @@ impl Updater {
request = request.proxy(proxy); request = request.proxy(proxy);
} }
if let Some(ref configure_client) = self.configure_client { if let Some(ref configure_client) = self.context.configure_client {
request = configure_client(request); request = configure_client(request);
} }
@@ -537,10 +583,6 @@ impl Updater {
let update = if should_update { let update = if should_update {
Some(Update { Some(Update {
run_on_main_thread: self.run_on_main_thread.clone(),
config: self.config.clone(),
on_before_exit: self.on_before_exit.clone(),
app_name: self.app_name.clone(),
current_version: self.current_version.to_string(), current_version: self.current_version.to_string(),
target: target.to_owned(), target: target.to_owned(),
extract_path: self.extract_path.clone(), extract_path: self.extract_path.clone(),
@@ -554,9 +596,7 @@ impl Updater {
proxy: self.proxy.clone(), proxy: self.proxy.clone(),
no_proxy: self.no_proxy, no_proxy: self.no_proxy,
headers: self.headers.clone(), headers: self.headers.clone(),
installer_args: self.installer_args.clone(), context: self.context.clone(),
current_exe_args: self.current_exe_args.clone(),
configure_client: self.configure_client.clone(),
}) })
} else { } else {
None None
@@ -600,11 +640,6 @@ impl Updater {
#[derive(Clone)] #[derive(Clone)]
pub struct Update { pub struct Update {
#[allow(dead_code)]
run_on_main_thread: Arc<RunOnMainThread>,
config: Config,
#[allow(unused)]
on_before_exit: Option<OnBeforeExit>,
/// Update description /// Update description
pub body: Option<String>, pub body: Option<String>,
/// Version used to check for update /// Version used to check for update
@@ -633,14 +668,7 @@ pub struct Update {
/// Extract path /// Extract path
#[allow(unused)] #[allow(unused)]
extract_path: PathBuf, extract_path: PathBuf,
/// App name, used for creating named tempfiles on Windows context: UpdaterContext,
#[allow(unused)]
app_name: String,
#[allow(unused)]
installer_args: Vec<OsString>,
#[allow(unused)]
current_exe_args: Vec<OsString>,
configure_client: Option<OnBeforeRequest>,
} }
impl Resource for Update {} impl Resource for Update {}
@@ -661,10 +689,10 @@ impl Update {
} }
let mut request = ClientBuilder::new().user_agent(UPDATER_USER_AGENT); let mut request = ClientBuilder::new().user_agent(UPDATER_USER_AGENT);
if self.config.dangerous_accept_invalid_certs { if self.context.config.dangerous_accept_invalid_certs {
request = request.danger_accept_invalid_certs(true); request = request.danger_accept_invalid_certs(true);
} }
if self.config.dangerous_accept_invalid_hostnames { if self.context.config.dangerous_accept_invalid_hostnames {
request = request.danger_accept_invalid_hostnames(true); request = request.danger_accept_invalid_hostnames(true);
} }
if let Some(timeout) = self.timeout { if let Some(timeout) = self.timeout {
@@ -676,7 +704,7 @@ impl Update {
let proxy = reqwest::Proxy::all(proxy.as_str())?; let proxy = reqwest::Proxy::all(proxy.as_str())?;
request = request.proxy(proxy); request = request.proxy(proxy);
} }
if let Some(ref configure_client) = self.configure_client { if let Some(ref configure_client) = self.context.configure_client {
request = configure_client(request); request = configure_client(request);
} }
let response = request let response = request
@@ -709,17 +737,27 @@ impl Update {
} }
on_download_finish(); on_download_finish();
verify_signature(&buffer, &self.signature, &self.config.pubkey)?; verify_signature(&buffer, &self.signature, &self.context.config.pubkey)?;
Ok(buffer) Ok(buffer)
} }
/// Installs the updater package downloaded by [`Update::download`] /// Installs the updater package downloaded by [`Update::download`]
///
/// ## Platform-specific:
///
/// - **Windows:** This function exits the app after launching the updater installer successfully
/// - **macOS / Linux:** You need to relaunch the app to run the newly install version
pub fn install(&self, bytes: impl AsRef<[u8]>) -> Result<()> { pub fn install(&self, bytes: impl AsRef<[u8]>) -> Result<()> {
self.install_inner(bytes.as_ref()) self.install_inner(bytes.as_ref())
} }
/// Downloads and installs the updater package /// Downloads and installs the updater package
///
/// ## Platform-specific:
///
/// - **Windows:** This function exits the app after launching the updater installer successfully
/// - **macOS / Linux:** You need to relaunch the app to run the newly install version
pub async fn download_and_install<C: FnMut(usize, Option<u64>), D: FnOnce()>( pub async fn download_and_install<C: FnMut(usize, Option<u64>), D: FnOnce()>(
&self, &self,
on_chunk: C, on_chunk: C,
@@ -733,6 +771,16 @@ impl Update {
fn install_inner(&self, _bytes: &[u8]) -> Result<()> { fn install_inner(&self, _bytes: &[u8]) -> Result<()> {
Ok(()) Ok(())
} }
/// Whether the Windows installer should restart the app after installed, default is `true`
#[cfg_attr(not(windows), allow(unused))]
pub fn restart_after_install(mut self, restart_after_install: bool) -> Self {
#[cfg(windows)]
{
self.context.restart_after_install = restart_after_install;
}
self
}
} }
#[cfg(windows)] #[cfg(windows)]
@@ -785,7 +833,6 @@ impl Update {
/// │ └──[AppName]_[version]_x64-setup.exe # NSIS installer /// │ └──[AppName]_[version]_x64-setup.exe # NSIS installer
/// └── ... /// └── ...
fn install_inner(&self, bytes: &[u8]) -> Result<()> { fn install_inner(&self, bytes: &[u8]) -> Result<()> {
use std::iter::once;
use windows_sys::{ use windows_sys::{
w, w,
Win32::UI::{Shell::ShellExecuteW, WindowsAndMessaging::SW_SHOW}, Win32::UI::{Shell::ShellExecuteW, WindowsAndMessaging::SW_SHOW},
@@ -793,48 +840,7 @@ impl Update {
let updater_type = self.extract(bytes)?; let updater_type = self.extract(bytes)?;
let install_mode = self.config.install_mode(); if let Some(on_before_exit) = self.context.on_before_exit.as_ref() {
let current_args = &self.current_exe_args()[1..];
let msi_args;
let nsis_args;
let installer_args: Vec<&OsStr> = match &updater_type {
WindowsUpdaterType::Nsis { .. } => {
nsis_args = current_args
.iter()
.map(escape_nsis_current_exe_arg)
.collect::<Vec<_>>();
install_mode
.nsis_args()
.iter()
.map(OsStr::new)
.chain(once(OsStr::new("/UPDATE")))
.chain(once(OsStr::new("/ARGS")))
.chain(nsis_args.iter().map(OsStr::new))
.chain(self.installer_args())
.collect()
}
WindowsUpdaterType::Msi { path, .. } => {
let escaped_args = current_args
.iter()
.map(escape_msi_property_arg)
.collect::<Vec<_>>()
.join(" ");
msi_args = OsString::from(format!("LAUNCHAPPARGS=\"{escaped_args}\""));
[OsStr::new("/i"), path.as_os_str()]
.into_iter()
.chain(install_mode.msiexec_args().iter().map(OsStr::new))
.chain(once(OsStr::new("/promptrestart")))
.chain(self.installer_args())
.chain(once(OsStr::new("AUTOLAUNCHAPP=True")))
.chain(once(msi_args.as_os_str()))
.collect()
}
};
if let Some(on_before_exit) = self.on_before_exit.as_ref() {
log::debug!("running on_before_exit hook"); log::debug!("running on_before_exit hook");
on_before_exit(); on_before_exit();
} }
@@ -846,12 +852,14 @@ impl Update {
|p| OsString::from(format!("{p}\\System32\\msiexec.exe")), |p| OsString::from(format!("{p}\\System32\\msiexec.exe")),
), ),
}; };
let file = encode_wide(file); let parameters = self.updater_parameters(&updater_type);
let parameters = installer_args.join(OsStr::new(" ")); log::debug!("Executing updater {file:?} with parameters: {parameters:?}");
let file = encode_wide(file);
let parameters = encode_wide(parameters); let parameters = encode_wide(parameters);
unsafe { let result = unsafe {
ShellExecuteW( ShellExecuteW(
std::ptr::null_mut(), std::ptr::null_mut(),
w!("open"), w!("open"),
@@ -861,22 +869,79 @@ impl Update {
SW_SHOW, SW_SHOW,
) )
}; };
if result as isize <= 32 {
return Err(crate::Error::Io(std::io::Error::last_os_error()));
}
std::process::exit(0); std::process::exit(0);
} }
fn installer_args(&self) -> Vec<&OsStr> { fn updater_parameters(&self, updater_type: &WindowsUpdaterType) -> OsString {
self.installer_args let install_mode = self.context.config.install_mode();
.iter() let current_args = &self.context.current_exe_args[1..];
.map(OsStr::new)
.collect::<Vec<_>>() match updater_type {
WindowsUpdaterType::Nsis { .. } => {
let mut installer_args: Vec<&OsStr> = Vec::new();
installer_args.extend(install_mode.nsis_args().iter().map(OsStr::new));
installer_args.push(OsStr::new("/UPDATE"));
let nsis_current_exe_arg;
if self.context.restart_after_install {
nsis_current_exe_arg = current_args
.iter()
.map(escape_nsis_current_exe_arg)
.collect::<Vec<_>>();
installer_args.extend(
install_mode
.nsis_restart_after_install_args()
.iter()
.map(OsStr::new),
);
installer_args.push(OsStr::new("/ARGS"));
installer_args.extend(nsis_current_exe_arg.iter().map(OsStr::new));
}
installer_args.extend(self.installer_args());
installer_args.join(OsStr::new(" "))
}
WindowsUpdaterType::Msi { path, .. } => {
let mut installer_args: Vec<&OsStr> = vec![OsStr::new("/i"), path.as_os_str()];
installer_args.extend(install_mode.msiexec_args().iter().map(OsStr::new));
installer_args.push(OsStr::new("/promptrestart"));
installer_args.extend(self.installer_args());
let msi_current_exe_arg;
if self.context.restart_after_install {
msi_current_exe_arg = format!(
"LAUNCHAPPARGS=\"{}\"",
current_args
.iter()
.map(escape_msi_property_arg)
.collect::<Vec<_>>()
.join(" ")
);
installer_args.extend(
install_mode
.msi_restart_after_install_args()
.iter()
.map(OsStr::new),
);
installer_args.push(OsStr::new(&msi_current_exe_arg));
}
installer_args.join(OsStr::new(" "))
}
}
} }
fn current_exe_args(&self) -> Vec<&OsStr> { fn installer_args(
self.current_exe_args &self,
.iter() ) -> std::iter::Map<std::slice::Iter<'_, OsString>, fn(&OsString) -> &OsStr> {
.map(OsStr::new) self.context.installer_args.iter().map(OsStr::new)
.collect::<Vec<_>>()
} }
fn extract(&self, bytes: &[u8]) -> Result<WindowsUpdaterType> { fn extract(&self, bytes: &[u8]) -> Result<WindowsUpdaterType> {
@@ -890,7 +955,10 @@ impl Update {
fn make_temp_dir(&self) -> Result<PathBuf> { fn make_temp_dir(&self) -> Result<PathBuf> {
Ok(tempfile::Builder::new() Ok(tempfile::Builder::new()
.prefix(&format!("{}-{}-updater-", self.app_name, self.version)) .prefix(&format!(
"{}-{}-updater-",
self.context.app_name, self.version
))
.tempdir()? .tempdir()?
.keep()) .keep())
} }
@@ -938,7 +1006,10 @@ impl Update {
let temp_dir = self.make_temp_dir()?; let temp_dir = self.make_temp_dir()?;
let mut temp_file = tempfile::Builder::new() let mut temp_file = tempfile::Builder::new()
.prefix(&format!("{}-{}-installer", self.app_name, self.version)) .prefix(&format!(
"{}-{}-installer",
self.context.app_name, self.version
))
.suffix(ext) .suffix(ext)
.rand_bytes(0) .rand_bytes(0)
.tempfile_in(temp_dir)?; .tempfile_in(temp_dir)?;
@@ -1277,7 +1348,7 @@ impl Update {
); );
let (tx, rx) = std::sync::mpsc::channel(); let (tx, rx) = std::sync::mpsc::channel();
let res = (self.run_on_main_thread)(Box::new(move || { let res = (self.context.run_on_main_thread)(Box::new(move || {
let mut script = let mut script =
osakit::Script::new_from_source(osakit::Language::AppleScript, &apple_script); osakit::Script::new_from_source(osakit::Language::AppleScript, &apple_script);
script.compile().expect("invalid AppleScript"); script.compile().expect("invalid AppleScript");
@@ -1450,7 +1521,7 @@ where
} }
// Validate signature // Validate signature
fn verify_signature(data: &[u8], release_signature: &str, pub_key: &str) -> Result<bool> { fn verify_signature(data: &[u8], release_signature: &str, pub_key: &str) -> Result<()> {
// we need to convert the pub key // we need to convert the pub key
let pub_key_decoded = base64_to_string(pub_key)?; let pub_key_decoded = base64_to_string(pub_key)?;
let public_key = PublicKey::decode(&pub_key_decoded)?; let public_key = PublicKey::decode(&pub_key_decoded)?;
@@ -1459,7 +1530,7 @@ fn verify_signature(data: &[u8], release_signature: &str, pub_key: &str) -> Resu
// Validate signature or bail out // Validate signature or bail out
public_key.verify(data, &signature, true)?; public_key.verify(data, &signature, true)?;
Ok(true) Ok(())
} }
fn base64_to_string(base64_string: &str) -> Result<String> { fn base64_to_string(base64_string: &str) -> Result<String> {
@@ -1498,25 +1569,31 @@ impl PathExt for PathBuf {
// adapted from https://github.com/rust-lang/rust/blob/1c047506f94cd2d05228eb992b0a6bbed1942349/library/std/src/sys/args/windows.rs#L174 // adapted from https://github.com/rust-lang/rust/blob/1c047506f94cd2d05228eb992b0a6bbed1942349/library/std/src/sys/args/windows.rs#L174
#[cfg(windows)] #[cfg(windows)]
fn escape_nsis_current_exe_arg(arg: &&OsStr) -> String { fn escape_nsis_current_exe_arg(arg: impl AsRef<OsStr>) -> OsString {
let arg = arg.to_string_lossy(); use std::os::windows::ffi::{OsStrExt, OsStringExt};
let mut cmd: Vec<char> = Vec::new();
let arg = arg.as_ref();
let mut cmd: Vec<u16> = Vec::new();
// compared to std we additionally escape `/` so that nsis won't interpret them as a beginning of an nsis argument. // compared to std we additionally escape `/` so that nsis won't interpret them as a beginning of an nsis argument.
let quote = arg.chars().any(|c| c == ' ' || c == '\t' || c == '/') || arg.is_empty(); let quote = arg
.as_encoded_bytes()
.iter()
.any(|c| *c == b' ' || *c == b'\t' || *c == b'/')
|| arg.is_empty();
let escape = true; let escape = true;
if quote { if quote {
cmd.push('"'); cmd.push('"' as u16);
} }
let mut backslashes: usize = 0; let mut backslashes: usize = 0;
for x in arg.chars() { for x in arg.encode_wide() {
if escape { if escape {
if x == '\\' { if x == '\\' as u16 {
backslashes += 1; backslashes += 1;
} else { } else {
if x == '"' { if x == '"' as u16 {
// Add n+1 backslashes to total 2n+1 before internal '"'. // Add n+1 backslashes to total 2n+1 before internal '"'.
cmd.extend((0..=backslashes).map(|_| '\\')); cmd.extend((0..=backslashes).map(|_| '\\' as u16));
} }
backslashes = 0; backslashes = 0;
} }
@@ -1525,10 +1602,10 @@ fn escape_nsis_current_exe_arg(arg: &&OsStr) -> String {
} }
if quote { if quote {
// Add n backslashes to total 2n before ending '"'. // Add n backslashes to total 2n before ending '"'.
cmd.extend((0..backslashes).map(|_| '\\')); cmd.extend((0..backslashes).map(|_| '\\' as u16));
cmd.push('"'); cmd.push('"' as u16);
} }
cmd.into_iter().collect() OsString::from_wide(&cmd)
} }
#[cfg(windows)] #[cfg(windows)]
@@ -6,8 +6,7 @@
"endpoints": ["http://localhost:3007"], "endpoints": ["http://localhost:3007"],
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEUwNDRGMjkwRjg2MDhCRDAKUldUUWkyRDRrUEpFNEQ4SmdwcU5PaXl6R2ZRUUNvUnhIaVkwVUltV0NMaEx6VTkrWVhpT0ZqeEEK", "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEUwNDRGMjkwRjg2MDhCRDAKUldUUWkyRDRrUEpFNEQ4SmdwcU5PaXl6R2ZRUUNvUnhIaVkwVUltV0NMaEx6VTkrWVhpT0ZqeEEK",
"windows": { "windows": {
"installMode": "quiet", "installMode": "quiet"
"installerArgs": ["/NS"]
} }
} }
}, },
@@ -46,7 +46,7 @@ struct Update {
platforms: HashMap<String, PlatformUpdate>, platforms: HashMap<String, PlatformUpdate>,
} }
fn build_app(cwd: &Path, config: &Config, bundle_updater: bool, target: BundleTarget) { fn build_app(cwd: &Path, config: &Config, target: Option<BundleTarget>) {
let mut command = Command::new("cargo"); let mut command = Command::new("cargo");
command command
.args(["tauri", "build", "--verbose"]) .args(["tauri", "build", "--verbose"])
@@ -56,17 +56,10 @@ fn build_app(cwd: &Path, config: &Config, bundle_updater: bool, target: BundleTa
.env("TAURI_SIGNING_PRIVATE_KEY_PASSWORD", "") .env("TAURI_SIGNING_PRIVATE_KEY_PASSWORD", "")
.current_dir(cwd); .current_dir(cwd);
#[cfg(target_os = "linux")] if let Some(target) = target {
command.args(["--bundles", target.name()]); command.arg("--bundles").arg(target.name());
#[cfg(target_os = "macos")]
command.args(["--bundles", target.name()]);
if bundle_updater {
#[cfg(windows)]
command.args(["--bundles", "msi", "nsis"]);
} else { } else {
#[cfg(windows)] command.arg("--no-bundle");
command.args(["--bundles", target.name()]);
} }
let status = command let status = command
@@ -318,7 +311,7 @@ fn update_app() {
{ {
// bundle app update // bundle app update
config.version = "1.0.0"; config.version = "1.0.0";
build_app(&manifest_dir, &config, true, BundleTarget::default()); build_app(&manifest_dir, &config, Some(bundle_target));
let bundle_updater_ext = if v1_compatible { let bundle_updater_ext = if v1_compatible {
out_bundle_path out_bundle_path
@@ -401,7 +394,7 @@ fn update_app() {
config.version = "0.1.0"; config.version = "0.1.0";
// bundle initial app version // bundle initial app version
build_app(&manifest_dir, &config, false, bundle_target); build_app(&manifest_dir, &config, None);
for expected_exit_code in status_checks { for expected_exit_code in status_checks {
let mut binary_cmd = if cfg!(windows) { let mut binary_cmd = if cfg!(windows) {
+3 -3
View File
@@ -3794,7 +3794,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.48.0",
] ]
[[package]] [[package]]
@@ -4137,9 +4137,9 @@ dependencies = [
[[package]] [[package]]
name = "wry" name = "wry"
version = "0.24.10" version = "0.24.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00711278ed357350d44c749c286786ecac644e044e4da410d466212152383b45" checksum = "4a2a144c3ab5e83e04724bc8e67cea552ffae413185fda459fafdae173fd985d"
dependencies = [ dependencies = [
"base64 0.13.1", "base64 0.13.1",
"block", "block",
+8
View File
@@ -8,6 +8,14 @@ edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }
repository = { workspace = true } repository = { workspace = true }
links = "tauri-plugin-upload" links = "tauri-plugin-upload"
exclude = [
"/banner.png",
"/guest-js",
"/package.json",
"/rollup.config.js",
"/test",
"/tsconfig.json",
]
[package.metadata.platforms.support] [package.metadata.platforms.support]
windows = { level = "full", notes = "" } windows = { level = "full", notes = "" }
+8 -1
View File
@@ -8,7 +8,14 @@ edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }
repository = { workspace = true } repository = { workspace = true }
links = "tauri-plugin-websocket" links = "tauri-plugin-websocket"
exclude = ["/examples"] exclude = [
"/banner.png",
"/examples",
"/guest-js",
"/package.json",
"/rollup.config.js",
"/tsconfig.json",
]
[package.metadata.platforms.support] [package.metadata.platforms.support]
windows = { level = "full", notes = "" } windows = { level = "full", notes = "" }
@@ -10,8 +10,8 @@
}, },
"devDependencies": { "devDependencies": {
"@tauri-apps/cli": "2.11.4", "@tauri-apps/cli": "2.11.4",
"typescript": "^6.0.0", "typescript": "^6.0.3",
"vite": "^8.0.1" "vite": "^8.0.16"
}, },
"dependencies": { "dependencies": {
"tauri-plugin-websocket-api": "link:..\\.." "tauri-plugin-websocket-api": "link:..\\.."
+7
View File
@@ -8,6 +8,13 @@ edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }
repository = { workspace = true } repository = { workspace = true }
links = "tauri-plugin-window-state" links = "tauri-plugin-window-state"
exclude = [
"/banner.png",
"/guest-js",
"/package.json",
"/rollup.config.js",
"/tsconfig.json",
]
[package.metadata.platforms.support] [package.metadata.platforms.support]
windows = { level = "full", notes = "" } windows = { level = "full", notes = "" }
+144 -163
View File
@@ -13,13 +13,13 @@ importers:
version: 10.0.1(eslint@10.4.0(jiti@2.6.1)) version: 10.0.1(eslint@10.4.0(jiti@2.6.1))
'@rollup/plugin-node-resolve': '@rollup/plugin-node-resolve':
specifier: 16.0.3 specifier: 16.0.3
version: 16.0.3(rollup@4.60.3) version: 16.0.3(rollup@4.62.2)
'@rollup/plugin-terser': '@rollup/plugin-terser':
specifier: 1.0.0 specifier: 1.0.0
version: 1.0.0(rollup@4.60.3) version: 1.0.0(rollup@4.62.2)
'@rollup/plugin-typescript': '@rollup/plugin-typescript':
specifier: 12.3.0 specifier: 12.3.0
version: 12.3.0(rollup@4.60.3)(tslib@2.8.1)(typescript@6.0.3) version: 12.3.0(rollup@4.62.2)(tslib@2.8.1)(typescript@6.0.3)
eslint: eslint:
specifier: 10.4.0 specifier: 10.4.0
version: 10.4.0(jiti@2.6.1) version: 10.4.0(jiti@2.6.1)
@@ -33,8 +33,8 @@ importers:
specifier: 3.8.3 specifier: 3.8.3
version: 3.8.3 version: 3.8.3
rollup: rollup:
specifier: 4.60.3 specifier: 4.62.2
version: 4.60.3 version: 4.62.2
tslib: tslib:
specifier: 2.8.1 specifier: 2.8.1
version: 2.8.1 version: 2.8.1
@@ -63,8 +63,8 @@ importers:
specifier: workspace:* specifier: workspace:*
version: link:../../plugins/clipboard-manager version: link:../../plugins/clipboard-manager
'@tauri-apps/plugin-dialog': '@tauri-apps/plugin-dialog':
specifier: '2' specifier: workspace:*
version: 2.7.1 version: link:../../plugins/dialog
'@tauri-apps/plugin-fs': '@tauri-apps/plugin-fs':
specifier: workspace:* specifier: workspace:*
version: link:../../plugins/fs version: link:../../plugins/fs
@@ -99,8 +99,8 @@ importers:
specifier: workspace:* specifier: workspace:*
version: link:../../plugins/shell version: link:../../plugins/shell
'@tauri-apps/plugin-store': '@tauri-apps/plugin-store':
specifier: '2' specifier: workspace:*
version: 2.4.3 version: link:../../plugins/store
'@tauri-apps/plugin-updater': '@tauri-apps/plugin-updater':
specifier: workspace:* specifier: workspace:*
version: link:../../plugins/updater version: link:../../plugins/updater
@@ -127,13 +127,13 @@ importers:
specifier: ^66.6.7 specifier: ^66.6.7
version: 66.6.7 version: 66.6.7
svelte: svelte:
specifier: ^5.54.0 specifier: ^5.55.7
version: 5.55.7(@typescript-eslint/types@8.58.2) version: 5.55.7(@typescript-eslint/types@8.58.2)
unocss: unocss:
specifier: ^66.6.7 specifier: ^66.6.7
version: 66.6.7(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(vite@8.0.16(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.1)(tsx@4.23.1)) version: 66.6.7(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(vite@8.0.16(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.1)(tsx@4.23.1))
vite: vite:
specifier: ^8.0.1 specifier: ^8.0.16
version: 8.0.16(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.1)(tsx@4.23.1) version: 8.0.16(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.1)(tsx@4.23.1)
plugins/autostart: plugins/autostart:
@@ -189,10 +189,10 @@ importers:
specifier: 2.11.4 specifier: 2.11.4
version: 2.11.4 version: 2.11.4
typescript: typescript:
specifier: ^6.0.0 specifier: ^6.0.3
version: 6.0.3 version: 6.0.3
vite: vite:
specifier: ^8.0.1 specifier: ^8.0.16
version: 8.0.16(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.1)(tsx@4.23.1) version: 8.0.16(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.1)(tsx@4.23.1)
plugins/dialog: plugins/dialog:
@@ -303,10 +303,10 @@ importers:
specifier: 2.11.4 specifier: 2.11.4
version: 2.11.4 version: 2.11.4
typescript: typescript:
specifier: ^6.0.0 specifier: ^6.0.3
version: 6.0.3 version: 6.0.3
vite: vite:
specifier: ^8.0.1 specifier: ^8.0.16
version: 8.0.16(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.1)(tsx@4.23.1) version: 8.0.16(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.1)(tsx@4.23.1)
plugins/stronghold: plugins/stronghold:
@@ -343,10 +343,10 @@ importers:
specifier: 2.11.4 specifier: 2.11.4
version: 2.11.4 version: 2.11.4
typescript: typescript:
specifier: ^6.0.0 specifier: ^6.0.3
version: 6.0.3 version: 6.0.3
vite: vite:
specifier: ^8.0.1 specifier: ^8.0.16
version: 8.0.16(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.1)(tsx@4.23.1) version: 8.0.16(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.1)(tsx@4.23.1)
plugins/window-state: plugins/window-state:
@@ -894,141 +894,141 @@ packages:
rollup: rollup:
optional: true optional: true
'@rollup/rollup-android-arm-eabi@4.60.3': '@rollup/rollup-android-arm-eabi@4.62.2':
resolution: {integrity: sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw==} resolution: {integrity: sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==}
cpu: [arm] cpu: [arm]
os: [android] os: [android]
'@rollup/rollup-android-arm64@4.60.3': '@rollup/rollup-android-arm64@4.62.2':
resolution: {integrity: sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw==} resolution: {integrity: sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==}
cpu: [arm64] cpu: [arm64]
os: [android] os: [android]
'@rollup/rollup-darwin-arm64@4.60.3': '@rollup/rollup-darwin-arm64@4.62.2':
resolution: {integrity: sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g==} resolution: {integrity: sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==}
cpu: [arm64] cpu: [arm64]
os: [darwin] os: [darwin]
'@rollup/rollup-darwin-x64@4.60.3': '@rollup/rollup-darwin-x64@4.62.2':
resolution: {integrity: sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw==} resolution: {integrity: sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==}
cpu: [x64] cpu: [x64]
os: [darwin] os: [darwin]
'@rollup/rollup-freebsd-arm64@4.60.3': '@rollup/rollup-freebsd-arm64@4.62.2':
resolution: {integrity: sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ==} resolution: {integrity: sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==}
cpu: [arm64] cpu: [arm64]
os: [freebsd] os: [freebsd]
'@rollup/rollup-freebsd-x64@4.60.3': '@rollup/rollup-freebsd-x64@4.62.2':
resolution: {integrity: sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA==} resolution: {integrity: sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==}
cpu: [x64] cpu: [x64]
os: [freebsd] os: [freebsd]
'@rollup/rollup-linux-arm-gnueabihf@4.60.3': '@rollup/rollup-linux-arm-gnueabihf@4.62.2':
resolution: {integrity: sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==} resolution: {integrity: sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [glibc] libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.60.3': '@rollup/rollup-linux-arm-musleabihf@4.62.2':
resolution: {integrity: sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==} resolution: {integrity: sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [musl] libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.60.3': '@rollup/rollup-linux-arm64-gnu@4.62.2':
resolution: {integrity: sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==} resolution: {integrity: sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc] libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.60.3': '@rollup/rollup-linux-arm64-musl@4.62.2':
resolution: {integrity: sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==} resolution: {integrity: sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl] libc: [musl]
'@rollup/rollup-linux-loong64-gnu@4.60.3': '@rollup/rollup-linux-loong64-gnu@4.62.2':
resolution: {integrity: sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==} resolution: {integrity: sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==}
cpu: [loong64] cpu: [loong64]
os: [linux] os: [linux]
libc: [glibc] libc: [glibc]
'@rollup/rollup-linux-loong64-musl@4.60.3': '@rollup/rollup-linux-loong64-musl@4.62.2':
resolution: {integrity: sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==} resolution: {integrity: sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==}
cpu: [loong64] cpu: [loong64]
os: [linux] os: [linux]
libc: [musl] libc: [musl]
'@rollup/rollup-linux-ppc64-gnu@4.60.3': '@rollup/rollup-linux-ppc64-gnu@4.62.2':
resolution: {integrity: sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==} resolution: {integrity: sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==}
cpu: [ppc64] cpu: [ppc64]
os: [linux] os: [linux]
libc: [glibc] libc: [glibc]
'@rollup/rollup-linux-ppc64-musl@4.60.3': '@rollup/rollup-linux-ppc64-musl@4.62.2':
resolution: {integrity: sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==} resolution: {integrity: sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==}
cpu: [ppc64] cpu: [ppc64]
os: [linux] os: [linux]
libc: [musl] libc: [musl]
'@rollup/rollup-linux-riscv64-gnu@4.60.3': '@rollup/rollup-linux-riscv64-gnu@4.62.2':
resolution: {integrity: sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==} resolution: {integrity: sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [glibc] libc: [glibc]
'@rollup/rollup-linux-riscv64-musl@4.60.3': '@rollup/rollup-linux-riscv64-musl@4.62.2':
resolution: {integrity: sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==} resolution: {integrity: sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [musl] libc: [musl]
'@rollup/rollup-linux-s390x-gnu@4.60.3': '@rollup/rollup-linux-s390x-gnu@4.62.2':
resolution: {integrity: sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==} resolution: {integrity: sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==}
cpu: [s390x] cpu: [s390x]
os: [linux] os: [linux]
libc: [glibc] libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.60.3': '@rollup/rollup-linux-x64-gnu@4.62.2':
resolution: {integrity: sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==} resolution: {integrity: sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc] libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.60.3': '@rollup/rollup-linux-x64-musl@4.62.2':
resolution: {integrity: sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==} resolution: {integrity: sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl] libc: [musl]
'@rollup/rollup-openbsd-x64@4.60.3': '@rollup/rollup-openbsd-x64@4.62.2':
resolution: {integrity: sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==} resolution: {integrity: sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==}
cpu: [x64] cpu: [x64]
os: [openbsd] os: [openbsd]
'@rollup/rollup-openharmony-arm64@4.60.3': '@rollup/rollup-openharmony-arm64@4.62.2':
resolution: {integrity: sha512-AaXwSvUi3QIPtroAUw1t5yHGIyqKEXwH54WUocFolZhpGDruJcs8c+xPNDRn4XiQsS7MEwnYsHW2l0MBLDMkWg==} resolution: {integrity: sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==}
cpu: [arm64] cpu: [arm64]
os: [openharmony] os: [openharmony]
'@rollup/rollup-win32-arm64-msvc@4.60.3': '@rollup/rollup-win32-arm64-msvc@4.62.2':
resolution: {integrity: sha512-65LAKM/bAWDqKNEelHlcHvm2V+Vfb8C6INFxQXRHCvaVN1rJfwr4NvdP4FyzUaLqWfaCGaadf6UbTm8xJeYfEg==} resolution: {integrity: sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==}
cpu: [arm64] cpu: [arm64]
os: [win32] os: [win32]
'@rollup/rollup-win32-ia32-msvc@4.60.3': '@rollup/rollup-win32-ia32-msvc@4.62.2':
resolution: {integrity: sha512-EEM2gyhBF5MFnI6vMKdX1LAosE627RGBzIoGMdLloPZkXrUN0Ckqgr2Qi8+J3zip/8NVVro3/FjB+tjhZUgUHA==} resolution: {integrity: sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==}
cpu: [ia32] cpu: [ia32]
os: [win32] os: [win32]
'@rollup/rollup-win32-x64-gnu@4.60.3': '@rollup/rollup-win32-x64-gnu@4.62.2':
resolution: {integrity: sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A==} resolution: {integrity: sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==}
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
'@rollup/rollup-win32-x64-msvc@4.60.3': '@rollup/rollup-win32-x64-msvc@4.62.2':
resolution: {integrity: sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA==} resolution: {integrity: sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==}
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
@@ -1123,21 +1123,12 @@ packages:
engines: {node: '>= 10'} engines: {node: '>= 10'}
hasBin: true hasBin: true
'@tauri-apps/plugin-dialog@2.7.1':
resolution: {integrity: sha512-OK1UBXYt+ojcmxMktzzuyonYIFta8CmAASpX+CA+DTGK24KlHjhYI6x2iOJ/TjZF4N7/ACK1oFmEOjIY9IhzOQ==}
'@tauri-apps/plugin-store@2.4.3':
resolution: {integrity: sha512-9LWPj9yMphRi9czEtUv87XHbl1b6xgd9EXpPrUnq6nG7+nbtoF84d4Kwz9xhAv/Hf30sr58pq7EOlyI936y8qw==}
'@tybys/wasm-util@0.10.2': '@tybys/wasm-util@0.10.2':
resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==}
'@types/esrecurse@4.3.1': '@types/esrecurse@4.3.1':
resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==}
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
'@types/estree@1.0.9': '@types/estree@1.0.9':
resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==}
@@ -1311,9 +1302,9 @@ packages:
resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
engines: {node: 18 || 20 || >=22} engines: {node: 18 || 20 || >=22}
brace-expansion@5.0.6: brace-expansion@5.0.8:
resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} resolution: {integrity: sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==}
engines: {node: 18 || 20 || >=22} engines: {node: 20 || >=22}
buffer-from@1.1.2: buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
@@ -1671,8 +1662,8 @@ packages:
ms@2.1.3: ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
nanoid@3.3.12: nanoid@3.3.16:
resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} resolution: {integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true hasBin: true
@@ -1739,8 +1730,8 @@ packages:
pkg-types@1.3.1: pkg-types@1.3.1:
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
postcss@8.5.15: postcss@8.5.23:
resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} resolution: {integrity: sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==}
engines: {node: ^10 || ^12 || >=14} engines: {node: ^10 || ^12 || >=14}
prelude-ls@1.2.1: prelude-ls@1.2.1:
@@ -1777,8 +1768,8 @@ packages:
engines: {node: ^20.19.0 || >=22.12.0} engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true hasBin: true
rollup@4.60.3: rollup@4.62.2:
resolution: {integrity: sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==} resolution: {integrity: sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'} engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true hasBin: true
@@ -2303,114 +2294,114 @@ snapshots:
'@rolldown/pluginutils@1.0.1': {} '@rolldown/pluginutils@1.0.1': {}
'@rollup/plugin-node-resolve@16.0.3(rollup@4.60.3)': '@rollup/plugin-node-resolve@16.0.3(rollup@4.62.2)':
dependencies: dependencies:
'@rollup/pluginutils': 5.3.0(rollup@4.60.3) '@rollup/pluginutils': 5.3.0(rollup@4.62.2)
'@types/resolve': 1.20.2 '@types/resolve': 1.20.2
deepmerge: 4.3.1 deepmerge: 4.3.1
is-module: 1.0.0 is-module: 1.0.0
resolve: 1.22.11 resolve: 1.22.11
optionalDependencies: optionalDependencies:
rollup: 4.60.3 rollup: 4.62.2
'@rollup/plugin-terser@1.0.0(rollup@4.60.3)': '@rollup/plugin-terser@1.0.0(rollup@4.62.2)':
dependencies: dependencies:
serialize-javascript: 7.0.5 serialize-javascript: 7.0.5
smob: 1.6.1 smob: 1.6.1
terser: 5.46.1 terser: 5.46.1
optionalDependencies: optionalDependencies:
rollup: 4.60.3 rollup: 4.62.2
'@rollup/plugin-typescript@12.3.0(rollup@4.60.3)(tslib@2.8.1)(typescript@6.0.3)': '@rollup/plugin-typescript@12.3.0(rollup@4.62.2)(tslib@2.8.1)(typescript@6.0.3)':
dependencies: dependencies:
'@rollup/pluginutils': 5.3.0(rollup@4.60.3) '@rollup/pluginutils': 5.3.0(rollup@4.62.2)
resolve: 1.22.11 resolve: 1.22.11
typescript: 6.0.3 typescript: 6.0.3
optionalDependencies: optionalDependencies:
rollup: 4.60.3 rollup: 4.62.2
tslib: 2.8.1 tslib: 2.8.1
'@rollup/pluginutils@5.3.0(rollup@4.60.3)': '@rollup/pluginutils@5.3.0(rollup@4.62.2)':
dependencies: dependencies:
'@types/estree': 1.0.9 '@types/estree': 1.0.9
estree-walker: 2.0.2 estree-walker: 2.0.2
picomatch: 4.0.4 picomatch: 4.0.4
optionalDependencies: optionalDependencies:
rollup: 4.60.3 rollup: 4.62.2
'@rollup/rollup-android-arm-eabi@4.60.3': '@rollup/rollup-android-arm-eabi@4.62.2':
optional: true optional: true
'@rollup/rollup-android-arm64@4.60.3': '@rollup/rollup-android-arm64@4.62.2':
optional: true optional: true
'@rollup/rollup-darwin-arm64@4.60.3': '@rollup/rollup-darwin-arm64@4.62.2':
optional: true optional: true
'@rollup/rollup-darwin-x64@4.60.3': '@rollup/rollup-darwin-x64@4.62.2':
optional: true optional: true
'@rollup/rollup-freebsd-arm64@4.60.3': '@rollup/rollup-freebsd-arm64@4.62.2':
optional: true optional: true
'@rollup/rollup-freebsd-x64@4.60.3': '@rollup/rollup-freebsd-x64@4.62.2':
optional: true optional: true
'@rollup/rollup-linux-arm-gnueabihf@4.60.3': '@rollup/rollup-linux-arm-gnueabihf@4.62.2':
optional: true optional: true
'@rollup/rollup-linux-arm-musleabihf@4.60.3': '@rollup/rollup-linux-arm-musleabihf@4.62.2':
optional: true optional: true
'@rollup/rollup-linux-arm64-gnu@4.60.3': '@rollup/rollup-linux-arm64-gnu@4.62.2':
optional: true optional: true
'@rollup/rollup-linux-arm64-musl@4.60.3': '@rollup/rollup-linux-arm64-musl@4.62.2':
optional: true optional: true
'@rollup/rollup-linux-loong64-gnu@4.60.3': '@rollup/rollup-linux-loong64-gnu@4.62.2':
optional: true optional: true
'@rollup/rollup-linux-loong64-musl@4.60.3': '@rollup/rollup-linux-loong64-musl@4.62.2':
optional: true optional: true
'@rollup/rollup-linux-ppc64-gnu@4.60.3': '@rollup/rollup-linux-ppc64-gnu@4.62.2':
optional: true optional: true
'@rollup/rollup-linux-ppc64-musl@4.60.3': '@rollup/rollup-linux-ppc64-musl@4.62.2':
optional: true optional: true
'@rollup/rollup-linux-riscv64-gnu@4.60.3': '@rollup/rollup-linux-riscv64-gnu@4.62.2':
optional: true optional: true
'@rollup/rollup-linux-riscv64-musl@4.60.3': '@rollup/rollup-linux-riscv64-musl@4.62.2':
optional: true optional: true
'@rollup/rollup-linux-s390x-gnu@4.60.3': '@rollup/rollup-linux-s390x-gnu@4.62.2':
optional: true optional: true
'@rollup/rollup-linux-x64-gnu@4.60.3': '@rollup/rollup-linux-x64-gnu@4.62.2':
optional: true optional: true
'@rollup/rollup-linux-x64-musl@4.60.3': '@rollup/rollup-linux-x64-musl@4.62.2':
optional: true optional: true
'@rollup/rollup-openbsd-x64@4.60.3': '@rollup/rollup-openbsd-x64@4.62.2':
optional: true optional: true
'@rollup/rollup-openharmony-arm64@4.60.3': '@rollup/rollup-openharmony-arm64@4.62.2':
optional: true optional: true
'@rollup/rollup-win32-arm64-msvc@4.60.3': '@rollup/rollup-win32-arm64-msvc@4.62.2':
optional: true optional: true
'@rollup/rollup-win32-ia32-msvc@4.60.3': '@rollup/rollup-win32-ia32-msvc@4.62.2':
optional: true optional: true
'@rollup/rollup-win32-x64-gnu@4.60.3': '@rollup/rollup-win32-x64-gnu@4.62.2':
optional: true optional: true
'@rollup/rollup-win32-x64-msvc@4.60.3': '@rollup/rollup-win32-x64-msvc@4.62.2':
optional: true optional: true
'@sveltejs/acorn-typescript@1.0.9(acorn@8.16.0)': '@sveltejs/acorn-typescript@1.0.9(acorn@8.16.0)':
@@ -2475,14 +2466,6 @@ snapshots:
'@tauri-apps/cli-win32-ia32-msvc': 2.11.4 '@tauri-apps/cli-win32-ia32-msvc': 2.11.4
'@tauri-apps/cli-win32-x64-msvc': 2.11.4 '@tauri-apps/cli-win32-x64-msvc': 2.11.4
'@tauri-apps/plugin-dialog@2.7.1':
dependencies:
'@tauri-apps/api': 2.11.0
'@tauri-apps/plugin-store@2.4.3':
dependencies:
'@tauri-apps/api': 2.11.0
'@tybys/wasm-util@0.10.2': '@tybys/wasm-util@0.10.2':
dependencies: dependencies:
tslib: 2.8.1 tslib: 2.8.1
@@ -2490,8 +2473,6 @@ snapshots:
'@types/esrecurse@4.3.1': {} '@types/esrecurse@4.3.1': {}
'@types/estree@1.0.8': {}
'@types/estree@1.0.9': {} '@types/estree@1.0.9': {}
'@types/json-schema@7.0.15': {} '@types/json-schema@7.0.15': {}
@@ -2748,7 +2729,7 @@ snapshots:
balanced-match@4.0.4: {} balanced-match@4.0.4: {}
brace-expansion@5.0.6: brace-expansion@5.0.8:
dependencies: dependencies:
balanced-match: 4.0.4 balanced-match: 4.0.4
@@ -3076,7 +3057,7 @@ snapshots:
minimatch@10.2.4: minimatch@10.2.4:
dependencies: dependencies:
brace-expansion: 5.0.6 brace-expansion: 5.0.8
mlly@1.8.2: mlly@1.8.2:
dependencies: dependencies:
@@ -3089,7 +3070,7 @@ snapshots:
ms@2.1.3: {} ms@2.1.3: {}
nanoid@3.3.12: {} nanoid@3.3.16: {}
natural-compare@1.4.0: {} natural-compare@1.4.0: {}
@@ -3175,9 +3156,9 @@ snapshots:
mlly: 1.8.2 mlly: 1.8.2
pathe: 2.0.3 pathe: 2.0.3
postcss@8.5.15: postcss@8.5.23:
dependencies: dependencies:
nanoid: 3.3.12 nanoid: 3.3.16
picocolors: 1.1.1 picocolors: 1.1.1
source-map-js: 1.2.1 source-map-js: 1.2.1
@@ -3220,35 +3201,35 @@ snapshots:
'@rolldown/binding-win32-arm64-msvc': 1.0.3 '@rolldown/binding-win32-arm64-msvc': 1.0.3
'@rolldown/binding-win32-x64-msvc': 1.0.3 '@rolldown/binding-win32-x64-msvc': 1.0.3
rollup@4.60.3: rollup@4.62.2:
dependencies: dependencies:
'@types/estree': 1.0.8 '@types/estree': 1.0.9
optionalDependencies: optionalDependencies:
'@rollup/rollup-android-arm-eabi': 4.60.3 '@rollup/rollup-android-arm-eabi': 4.62.2
'@rollup/rollup-android-arm64': 4.60.3 '@rollup/rollup-android-arm64': 4.62.2
'@rollup/rollup-darwin-arm64': 4.60.3 '@rollup/rollup-darwin-arm64': 4.62.2
'@rollup/rollup-darwin-x64': 4.60.3 '@rollup/rollup-darwin-x64': 4.62.2
'@rollup/rollup-freebsd-arm64': 4.60.3 '@rollup/rollup-freebsd-arm64': 4.62.2
'@rollup/rollup-freebsd-x64': 4.60.3 '@rollup/rollup-freebsd-x64': 4.62.2
'@rollup/rollup-linux-arm-gnueabihf': 4.60.3 '@rollup/rollup-linux-arm-gnueabihf': 4.62.2
'@rollup/rollup-linux-arm-musleabihf': 4.60.3 '@rollup/rollup-linux-arm-musleabihf': 4.62.2
'@rollup/rollup-linux-arm64-gnu': 4.60.3 '@rollup/rollup-linux-arm64-gnu': 4.62.2
'@rollup/rollup-linux-arm64-musl': 4.60.3 '@rollup/rollup-linux-arm64-musl': 4.62.2
'@rollup/rollup-linux-loong64-gnu': 4.60.3 '@rollup/rollup-linux-loong64-gnu': 4.62.2
'@rollup/rollup-linux-loong64-musl': 4.60.3 '@rollup/rollup-linux-loong64-musl': 4.62.2
'@rollup/rollup-linux-ppc64-gnu': 4.60.3 '@rollup/rollup-linux-ppc64-gnu': 4.62.2
'@rollup/rollup-linux-ppc64-musl': 4.60.3 '@rollup/rollup-linux-ppc64-musl': 4.62.2
'@rollup/rollup-linux-riscv64-gnu': 4.60.3 '@rollup/rollup-linux-riscv64-gnu': 4.62.2
'@rollup/rollup-linux-riscv64-musl': 4.60.3 '@rollup/rollup-linux-riscv64-musl': 4.62.2
'@rollup/rollup-linux-s390x-gnu': 4.60.3 '@rollup/rollup-linux-s390x-gnu': 4.62.2
'@rollup/rollup-linux-x64-gnu': 4.60.3 '@rollup/rollup-linux-x64-gnu': 4.62.2
'@rollup/rollup-linux-x64-musl': 4.60.3 '@rollup/rollup-linux-x64-musl': 4.62.2
'@rollup/rollup-openbsd-x64': 4.60.3 '@rollup/rollup-openbsd-x64': 4.62.2
'@rollup/rollup-openharmony-arm64': 4.60.3 '@rollup/rollup-openharmony-arm64': 4.62.2
'@rollup/rollup-win32-arm64-msvc': 4.60.3 '@rollup/rollup-win32-arm64-msvc': 4.62.2
'@rollup/rollup-win32-ia32-msvc': 4.60.3 '@rollup/rollup-win32-ia32-msvc': 4.62.2
'@rollup/rollup-win32-x64-gnu': 4.60.3 '@rollup/rollup-win32-x64-gnu': 4.62.2
'@rollup/rollup-win32-x64-msvc': 4.60.3 '@rollup/rollup-win32-x64-msvc': 4.62.2
fsevents: 2.3.3 fsevents: 2.3.3
safe-regex@2.1.1: safe-regex@2.1.1:
@@ -3412,7 +3393,7 @@ snapshots:
dependencies: dependencies:
lightningcss: 1.32.0 lightningcss: 1.32.0
picomatch: 4.0.4 picomatch: 4.0.4
postcss: 8.5.15 postcss: 8.5.23
rolldown: 1.0.3 rolldown: 1.0.3
tinyglobby: 0.2.17 tinyglobby: 0.2.17
optionalDependencies: optionalDependencies: