Skip to content

Commit ab7489c

Browse files
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 tauri-apps/wry#1703 * format * Make typescript happy * Close new update on destroy
1 parent 3fb27bf commit ab7489c

16 files changed

Lines changed: 327 additions & 187 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"updater": minor
3+
"updater-js": minor
4+
---
5+
6+
On Windows, add a new option `restartAfterInstall`/`restart_after_install` to install an update without the installer re-launching the app

examples/api/src-tauri/tauri.conf.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,9 @@
7777
},
7878
"updater": {
7979
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDE5QzMxNjYwNTM5OEUwNTgKUldSWTRKaFRZQmJER1h4d1ZMYVA3dnluSjdpN2RmMldJR09hUFFlZDY0SlFqckkvRUJhZDJVZXAK",
80+
"dangerousInsecureTransportProtocol": true,
8081
"endpoints": [
82+
"http://localhost:5173/updater-test/updater.json",
8183
"https://tauri-update-server.vercel.app/update/{{target}}/{{current_version}}"
8284
]
8385
}
@@ -99,6 +101,9 @@
99101
"localePath": "locales/pt-BR.wxl"
100102
}
101103
}
104+
},
105+
"nsis": {
106+
"compression": "none"
102107
}
103108
},
104109
"iOS": {

examples/api/src/views/Updater.svelte

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
1-
<script>
2-
import { check } from '@tauri-apps/plugin-updater'
1+
<script lang="ts">
2+
import { check, Update } from '@tauri-apps/plugin-updater'
33
import { relaunch } from '@tauri-apps/plugin-process'
4+
import { onDestroy } from 'svelte'
45
5-
export let onMessage
6+
let { onMessage } = $props()
67
7-
let isChecking, isInstalling, newUpdate
8-
let totalSize = 0,
9-
downloadedSize = 0
8+
let isChecking = $state(false)
9+
let isInstalling = $state(false)
10+
let newUpdate = $state<Update | undefined>()
11+
let totalSize = $state(0)
12+
let downloadedSize = $state(0)
13+
let progress = $derived(
14+
totalSize ? Math.round((downloadedSize / totalSize) * 100) : 0
15+
)
1016
1117
async function checkUpdate() {
1218
isChecking = true
@@ -31,10 +37,10 @@
3137
isInstalling = true
3238
downloadedSize = 0
3339
try {
34-
await newUpdate.downloadAndInstall((downloadProgress) => {
40+
await newUpdate!.downloadAndInstall((downloadProgress) => {
3541
switch (downloadProgress.event) {
3642
case 'Started':
37-
totalSize = downloadProgress.data.contentLength
43+
totalSize = downloadProgress.data.contentLength!
3844
break
3945
case 'Progress':
4046
downloadedSize += downloadProgress.data.chunkLength
@@ -54,14 +60,16 @@
5460
}
5561
}
5662
57-
$: progress = totalSize ? Math.round((downloadedSize / totalSize) * 100) : 0
63+
onDestroy(() => {
64+
newUpdate?.close()
65+
})
5866
</script>
5967

6068
<div class="flex children:grow children:h10">
6169
{#if !isChecking && !newUpdate}
62-
<button class="btn" on:click={checkUpdate}>Check update</button>
70+
<button class="btn" onclick={checkUpdate}>Check update</button>
6371
{:else if !isInstalling && newUpdate}
64-
<button class="btn" on:click={install}>Install update</button>
72+
<button class="btn" onclick={install}>Install update</button>
6573
{:else}
6674
<div class="progress">
6775
<span>{progress}%</span>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
*
2+
3+
!.gitignore
4+
!README.md
5+
!updater.json
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
To test the updater:
2+
3+
1. Comment out `verify_signature` inside `plugins/updater/src/updater.rs`
4+
2. Run `pnpm tauri build --debug` to build the bundles
5+
3. Copy the bundle you want to test to this folder (`examples/api/updater-test/`)
6+
4. Run `pnpm tauri dev` and open the Updater tab to check
7+
8+
If the bundle you're testing doesn't exist in the `updater.json` yet, add it manually and welcome to open an PR
9+
10+
> The `updater.json` and debug bundles will be served by `vite`
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"version": "2.1.0",
3+
"notes": "Test update!",
4+
"pub_date": "2026-03-01T14:04:20+00:00",
5+
"platforms": {
6+
"windows-x86_64": {
7+
"signature": "",
8+
"url": "http://localhost:5173/updater-test/Tauri API_2.0.0_x64_en-US.msi"
9+
}
10+
}
11+
}

examples/api/vite.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export default defineConfig(async () => {
2727
port: 5173,
2828
strictPort: true,
2929
fs: {
30-
allow: ['.', '../../tooling/api/dist']
30+
allow: ['.']
3131
}
3232
}
3333
}

plugins/updater/api-iife.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

plugins/updater/guest-js/index.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ interface DownloadOptions {
4040
timeout?: number
4141
}
4242

43+
/** Options used when installing an update */
44+
interface InstallOptions {
45+
/**
46+
* If the Windows installer should restart the app after installed, default is `true`
47+
*/
48+
restartAfterInstall?: boolean
49+
}
50+
4351
interface UpdateMetadata {
4452
rid: number
4553
currentVersion: string
@@ -95,14 +103,15 @@ class Update extends Resource {
95103
}
96104

97105
/** Install downloaded updater package */
98-
async install(): Promise<void> {
106+
async install(options?: InstallOptions): Promise<void> {
99107
if (!this.downloadedBytes) {
100108
throw new Error('Update.install called before Update.download')
101109
}
102110

103111
await invoke('plugin:updater|install', {
104112
updateRid: this.rid,
105-
bytesRid: this.downloadedBytes.rid
113+
bytesRid: this.downloadedBytes.rid,
114+
...options
106115
})
107116

108117
// Don't need to call close, we did it in rust side already
@@ -112,7 +121,7 @@ class Update extends Resource {
112121
/** Downloads the updater package and installs it */
113122
async downloadAndInstall(
114123
onEvent?: (progress: DownloadEvent) => void,
115-
options?: DownloadOptions
124+
options?: DownloadOptions & InstallOptions
116125
): Promise<void> {
117126
convertToRustHeaders(options)
118127
const channel = new Channel<DownloadEvent>()

plugins/updater/src/commands.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,12 +142,22 @@ pub(crate) async fn install<R: Runtime>(
142142
webview: Webview<R>,
143143
update_rid: ResourceId,
144144
bytes_rid: ResourceId,
145+
restart_after_install: Option<bool>,
145146
) -> Result<()> {
146147
let update = webview.resources_table().get::<Update>(update_rid)?;
147148
let bytes = webview
148149
.resources_table()
149150
.get::<DownloadedBytes>(bytes_rid)?;
150-
update.install(&bytes.0)?;
151+
152+
if let Some(restart_after_install) = restart_after_install {
153+
let update = (*update).clone();
154+
update
155+
.restart_after_install(restart_after_install)
156+
.install(&bytes.0)?;
157+
} else {
158+
update.install(&bytes.0)?;
159+
}
160+
151161
let _ = webview.resources_table().close(bytes_rid);
152162
Ok(())
153163
}
@@ -159,6 +169,7 @@ pub(crate) async fn download_and_install<R: Runtime>(
159169
on_event: Channel<DownloadEvent>,
160170
headers: Option<Vec<(String, String)>>,
161171
timeout: Option<u64>,
172+
restart_after_install: Option<bool>,
162173
) -> Result<()> {
163174
let update = webview.resources_table().get::<Update>(rid)?;
164175

@@ -176,6 +187,10 @@ pub(crate) async fn download_and_install<R: Runtime>(
176187
update.timeout = Some(Duration::from_millis(timeout));
177188
}
178189

190+
if let Some(restart_after_install) = restart_after_install {
191+
update = update.restart_after_install(restart_after_install);
192+
}
193+
179194
let mut first_chunk = true;
180195

181196
update

0 commit comments

Comments
 (0)