diff --git a/.changes/bundler-wix-version.md b/.changes/bundler-wix-version.md new file mode 100644 index 000000000000..a9ae4e1c2e1b --- /dev/null +++ b/.changes/bundler-wix-version.md @@ -0,0 +1,6 @@ +--- +"tauri-bundler": "patch:feat" +--- + +Add `bundler > windows > wix > version` to manually specify a wix-compatible version. + diff --git a/crates/tauri-bundler/src/bundle/settings.rs b/crates/tauri-bundler/src/bundle/settings.rs index ec90b2404770..d9acaed5fb3b 100644 --- a/crates/tauri-bundler/src/bundle/settings.rs +++ b/crates/tauri-bundler/src/bundle/settings.rs @@ -351,6 +351,15 @@ impl Default for WixLanguage { /// Settings specific to the WiX implementation. #[derive(Clone, Debug, Default)] pub struct WixSettings { + /// MSI installer version in the format `major.minor.patch.build` (build is optional). + /// + /// Because a valid version is required for MSI installer, it will be derived from [`PackageSettings::version`] if this field is not set. + /// + /// The first field is the major version and has a maximum value of 255. The second field is the minor version and has a maximum value of 255. + /// The third and fourth fields have a maximum value of 65,535. + /// + /// See for more info. + pub version: Option, /// A GUID upgrade code for MSI installer. This code **_must stay the same across all of your updates_**, /// otherwise, Windows will treat your update as a different app and your users will have duplicate versions of your app. /// diff --git a/crates/tauri-bundler/src/bundle/windows/msi/mod.rs b/crates/tauri-bundler/src/bundle/windows/msi/mod.rs index db28657e2ccc..f9daaf25718e 100644 --- a/crates/tauri-bundler/src/bundle/windows/msi/mod.rs +++ b/crates/tauri-bundler/src/bundle/windows/msi/mod.rs @@ -282,19 +282,37 @@ fn clear_env_for_wix(cmd: &mut Command) { } } -// WiX requires versions to be numeric only in a `major.minor.patch.build` format -pub fn convert_version(version_str: &str) -> anyhow::Result { - let version = semver::Version::parse(version_str).context("invalid app version")?; - if version.major > 255 { +fn validate_wix_version(version_str: &str) -> anyhow::Result<()> { + let components = version_str + .split('.') + .flat_map(|c| c.parse::().ok()) + .collect::>(); + + anyhow::ensure!( + components.len() >= 3, + "app wix version should be in the format major.minor.patch.build (build is optional)" + ); + + if components[0] > 255 { bail!("app version major number cannot be greater than 255"); } - if version.minor > 255 { + if components[1] > 255 { bail!("app version minor number cannot be greater than 255"); } - if version.patch > 65535 { + if components[2] > 65535 { bail!("app version patch number cannot be greater than 65535"); } + if components.len() == 4 && components[3] > 65535 { + bail!("app version build number cannot be greater than 65535"); + } + + Ok(()) +} + +// WiX requires versions to be numeric only in a `major.minor.patch.build` format +fn convert_version(version_str: &str) -> anyhow::Result { + let version = semver::Version::parse(version_str).context("invalid app version")?; if !version.build.is_empty() { let build = version.build.parse::(); if build.map(|b| b <= 65535).unwrap_or_default() { @@ -433,7 +451,18 @@ pub fn build_wix_app_installer( } }; - let app_version = convert_version(settings.version_string())?; + let app_version = if let Some(version) = settings + .windows() + .wix + .as_ref() + .and_then(|wix| wix.version.clone()) + { + version + } else { + convert_version(settings.version_string())? + }; + + validate_wix_version(&app_version)?; // target only supports x64. log::info!("Target: {}", arch); @@ -1056,3 +1085,35 @@ fn generate_resource_data(settings: &Settings) -> crate::Result { Ok(resources) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn validates_wix_version() { + assert!(validate_wix_version("1.1.1").is_ok()); + assert!(validate_wix_version("1.1.1.1").is_ok()); + assert!(validate_wix_version("255.1.1.1").is_ok()); + assert!(validate_wix_version("1.255.1.1").is_ok()); + assert!(validate_wix_version("1.1.65535.1").is_ok()); + assert!(validate_wix_version("1.1.1.65535").is_ok()); + + assert!(validate_wix_version("256.1.1.1").is_err()); + assert!(validate_wix_version("1.256.1.1").is_err()); + assert!(validate_wix_version("1.1.65536.1").is_err()); + assert!(validate_wix_version("1.1.1.65536").is_err()); + } + + #[test] + fn converts_version_to_wix() { + assert_eq!(convert_version("1.1.2").unwrap(), "1.1.2"); + assert_eq!(convert_version("1.1.2-4").unwrap(), "1.1.2.4"); + assert_eq!(convert_version("1.1.2-65535").unwrap(), "1.1.2.65535"); + assert_eq!(convert_version("1.1.2+2").unwrap(), "1.1.2.2"); + + assert!(convert_version("1.1.2-alpha").is_err()); + assert!(convert_version("1.1.2-alpha.4").is_err()); + assert!(convert_version("1.1.2+asd.3").is_err()); + } +} diff --git a/crates/tauri-cli/config.schema.json b/crates/tauri-cli/config.schema.json index 7acb2054a938..9bc3f4577bd6 100644 --- a/crates/tauri-cli/config.schema.json +++ b/crates/tauri-cli/config.schema.json @@ -2170,6 +2170,13 @@ "description": "Configuration for the MSI bundle using WiX.\n\n See more: ", "type": "object", "properties": { + "version": { + "description": "MSI installer version in the format `major.minor.patch.build` (build is optional).\n\n Because a valid version is required for MSI installer, it will be derived from [`Config::version`] if this field is not set.\n\n The first field is the major version and has a maximum value of 255. The second field is the minor version and has a maximum value of 255.\n The third and foruth fields have a maximum value of 65,535.\n\n See for more info.", + "type": [ + "string", + "null" + ] + }, "upgradeCode": { "description": "A GUID upgrade code for MSI installer. This code **_must stay the same across all of your updates_**,\n otherwise, Windows will treat your update as a different app and your users will have duplicate versions of your app.\n\n By default, tauri generates this code by generating a Uuid v5 using the string `.exe.app.x64` in the DNS namespace.\n You can use Tauri's CLI to generate and print this code for you, run `tauri inspect wix-upgrade-code`.\n\n It is recommended that you set this value in your tauri config file to avoid accidental changes in your upgrade code\n whenever you want to change your product name.", "type": [ diff --git a/crates/tauri-cli/src/helpers/config.rs b/crates/tauri-cli/src/helpers/config.rs index 06fc9be8935c..2ae0bf030b07 100644 --- a/crates/tauri-cli/src/helpers/config.rs +++ b/crates/tauri-cli/src/helpers/config.rs @@ -62,6 +62,7 @@ pub type ConfigHandle = Arc>>; pub fn wix_settings(config: WixConfig) -> tauri_bundler::WixSettings { tauri_bundler::WixSettings { + version: config.version, upgrade_code: config.upgrade_code, language: tauri_bundler::WixLanguage(match config.language { WixLanguage::One(lang) => vec![(lang, Default::default())], diff --git a/crates/tauri-schema-generator/schemas/config.schema.json b/crates/tauri-schema-generator/schemas/config.schema.json index 7acb2054a938..9bc3f4577bd6 100644 --- a/crates/tauri-schema-generator/schemas/config.schema.json +++ b/crates/tauri-schema-generator/schemas/config.schema.json @@ -2170,6 +2170,13 @@ "description": "Configuration for the MSI bundle using WiX.\n\n See more: ", "type": "object", "properties": { + "version": { + "description": "MSI installer version in the format `major.minor.patch.build` (build is optional).\n\n Because a valid version is required for MSI installer, it will be derived from [`Config::version`] if this field is not set.\n\n The first field is the major version and has a maximum value of 255. The second field is the minor version and has a maximum value of 255.\n The third and foruth fields have a maximum value of 65,535.\n\n See for more info.", + "type": [ + "string", + "null" + ] + }, "upgradeCode": { "description": "A GUID upgrade code for MSI installer. This code **_must stay the same across all of your updates_**,\n otherwise, Windows will treat your update as a different app and your users will have duplicate versions of your app.\n\n By default, tauri generates this code by generating a Uuid v5 using the string `.exe.app.x64` in the DNS namespace.\n You can use Tauri's CLI to generate and print this code for you, run `tauri inspect wix-upgrade-code`.\n\n It is recommended that you set this value in your tauri config file to avoid accidental changes in your upgrade code\n whenever you want to change your product name.", "type": [ diff --git a/crates/tauri-utils/src/config.rs b/crates/tauri-utils/src/config.rs index 36440c9e7afd..10f926f24032 100644 --- a/crates/tauri-utils/src/config.rs +++ b/crates/tauri-utils/src/config.rs @@ -658,6 +658,15 @@ impl Default for WixLanguage { #[cfg_attr(feature = "schema", derive(JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct WixConfig { + /// MSI installer version in the format `major.minor.patch.build` (build is optional). + /// + /// Because a valid version is required for MSI installer, it will be derived from [`Config::version`] if this field is not set. + /// + /// The first field is the major version and has a maximum value of 255. The second field is the minor version and has a maximum value of 255. + /// The third and foruth fields have a maximum value of 65,535. + /// + /// See for more info. + pub version: Option, /// A GUID upgrade code for MSI installer. This code **_must stay the same across all of your updates_**, /// otherwise, Windows will treat your update as a different app and your users will have duplicate versions of your app. ///