Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
270bdda
feat(updater): option to not restart after install
Legend-Master Mar 1, 2026
49255a6
More platform cfg
Legend-Master Mar 1, 2026
1ed77f7
Add the same function for `UpdaterBuilder`
Legend-Master Mar 1, 2026
2cae794
Fix missing `#[cfg(windows)]`
Legend-Master Mar 1, 2026
2725cd9
Mark `current_exe_args` cfg(windows)
Legend-Master Mar 1, 2026
d7f6c22
Mark `on_before_exit` cfg(windows)
Legend-Master Mar 1, 2026
86612c3
Mark `installer_args` cfg(windows)
Legend-Master Mar 1, 2026
162a29a
Note about `installer_arg` apply to both
Legend-Master Mar 1, 2026
beb85f0
Remove current args and restart together
Legend-Master Mar 1, 2026
a8c74f3
Only build needed bundle on windows as well
Legend-Master Mar 1, 2026
6113438
Remove `/NS` since it breaks the msi updater
Legend-Master Mar 1, 2026
cd18639
Add launch updater debug log
Legend-Master Mar 1, 2026
3decbf5
Add a folder to test updates
Legend-Master Mar 1, 2026
17344a5
Remove unused `#[allow(unused)]`
Legend-Master Mar 1, 2026
5fe4ced
Format
Legend-Master Mar 1, 2026
f3feb30
messed up git stage
Legend-Master Mar 1, 2026
b5d16d1
Add change file
Legend-Master Mar 1, 2026
534bd9a
Clean up
Legend-Master Mar 2, 2026
baca47b
Merge branch 'v2' into updater-install-no-restart
Legend-Master Mar 31, 2026
f5ac508
Disable NSIS compression for API example
Legend-Master Mar 31, 2026
b1578b7
Bump wry for v1 test to pull in https://github.com/tauri-apps/wry/pul…
Legend-Master Mar 31, 2026
f4c86c5
format
Legend-Master Mar 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changes/updater-install-no-restart.md
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions examples/api/src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@
},
"updater": {
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDE5QzMxNjYwNTM5OEUwNTgKUldSWTRKaFRZQmJER1h4d1ZMYVA3dnluSjdpN2RmMldJR09hUFFlZDY0SlFqckkvRUJhZDJVZXAK",
"dangerousInsecureTransportProtocol": true,
"endpoints": [
"http://localhost:5173/updater-test/updater.json",
"https://tauri-update-server.vercel.app/update/{{target}}/{{current_version}}"
]
}
Expand All @@ -99,6 +101,9 @@
"localePath": "locales/pt-BR.wxl"
}
}
},
"nsis": {
"compression": "none"
}
},
"iOS": {
Expand Down
23 changes: 13 additions & 10 deletions examples/api/src/views/Updater.svelte
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
<script>
import { check } from '@tauri-apps/plugin-updater'
<script lang="ts">
import { check, Update } from '@tauri-apps/plugin-updater'
import { relaunch } from '@tauri-apps/plugin-process'

export let onMessage
let { onMessage } = $props()

let isChecking, isInstalling, newUpdate
let totalSize = 0,
downloadedSize = 0
let isChecking = $state(false)
let isInstalling = $state(false)
let newUpdate: Update = $state()
let totalSize = $state(0)
let downloadedSize = $state(0)
let progress = $derived(
totalSize ? Math.round((downloadedSize / totalSize) * 100) : 0
)

async function checkUpdate() {
isChecking = true
Expand Down Expand Up @@ -53,15 +58,13 @@
isInstalling = false
}
}

$: progress = totalSize ? Math.round((downloadedSize / totalSize) * 100) : 0
</script>

<div class="flex children:grow children:h10">
{#if !isChecking && !newUpdate}
<button class="btn" on:click={checkUpdate}>Check update</button>
<button class="btn" onclick={checkUpdate}>Check update</button>
{:else if !isInstalling && newUpdate}
<button class="btn" on:click={install}>Install update</button>
<button class="btn" onclick={install}>Install update</button>
{:else}
<div class="progress">
<span>{progress}%</span>
Expand Down
5 changes: 5 additions & 0 deletions examples/api/updater-test/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
*

!.gitignore
!README.md
!updater.json
10 changes: 10 additions & 0 deletions examples/api/updater-test/README.md
Original file line number Diff line number Diff line change
@@ -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 changes: 11 additions & 0 deletions examples/api/updater-test/updater.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
2 changes: 1 addition & 1 deletion examples/api/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default defineConfig(async () => {
port: 5173,
strictPort: true,
fs: {
allow: ['.', '../../tooling/api/dist']
allow: ['.']
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion plugins/updater/api-iife.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 12 additions & 3 deletions plugins/updater/guest-js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ interface DownloadOptions {
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 {
rid: number
currentVersion: string
Expand Down Expand Up @@ -95,14 +103,15 @@ class Update extends Resource {
}

/** Install downloaded updater package */
async install(): Promise<void> {
async install(options?: InstallOptions): Promise<void> {
if (!this.downloadedBytes) {
throw new Error('Update.install called before Update.download')
}

await invoke('plugin:updater|install', {
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
Expand All @@ -112,7 +121,7 @@ class Update extends Resource {
/** Downloads the updater package and installs it */
async downloadAndInstall(
onEvent?: (progress: DownloadEvent) => void,
options?: DownloadOptions
options?: DownloadOptions & InstallOptions
): Promise<void> {
convertToRustHeaders(options)
const channel = new Channel<DownloadEvent>()
Expand Down
17 changes: 16 additions & 1 deletion plugins/updater/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,22 @@ pub(crate) async fn install<R: Runtime>(
webview: Webview<R>,
update_rid: ResourceId,
bytes_rid: ResourceId,
restart_after_install: Option<bool>,
) -> Result<()> {
let update = webview.resources_table().get::<Update>(update_rid)?;
let bytes = webview
.resources_table()
.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);
Ok(())
}
Expand All @@ -159,6 +169,7 @@ pub(crate) async fn download_and_install<R: Runtime>(
on_event: Channel<DownloadEvent>,
headers: Option<Vec<(String, String)>>,
timeout: Option<u64>,
restart_after_install: Option<bool>,
) -> Result<()> {
let update = webview.resources_table().get::<Update>(rid)?;

Expand All @@ -176,6 +187,10 @@ pub(crate) async fn download_and_install<R: Runtime>(
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;

update
Expand Down
19 changes: 17 additions & 2 deletions plugins/updater/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
pub fn nsis_args(&self) -> &'static [&'static str] {
// `/P`: Passive
// `/S`: Silent
// `/R`: Restart
match self {
Self::Passive => &["/P", "/R"],
Self::Quiet => &["/S", "/R"],
Self::Passive => &["/P"],
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 {
Expand All @@ -63,6 +76,8 @@ impl Display for WindowsUpdateInstallMode {
#[serde(rename_all = "camelCase")]
pub struct WindowsConfig {
/// Additional arguments given to the NSIS or WiX installer.
///
/// Note: this applies to both WiX and NSIS installers
#[serde(
default,
alias = "installer-args",
Expand Down
6 changes: 3 additions & 3 deletions plugins/updater/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,9 @@ impl<R: Runtime, T: Manager<R>> UpdaterExt<R> for T {
builder = builder.target(target);
}

let args = self.env().args_os;
if !args.is_empty() {
builder = builder.current_exe_args(args);
#[cfg(windows)]
{
builder = builder.current_exe_args(self.env().args_os);
}

builder.version_comparator = version_comparator.clone();
Expand Down
Loading
Loading