feat!(core): add bundle createUpdaterArtifacts configuration (#9883)

* Add updater field

* Don't sign updaters when updater field is false

* Clippy

* Add updater to bundle migration

* Format

* Add updater config to api example

* No warning if update is not enabled

* Build

* Add change file

* We don't generate updater for dmg package

* Warning only for v1 compatible

* clean up

* More clean up

* little bit more

* Apply suggestions from code review

Co-authored-by: Amr Bashir <amr.bashir2015@gmail.com>

* Revert license header change

* Remove option around pubkey and msi args

* More migration tests

* Refactor private_key getter

* Only generate signature for updater for v1 compat

* Format

* Use map_err instead of anyhow context

* Don't generate updater for api example

* Fix misaligned comment

* Rename `updater` to `createUpdaterArtifacts`

* Revert changes in helloworld example

* Add warning for v1 compatible

* Update .changes/separate-updater-field.md

Co-authored-by: Lucas Nogueira <118899497+lucasfernog-crabnebula@users.noreply.github.com>

* update error messages [skip ci]

---------

Co-authored-by: Amr Bashir <amr.bashir2015@gmail.com>
Co-authored-by: Lucas Nogueira <118899497+lucasfernog-crabnebula@users.noreply.github.com>
Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
This commit is contained in:
Tony 2024-07-01 19:34:58 +08:00 committed by GitHub
parent e7fd7c60d6
commit 911242f092
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 346 additions and 153 deletions

View File

@ -0,0 +1,8 @@
---
"tauri-utils": patch:breaking
"tauri-bundler": patch:breaking
"tauri-cli": patch:breaking
"@tauri-apps/cli": patch:breaking
---
Move updater target from `bundle > targets` to a separate field `bundle > createUpdaterArtifacts`

View File

@ -72,6 +72,7 @@
"android": {
"minSdkVersion": 24
},
"createUpdaterArtifacts": false,
"iOS": {},
"icon": [],
"linux": {
@ -1519,7 +1520,7 @@
"type": "boolean"
},
"targets": {
"description": "The bundle targets, currently supports [\"deb\", \"rpm\", \"appimage\", \"nsis\", \"msi\", \"app\", \"dmg\", \"updater\"] or \"all\".",
"description": "The bundle targets, currently supports [\"deb\", \"rpm\", \"appimage\", \"nsis\", \"msi\", \"app\", \"dmg\"] or \"all\".",
"default": "all",
"allOf": [
{
@ -1527,6 +1528,15 @@
}
]
},
"createUpdaterArtifacts": {
"description": "Produce updaters and their signatures or not",
"default": false,
"allOf": [
{
"$ref": "#/definitions/Updater"
}
]
},
"publisher": {
"description": "The application's publisher. Defaults to the second element in the identifier string.\n Currently maps to the Manufacturer property of the Windows Installer.",
"type": [
@ -1794,12 +1804,34 @@
"enum": [
"dmg"
]
}
]
},
"Updater": {
"description": "Updater type",
"anyOf": [
{
"description": "Generates lagacy zipped v1 compatible updaters",
"allOf": [
{
"$ref": "#/definitions/V1Compatible"
}
]
},
{
"description": "The Tauri updater bundle.",
"description": "Produce updaters and their signatures or not",
"type": "boolean"
}
]
},
"V1Compatible": {
"description": "Generates lagacy zipped v1 compatible updaters",
"oneOf": [
{
"description": "Generates lagacy zipped v1 compatible updaters",
"type": "string",
"enum": [
"updater"
"v1Compatible"
]
}
]

View File

@ -116,8 +116,6 @@ pub enum BundleType {
App,
/// The Apple Disk Image bundle (.dmg).
Dmg,
/// The Tauri updater bundle.
Updater,
}
impl BundleType {
@ -131,7 +129,6 @@ impl BundleType {
BundleType::Nsis,
BundleType::App,
BundleType::Dmg,
BundleType::Updater,
]
}
}
@ -149,7 +146,6 @@ impl Display for BundleType {
Self::Nsis => "nsis",
Self::App => "app",
Self::Dmg => "dmg",
Self::Updater => "updater",
}
)
}
@ -178,7 +174,6 @@ impl<'de> Deserialize<'de> for BundleType {
"nsis" => Ok(Self::Nsis),
"app" => Ok(Self::App),
"dmg" => Ok(Self::Dmg),
"updater" => Ok(Self::Updater),
_ => Err(DeError::custom(format!("unknown bundle target '{s}'"))),
}
}
@ -1070,6 +1065,33 @@ impl BundleResources {
}
}
/// Updater type
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
pub enum Updater {
/// Generates lagacy zipped v1 compatible updaters
String(V1Compatible),
/// Produce updaters and their signatures or not
// Can't use untagged on enum field here: https://github.com/GREsau/schemars/issues/222
Bool(bool),
}
impl Default for Updater {
fn default() -> Self {
Self::Bool(false)
}
}
/// Generates lagacy zipped v1 compatible updaters
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub enum V1Compatible {
/// Generates lagacy zipped v1 compatible updaters
V1Compatible,
}
/// Configuration for tauri-bundler.
///
/// See more: <https://tauri.app/v1/api/config#bundleconfig>
@ -1081,9 +1103,12 @@ pub struct BundleConfig {
/// Whether Tauri should bundle your application or just output the executable.
#[serde(default)]
pub active: bool,
/// The bundle targets, currently supports ["deb", "rpm", "appimage", "nsis", "msi", "app", "dmg", "updater"] or "all".
/// The bundle targets, currently supports ["deb", "rpm", "appimage", "nsis", "msi", "app", "dmg"] or "all".
#[serde(default)]
pub targets: BundleTarget,
#[serde(default)]
/// Produce updaters and their signatures or not
pub create_updater_artifacts: Updater,
/// The application's publisher. Defaults to the second element in the identifier string.
/// Currently maps to the Manufacturer property of the Windows Installer.
pub publisher: Option<String>,
@ -2474,6 +2499,7 @@ mod build {
let icon = vec_lit(&self.icon, str_lit);
let active = self.active;
let targets = quote!(Default::default());
let create_updater_artifacts = quote!(Default::default());
let resources = quote!(None);
let copyright = quote!(None);
let category = quote!(None);
@ -2497,6 +2523,7 @@ mod build {
homepage,
icon,
targets,
create_updater_artifacts,
resources,
copyright,
category,
@ -2811,6 +2838,7 @@ mod test {
let bundle = BundleConfig {
active: false,
targets: Default::default(),
create_updater_artifacts: Default::default(),
publisher: None,
homepage: None,
icon: Vec::new(),

View File

@ -42,7 +42,7 @@ pub struct Bundle {
/// Bundles the project.
/// Returns the list of paths where the bundles can be found.
pub fn bundle_project(settings: Settings) -> crate::Result<Vec<Bundle>> {
pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<Bundle>> {
let mut package_types = settings.package_types()?;
if package_types.is_empty() {
return Ok(Vec::new());
@ -50,8 +50,6 @@ pub fn bundle_project(settings: Settings) -> crate::Result<Vec<Bundle>> {
package_types.sort_by_key(|a| a.priority());
let mut bundles: Vec<Bundle> = Vec::new();
let target_os = settings
.target()
.split('-')
@ -68,7 +66,7 @@ pub fn bundle_project(settings: Settings) -> crate::Result<Vec<Bundle>> {
if settings.can_sign() {
for bin in settings.binaries() {
let bin_path = settings.binary_path(bin);
windows::sign::try_sign(&bin_path, &settings)?;
windows::sign::try_sign(&bin_path, settings)?;
}
// Sign the sidecar binaries
@ -89,7 +87,7 @@ pub fn bundle_project(settings: Settings) -> crate::Result<Vec<Bundle>> {
continue;
}
windows::sign::try_sign(&path, &settings)?;
windows::sign::try_sign(&path, settings)?;
}
} else {
#[cfg(not(target_os = "windows"))]
@ -97,6 +95,7 @@ pub fn bundle_project(settings: Settings) -> crate::Result<Vec<Bundle>> {
}
}
let mut bundles = Vec::<Bundle>::new();
for package_type in &package_types {
// bundle was already built! e.g. DMG already built .app
if bundles.iter().any(|b| b.package_type == *package_type) {
@ -105,13 +104,13 @@ pub fn bundle_project(settings: Settings) -> crate::Result<Vec<Bundle>> {
let bundle_paths = match package_type {
#[cfg(target_os = "macos")]
PackageType::MacOsBundle => macos::app::bundle_project(&settings)?,
PackageType::MacOsBundle => macos::app::bundle_project(settings)?,
#[cfg(target_os = "macos")]
PackageType::IosBundle => macos::ios::bundle_project(&settings)?,
PackageType::IosBundle => macos::ios::bundle_project(settings)?,
// dmg is dependent of MacOsBundle, we send our bundles to prevent rebuilding
#[cfg(target_os = "macos")]
PackageType::Dmg => {
let bundled = macos::dmg::bundle_project(&settings, &bundles)?;
let bundled = macos::dmg::bundle_project(settings, &bundles)?;
if !bundled.app.is_empty() {
bundles.push(Bundle {
package_type: PackageType::MacOsBundle,
@ -122,33 +121,15 @@ pub fn bundle_project(settings: Settings) -> crate::Result<Vec<Bundle>> {
}
#[cfg(target_os = "windows")]
PackageType::WindowsMsi => windows::msi::bundle_project(&settings, false)?,
PackageType::Nsis => windows::nsis::bundle_project(&settings, false)?,
PackageType::WindowsMsi => windows::msi::bundle_project(settings, false)?,
PackageType::Nsis => windows::nsis::bundle_project(settings, false)?,
#[cfg(target_os = "linux")]
PackageType::Deb => linux::debian::bundle_project(&settings)?,
PackageType::Deb => linux::debian::bundle_project(settings)?,
#[cfg(target_os = "linux")]
PackageType::Rpm => linux::rpm::bundle_project(&settings)?,
PackageType::Rpm => linux::rpm::bundle_project(settings)?,
#[cfg(target_os = "linux")]
PackageType::AppImage => linux::appimage::bundle_project(&settings)?,
// updater is dependent of multiple bundle, we send our bundles to prevent rebuilding
PackageType::Updater => {
if !package_types.iter().any(|p| {
matches!(
p,
PackageType::AppImage
| PackageType::MacOsBundle
| PackageType::Dmg
| PackageType::Nsis
| PackageType::WindowsMsi
)
}) {
log::warn!("The updater bundle target exists but couldn't find any updater-enabled target, so the updater artifacts won't be generated. Please add one of these targets as well: app, appimage, msi, nsis");
continue;
}
updater_bundle::bundle_project(&settings, &bundles)?
}
PackageType::AppImage => linux::appimage::bundle_project(settings)?,
_ => {
log::warn!("ignoring {}", package_type.short_name());
continue;
@ -161,6 +142,33 @@ pub fn bundle_project(settings: Settings) -> crate::Result<Vec<Bundle>> {
});
}
if let Some(updater) = settings.updater() {
if package_types.iter().any(|package_type| {
if updater.v1_compatible {
matches!(
package_type,
PackageType::AppImage
| PackageType::MacOsBundle
| PackageType::Nsis
| PackageType::WindowsMsi
)
} else {
matches!(package_type, PackageType::MacOsBundle)
}
}) {
let updater_paths = updater_bundle::bundle_project(settings, &bundles)?;
bundles.push(Bundle {
package_type: PackageType::Updater,
bundle_paths: updater_paths,
});
} else if updater.v1_compatible {
log::warn!("The updater bundle target exists but couldn't find any updater-enabled target, so the updater artifacts won't be generated. Please add one of these targets as well: app, appimage, msi, nsis");
}
if updater.v1_compatible {
log::warn!("Legacy v1 compatible updater is deprecated and will be removed in v3, change bundle > createUpdaterArtifacts to true when your users are updated to the version with v2 updater plugin");
}
}
#[cfg(target_os = "macos")]
{
// Clean up .app if only building dmg or updater
@ -188,34 +196,37 @@ pub fn bundle_project(settings: Settings) -> crate::Result<Vec<Bundle>> {
}
}
if !bundles.is_empty() {
let bundles_wo_updater = bundles
.iter()
.filter(|b| b.package_type != PackageType::Updater)
.collect::<Vec<_>>();
let pluralised = if bundles_wo_updater.len() == 1 {
"bundle"
} else {
"bundles"
};
let mut printable_paths = String::new();
for bundle in &bundles {
for path in &bundle.bundle_paths {
let mut note = "";
if bundle.package_type == crate::PackageType::Updater {
note = " (updater)";
}
writeln!(printable_paths, " {}{}", display_path(path), note).unwrap();
}
}
log::info!(action = "Finished"; "{} {} at:\n{}", bundles_wo_updater.len(), pluralised, printable_paths);
Ok(bundles)
} else {
Err(anyhow::anyhow!("No bundles were built").into())
if bundles.is_empty() {
return Err(anyhow::anyhow!("No bundles were built").into());
}
let bundles_wo_updater = bundles
.iter()
.filter(|b| b.package_type != PackageType::Updater)
.collect::<Vec<_>>();
let finished_bundles = bundles_wo_updater.len();
let pluralised = if finished_bundles == 1 {
"bundle"
} else {
"bundles"
};
let mut printable_paths = String::new();
for bundle in &bundles {
for path in &bundle.bundle_paths {
let note = if bundle.package_type == crate::PackageType::Updater {
" (updater)"
} else {
""
};
let path_display = display_path(path);
writeln!(printable_paths, " {path_display}{note}").unwrap();
}
}
log::info!(action = "Finished"; "{finished_bundles} {pluralised} at:\n{printable_paths}");
Ok(bundles)
}
/// Check to see if there are icons in the settings struct

View File

@ -50,7 +50,6 @@ impl From<BundleType> for PackageType {
BundleType::Nsis => Self::Nsis,
BundleType::App => Self::MacOsBundle,
BundleType::Dmg => Self::Dmg,
BundleType::Updater => Self::Updater,
}
}
}
@ -156,10 +155,12 @@ pub struct PackageSettings {
/// The updater settings.
#[derive(Debug, Default, Clone)]
pub struct UpdaterSettings {
/// Should generate v1 compatible zipped updater
pub v1_compatible: bool,
/// Signature public key.
pub pubkey: String,
/// Args to pass to `msiexec.exe` to run the updater on Windows.
pub msiexec_args: Option<&'static [&'static str]>,
pub msiexec_args: &'static [&'static str],
}
/// The Linux debian bundle settings.
@ -865,7 +866,7 @@ impl Settings {
.unwrap_or(std::env::consts::OS)
.replace("darwin", "macos");
let mut platform_types = match target_os.as_str() {
let platform_types = match target_os.as_str() {
"macos" => vec![PackageType::MacOsBundle, PackageType::Dmg],
"ios" => vec![PackageType::IosBundle],
"linux" => vec![PackageType::Deb, PackageType::Rpm, PackageType::AppImage],
@ -877,10 +878,6 @@ impl Settings {
}
};
if self.is_update_enabled() {
platform_types.push(PackageType::Updater);
}
if let Some(package_types) = &self.package_types {
let mut types = vec![];
for package_type in package_types {
@ -1087,14 +1084,4 @@ impl Settings {
pub fn updater(&self) -> Option<&UpdaterSettings> {
self.bundle_settings.updater.as_ref()
}
/// Is update enabled
pub fn is_update_enabled(&self) -> bool {
self
.bundle_settings
.updater
.as_ref()
.map(|u| !u.pubkey.is_empty())
.unwrap_or_default()
}
}

View File

@ -646,7 +646,7 @@ pub fn build_wix_app_installer(
to_json(
settings
.updater()
.and_then(|updater| updater.msiexec_args)
.map(|updater| updater.msiexec_args)
.map(|args| args.join(" "))
.unwrap_or_else(|| "/passive".to_string()),
),

View File

@ -72,6 +72,7 @@
"android": {
"minSdkVersion": 24
},
"createUpdaterArtifacts": false,
"iOS": {},
"icon": [],
"linux": {
@ -1519,7 +1520,7 @@
"type": "boolean"
},
"targets": {
"description": "The bundle targets, currently supports [\"deb\", \"rpm\", \"appimage\", \"nsis\", \"msi\", \"app\", \"dmg\", \"updater\"] or \"all\".",
"description": "The bundle targets, currently supports [\"deb\", \"rpm\", \"appimage\", \"nsis\", \"msi\", \"app\", \"dmg\"] or \"all\".",
"default": "all",
"allOf": [
{
@ -1527,6 +1528,15 @@
}
]
},
"createUpdaterArtifacts": {
"description": "Produce updaters and their signatures or not",
"default": false,
"allOf": [
{
"$ref": "#/definitions/Updater"
}
]
},
"publisher": {
"description": "The application's publisher. Defaults to the second element in the identifier string.\n Currently maps to the Manufacturer property of the Windows Installer.",
"type": [
@ -1794,12 +1804,34 @@
"enum": [
"dmg"
]
}
]
},
"Updater": {
"description": "Updater type",
"anyOf": [
{
"description": "Generates lagacy zipped v1 compatible updaters",
"allOf": [
{
"$ref": "#/definitions/V1Compatible"
}
]
},
{
"description": "The Tauri updater bundle.",
"description": "Produce updaters and their signatures or not",
"type": "boolean"
}
]
},
"V1Compatible": {
"description": "Generates lagacy zipped v1 compatible updaters",
"oneOf": [
{
"description": "Generates lagacy zipped v1 compatible updaters",
"type": "string",
"enum": [
"updater"
"v1Compatible"
]
}
]

View File

@ -198,93 +198,104 @@ pub fn bundle<A: AppSettings>(
}
}
let bundles = tauri_bundler::bundle_project(settings)
let bundles = tauri_bundler::bundle_project(&settings)
.map_err(|e| match e {
tauri_bundler::Error::BundlerError(e) => e,
e => anyhow::anyhow!("{e:#}"),
})
.with_context(|| "failed to bundle project")?;
sign_updaters(settings, bundles, ci)?;
Ok(())
}
fn sign_updaters(
settings: tauri_bundler::Settings,
bundles: Vec<tauri_bundler::Bundle>,
ci: bool,
) -> crate::Result<()> {
let Some(update_settings) = settings.updater() else {
// Updater not enabled
return Ok(());
};
let update_enabled_bundles: Vec<&tauri_bundler::Bundle> = bundles
.iter()
.filter(|bundle| {
matches!(
bundle.package_type,
PackageType::Updater | PackageType::Nsis | PackageType::WindowsMsi | PackageType::AppImage
)
if update_settings.v1_compatible {
matches!(bundle.package_type, PackageType::Updater)
} else {
matches!(
bundle.package_type,
PackageType::Updater
| PackageType::Nsis
| PackageType::WindowsMsi
| PackageType::AppImage
)
}
})
.collect();
// Skip if no updater is active
if !update_enabled_bundles.is_empty() {
let updater_pub_key = config
.plugins
.0
.get("updater")
.and_then(|k| k.get("pubkey"))
.and_then(|v| v.as_str())
.map(|v| v.to_string());
if update_enabled_bundles.is_empty() {
return Ok(());
}
if let Some(pubkey) = updater_pub_key {
// get the public key
// check if pubkey points to a file...
let maybe_path = Path::new(&pubkey);
let pubkey = if maybe_path.exists() {
std::fs::read_to_string(maybe_path)?
} else {
pubkey
};
// get the public key
let pubkey = &update_settings.pubkey;
// check if pubkey points to a file...
let maybe_path = Path::new(pubkey);
let pubkey = if maybe_path.exists() {
std::fs::read_to_string(maybe_path)?
} else {
pubkey.to_string()
};
// if no password provided we use an empty string
let password = std::env::var("TAURI_SIGNING_PRIVATE_KEY_PASSWORD")
.ok()
.or_else(|| if ci { Some("".into()) } else { None });
// if no password provided we use an empty string
let password = std::env::var("TAURI_SIGNING_PRIVATE_KEY_PASSWORD")
.ok()
.or_else(|| if ci { Some("".into()) } else { None });
// get the private key
let secret_key = match std::env::var("TAURI_SIGNING_PRIVATE_KEY") {
Ok(private_key) => {
// check if private_key points to a file...
let maybe_path = Path::new(&private_key);
let private_key = if maybe_path.exists() {
std::fs::read_to_string(maybe_path)?
} else {
private_key
};
updater_signature::secret_key(private_key, password)
}
_ => Err(anyhow::anyhow!("A public key has been found, but no private key. Make sure to set `TAURI_SIGNING_PRIVATE_KEY` environment variable.")),
}?;
// get the private key
let private_key = std::env::var("TAURI_SIGNING_PRIVATE_KEY")
.map_err(|_| anyhow::anyhow!("A public key has been found, but no private key. Make sure to set `TAURI_SIGNING_PRIVATE_KEY` environment variable."))?;
// check if private_key points to a file...
let maybe_path = Path::new(&private_key);
let private_key = if maybe_path.exists() {
std::fs::read_to_string(maybe_path)?
} else {
private_key
};
let secret_key = updater_signature::secret_key(private_key, password)?;
let pubkey = base64::engine::general_purpose::STANDARD.decode(pubkey)?;
let pub_key_decoded = String::from_utf8_lossy(&pubkey);
let public_key = minisign::PublicKeyBox::from_string(&pub_key_decoded)?.into_public_key()?;
let pubkey = base64::engine::general_purpose::STANDARD.decode(pubkey)?;
let pub_key_decoded = String::from_utf8_lossy(&pubkey);
let public_key = minisign::PublicKeyBox::from_string(&pub_key_decoded)?.into_public_key()?;
// make sure we have our package built
let mut signed_paths = Vec::new();
for bundle in update_enabled_bundles {
// we expect to have only one path in the vec but we iter if we add
// another type of updater package who require multiple file signature
for path in bundle.bundle_paths.iter() {
// sign our path from environment variables
let (signature_path, signature) = updater_signature::sign_file(&secret_key, path)?;
if signature.keynum() != public_key.keynum() {
log::warn!("The updater secret key from `TAURI_PRIVATE_KEY` does not match the public key from `plugins > updater > pubkey`. If you are not rotating keys, this means your configuration is wrong and won't be accepted at runtime when performing update.");
}
signed_paths.push(signature_path);
}
let mut signed_paths = Vec::new();
for bundle in update_enabled_bundles {
// we expect to have only one path in the vec but we iter if we add
// another type of updater package who require multiple file signature
for path in &bundle.bundle_paths {
// sign our path from environment variables
let (signature_path, signature) = updater_signature::sign_file(&secret_key, path)?;
if signature.keynum() != public_key.keynum() {
log::warn!("The updater secret key from `TAURI_SIGNING_PRIVATE_KEY` does not match the public key from `plugins > updater > pubkey`. If you are not rotating keys, this means your configuration is wrong and won't be accepted at runtime when performing update.");
}
print_signed_updater_archive(&signed_paths)?;
signed_paths.push(signature_path);
}
}
print_signed_updater_archive(&signed_paths)?;
Ok(())
}
fn print_signed_updater_archive(output_paths: &[PathBuf]) -> crate::Result<()> {
use std::fmt::Write;
if !output_paths.is_empty() {
let pluralised = if output_paths.len() == 1 {
let finished_bundles = output_paths.len();
let pluralised = if finished_bundles == 1 {
"updater signature"
} else {
"updater signatures"
@ -297,7 +308,7 @@ fn print_signed_updater_archive(output_paths: &[PathBuf]) -> crate::Result<()> {
tauri_utils::display_path(path)
)?;
}
log::info!( action = "Finished"; "{} {} at:\n{}", output_paths.len(), pluralised, printable_paths);
log::info!( action = "Finished"; "{finished_bundles} {pluralised} at:\n{printable_paths}");
}
Ok(())
}

View File

@ -24,7 +24,7 @@ use tauri_bundler::{
AppCategory, AppImageSettings, BundleBinary, BundleSettings, DebianSettings, DmgSettings,
MacOsSettings, PackageSettings, Position, RpmSettings, Size, UpdaterSettings, WindowsSettings,
};
use tauri_utils::config::{parse::is_configuration_file, DeepLinkProtocol};
use tauri_utils::config::{parse::is_configuration_file, DeepLinkProtocol, Updater};
use super::{AppSettings, DevProcess, ExitReason, Interface};
use crate::{
@ -807,11 +807,23 @@ impl AppSettings for RustAppSettings {
let arch64bits =
self.target_triple.starts_with("x86_64") || self.target_triple.starts_with("aarch64");
let updater_settings = if let Some(updater_plugin_config) = config.plugins.0.get("updater") {
let updater: UpdaterConfig = serde_json::from_value(updater_plugin_config.clone())?;
let updater_enabled = config.bundle.create_updater_artifacts != Updater::Bool(false);
let v1_compatible = matches!(config.bundle.create_updater_artifacts, Updater::String(_));
let updater_settings = if updater_enabled {
let updater: UpdaterConfig = serde_json::from_value(
config
.plugins
.0
.get("updater")
.ok_or_else(|| {
anyhow::anyhow!("failed to get updater configuration: plugins > updater doesn't exist")
})?
.clone(),
)?;
Some(UpdaterSettings {
v1_compatible,
pubkey: updater.pubkey,
msiexec_args: Some(updater.windows.install_mode.msiexec_args()),
msiexec_args: updater.windows.install_mode.msiexec_args(),
})
} else {
None

View File

@ -224,6 +224,36 @@ fn process_bundle(config: &mut Map<String, Value>) {
if let Some(license_file) = license_file {
bundle_config.insert("licenseFile".into(), license_file);
}
// Migrate updater from targets to update field
if let Some(targets) = bundle_config.get_mut("targets") {
let shuold_migrate = if let Some(targets) = targets.as_array_mut() {
// targets: ["updater", ...]
if let Some(index) = targets
.iter()
.position(|target| *target == serde_json::Value::String("updater".to_owned()))
{
targets.remove(index);
true
} else {
false
}
} else if let Some(target) = targets.as_str() {
// targets: "updater"
// targets: "all"
if target == "updater" {
bundle_config.remove("targets");
true
} else {
target == "all"
}
} else {
false
};
if shuold_migrate {
bundle_config.insert("createUpdaterArtifacts".to_owned(), "v1Compatible".into());
}
}
}
config.insert("bundle".into(), bundle_config);
@ -773,6 +803,48 @@ mod test {
);
}
#[test]
fn migrate_updater_target() {
let original = serde_json::json!({
"tauri": {
"bundle": {
"targets": ["nsis", "updater"]
}
}
});
let migrated = migrate(&original);
assert_eq!(migrated["bundle"]["createUpdaterArtifacts"], "v1Compatible");
assert_eq!(
migrated["bundle"]["targets"].as_array(),
Some(&vec!["nsis".into()])
);
let original = serde_json::json!({
"tauri": {
"bundle": {
"targets": "all"
}
}
});
let migrated = migrate(&original);
assert_eq!(migrated["bundle"]["createUpdaterArtifacts"], "v1Compatible");
assert_eq!(migrated["bundle"]["targets"], "all");
let original = serde_json::json!({
"tauri": {
"bundle": {
"targets": "updater"
}
}
});
let migrated = migrate(&original);
assert_eq!(migrated["bundle"]["createUpdaterArtifacts"], "v1Compatible");
assert_eq!(migrated["bundle"].get("targets"), None);
}
#[test]
fn migrate_csp_object() {
let original = serde_json::json!({