diff --git a/.changes/v1-migration-improvement.md b/.changes/v1-migration-improvement.md new file mode 100644 index 000000000..7ba12e936 --- /dev/null +++ b/.changes/v1-migration-improvement.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": patch:bug +"@tauri-apps/cli": patch:bug +--- + +Improve migration tooling by supporting TOML configs, handle nulls and properly check for updater migration. diff --git a/tooling/cli/src/migrate/migrations/v1/config.rs b/tooling/cli/src/migrate/migrations/v1/config.rs index 241f2441a..6722e67f2 100644 --- a/tooling/cli/src/migrate/migrations/v1/config.rs +++ b/tooling/cli/src/migrate/migrations/v1/config.rs @@ -21,7 +21,11 @@ pub fn migrate(tauri_dir: &Path) -> Result { tauri_utils_v1::config::parse::parse_value(tauri_dir.join("tauri.conf.json")) { let migrated = migrate_config(&mut config)?; - fs::write(&config_path, serde_json::to_string_pretty(&config)?)?; + if config_path.extension().map_or(false, |ext| ext == "toml") { + fs::write(&config_path, toml::to_string_pretty(&config)?)?; + } else { + fs::write(&config_path, serde_json::to_string_pretty(&config)?)?; + } let mut permissions: Vec = vec!["core:default"] .into_iter() @@ -101,8 +105,12 @@ fn migrate_config(config: &mut Value) -> Result { } // system tray - if let Some(tray) = tauri_config.remove("systemTray") { - tauri_config.insert("trayIcon".into(), tray); + if let Some((tray, key)) = tauri_config + .remove("systemTray") + .map(|v| (v, "trayIcon")) + .or_else(|| tauri_config.remove("system-tray").map(|v| (v, "tray-icon"))) + { + tauri_config.insert(key.into(), tray); } // cli @@ -114,9 +122,21 @@ fn migrate_config(config: &mut Value) -> Result { process_updater(tauri_config, &mut plugins, &mut migrated)?; } - config.insert("plugins".into(), plugins.into()); + process_bundle(config, &migrated); - process_bundle(config); + // if we have migrated the updater config, let's ensure createUpdaterArtifacts is set + if plugins.contains_key("updater") { + let bundle_config = config + .entry("bundle") + .or_insert_with(|| Value::Object(Default::default())) + .as_object_mut() + .unwrap(); + if !bundle_config.contains_key("createUpdaterArtifacts") { + bundle_config.insert("createUpdaterArtifacts".to_owned(), "v1Compatible".into()); + } + } + + config.insert("plugins".into(), plugins.into()); if let Some(tauri_config) = config.remove("tauri") { config.insert("app".into(), tauri_config); @@ -129,8 +149,16 @@ fn migrate_config(config: &mut Value) -> Result { fn process_package_metadata(config: &mut Map) { if let Some(mut package_config) = config.remove("package") { if let Some(package_config) = package_config.as_object_mut() { - if let Some(product_name) = package_config.remove("productName") { - config.insert("productName".into(), product_name); + if let Some((product_name, key)) = package_config + .remove("productName") + .map(|v| (v, "productName")) + .or_else(|| { + package_config + .remove("product-name") + .map(|v| (v, "product-name")) + }) + { + config.insert(key.into(), product_name); } if let Some(version) = package_config.remove("version") { @@ -152,25 +180,45 @@ fn process_package_metadata(config: &mut Map) { fn process_build(config: &mut Map) { if let Some(build_config) = config.get_mut("build").and_then(|b| b.as_object_mut()) { - if let Some(dist_dir) = build_config.remove("distDir") { - build_config.insert("frontendDist".into(), dist_dir); + if let Some((dist_dir, key)) = build_config + .remove("distDir") + .map(|v| (v, "frontendDist")) + .or_else(|| { + build_config + .remove("dist-dir") + .map(|v| (v, "frontend-dist")) + }) + { + build_config.insert(key.into(), dist_dir); } - if let Some(dev_path) = build_config.remove("devPath") { + if let Some((dev_path, key)) = build_config + .remove("devPath") + .map(|v| (v, "devUrl")) + .or_else(|| build_config.remove("dev-path").map(|v| (v, "dev-url"))) + { let is_url = url::Url::parse(dev_path.as_str().unwrap_or_default()).is_ok(); if is_url { - build_config.insert("devUrl".into(), dev_path); + build_config.insert(key.into(), dev_path); } } - if let Some(with_global_tauri) = build_config.remove("withGlobalTauri") { + if let Some((with_global_tauri, key)) = build_config + .remove("withGlobalTauri") + .map(|v| (v, "withGlobalTauri")) + .or_else(|| { + build_config + .remove("with-global-tauri") + .map(|v| (v, "with-global-tauri")) + }) + { config .get_mut("tauri") .and_then(|t| t.as_object_mut()) - .map(|t| t.insert("withGlobalTauri".into(), with_global_tauri)); + .map(|t| t.insert(key.into(), with_global_tauri)); } } } -fn process_bundle(config: &mut Map) { +fn process_bundle(config: &mut Map, migrated: &MigratedConfig) { let mut license_file = None; if let Some(mut bundle_config) = config @@ -196,19 +244,22 @@ fn process_bundle(config: &mut Map) { } // license file - if let Some(macos) = bundle_config.get_mut("macOS") { - if let Some(license) = macos.as_object_mut().unwrap().remove("license") { + if let Some(macos) = bundle_config + .get_mut("macOS") + .and_then(|v| v.as_object_mut()) + { + if let Some(license) = macos.remove("license") { license_file = Some(license); } } if let Some(windows) = bundle_config.get_mut("windows") { - if let Some(wix) = windows.get_mut("wix") { - if let Some(license_path) = wix.as_object_mut().unwrap().remove("license") { + if let Some(wix) = windows.get_mut("wix").and_then(|v| v.as_object_mut()) { + if let Some(license_path) = wix.remove("license") { license_file = Some(license_path); } } - if let Some(nsis) = windows.get_mut("nsis") { - if let Some(license_path) = nsis.as_object_mut().unwrap().remove("license") { + if let Some(nsis) = windows.get_mut("nsis").and_then(|v| v.as_object_mut()) { + if let Some(license_path) = nsis.remove("license") { license_file = Some(license_path); } } @@ -219,7 +270,7 @@ fn process_bundle(config: &mut Map) { // 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() { + let should_migrate = if let Some(targets) = targets.as_array_mut() { // targets: ["updater", ...] if let Some(index) = targets .iter() @@ -232,17 +283,19 @@ fn process_bundle(config: &mut Map) { } } else if let Some(target) = targets.as_str() { // targets: "updater" - // targets: "all" if target == "updater" { bundle_config.remove("targets"); true } else { - target == "all" + // note that target == "all" is the default from the v1 tauri CLI + // so we shouldn't bindly force updater bundles to be created + // instead we only migrate if the updater has been migrated + target == "all" && migrated.plugins.contains("updater") } } else { false }; - if shuold_migrate { + if should_migrate { bundle_config.insert("createUpdaterArtifacts".to_owned(), "v1Compatible".into()); } } @@ -287,6 +340,18 @@ fn process_security(security: &mut Map) -> Result<()> { security.insert("csp".into(), csp); } + + // dangerous_remote_domain_ipc_access no longer exists + if let Some(dangerous_remote_domain_ipc_access) = security + .remove("dangerousRemoteDomainIpcAccess") + .or_else(|| security.remove("dangerous-remote-domain-ipc-access")) + { + println!("dangerous remote domain IPC access config ({dangerous_remote_domain_ipc_access:?}) no longer exists, see documentation for capabilities and remote access: https://v2.tauri.app/security/capabilities/#remote-api-access") + } + security + .remove("dangerousUseHttpScheme") + .or_else(|| security.remove("dangerous-use-http-scheme")); + Ok(()) } @@ -797,8 +862,47 @@ mod test { ); } + #[test] + fn can_migrate_default_config() { + let original = serde_json::to_value(tauri_utils_v1::config::Config::default()).unwrap(); + migrate(&original); + } + + #[test] + fn can_migrate_api_example_config() { + let original = + serde_json::from_str(include_str!("./fixtures/api-example.tauri.conf.json")).unwrap(); + migrate(&original); + } + + #[test] + fn can_migrate_cli_template_config() { + let original = + serde_json::from_str(include_str!("./fixtures/cli-template.tauri.conf.json")).unwrap(); + migrate(&original); + } + #[test] fn migrate_updater_target() { + let original = serde_json::json!({}); + + let migrated = migrate(&original); + assert_eq!( + migrated["bundle"]["createUpdaterArtifacts"], + serde_json::Value::Null + ); + + let original = serde_json::json!({ + "tauri": { + "updater": { + "active": true + } + } + }); + + let migrated = migrate(&original); + assert_eq!(migrated["bundle"]["createUpdaterArtifacts"], "v1Compatible"); + let original = serde_json::json!({ "tauri": { "bundle": { @@ -814,6 +918,14 @@ mod test { Some(&vec!["nsis".into()]) ); + let original = + serde_json::from_str(include_str!("./fixtures/cli-template.tauri.conf.json")).unwrap(); + let migrated = migrate(&original); + assert_eq!( + migrated["bundle"]["createUpdaterArtifacts"], + serde_json::Value::Null + ); + let original = serde_json::json!({ "tauri": { "bundle": { @@ -822,6 +934,24 @@ mod test { } }); + let migrated = migrate(&original); + assert_eq!( + migrated["bundle"]["createUpdaterArtifacts"], + serde_json::Value::Null + ); + assert_eq!(migrated["bundle"]["targets"], "all"); + + let original = serde_json::json!({ + "tauri": { + "bundle": { + "targets": "all" + }, + "updater": { + "active": true + } + } + }); + let migrated = migrate(&original); assert_eq!(migrated["bundle"]["createUpdaterArtifacts"], "v1Compatible"); assert_eq!(migrated["bundle"]["targets"], "all"); diff --git a/tooling/cli/src/migrate/migrations/v1/fixtures/api-example.tauri.conf.json b/tooling/cli/src/migrate/migrations/v1/fixtures/api-example.tauri.conf.json new file mode 100644 index 000000000..d773dd7f9 --- /dev/null +++ b/tooling/cli/src/migrate/migrations/v1/fixtures/api-example.tauri.conf.json @@ -0,0 +1,136 @@ +{ + "$schema": "../../../core/tauri-config-schema/schema.json", + "build": { + "distDir": "../dist", + "devPath": "http://localhost:5173", + "beforeDevCommand": "yarn dev", + "beforeBuildCommand": "yarn build" + }, + "package": { + "productName": "Tauri API", + "version": "1.0.0" + }, + "tauri": { + "pattern": { + "use": "isolation", + "options": { + "dir": "../isolation-dist/" + } + }, + "macOSPrivateApi": true, + "cli": { + "description": "Tauri API example", + "args": [ + { + "short": "c", + "name": "config", + "takesValue": true, + "description": "Config path" + }, + { + "short": "t", + "name": "theme", + "takesValue": true, + "description": "App theme", + "possibleValues": ["light", "dark", "system"] + }, + { + "short": "v", + "name": "verbose", + "multipleOccurrences": true, + "description": "Verbosity level" + } + ], + "subcommands": { + "update": { + "description": "Updates the app", + "args": [ + { + "short": "b", + "name": "background", + "description": "Update in background" + } + ] + } + } + }, + "bundle": { + "active": true, + "identifier": "com.tauri.api", + "icon": [ + "../../.icons/32x32.png", + "../../.icons/128x128.png", + "../../.icons/128x128@2x.png", + "../../.icons/icon.icns", + "../../.icons/icon.ico" + ], + "windows": { + "wix": { + "language": { + "en-US": {}, + "pt-BR": { + "localePath": "locales/pt-BR.wxl" + } + } + } + } + }, + "updater": { + "active": true, + "dialog": false, + "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDE5QzMxNjYwNTM5OEUwNTgKUldSWTRKaFRZQmJER1h4d1ZMYVA3dnluSjdpN2RmMldJR09hUFFlZDY0SlFqckkvRUJhZDJVZXAK", + "endpoints": [ + "https://tauri-update-server.vercel.app/update/{{target}}/{{current_version}}" + ] + }, + "allowlist": { + "all": true, + "fs": { + "scope": { + "allow": ["$APPDATA/db/**", "$DOWNLOAD/**", "$RESOURCE/**"], + "deny": ["$APPDATA/db/*.stronghold"] + } + }, + "shell": { + "open": true, + "scope": [ + { + "name": "sh", + "cmd": "sh", + "args": ["-c", { "validator": "\\S+" }] + }, + { + "name": "cmd", + "cmd": "cmd", + "args": ["/C", { "validator": "\\S+" }] + } + ] + }, + "protocol": { + "asset": true, + "assetScope": { + "allow": ["$APPDATA/db/**", "$RESOURCE/**"], + "deny": ["$APPDATA/db/*.stronghold"] + } + }, + "http": { + "scope": ["http://localhost:3003"] + } + }, + "windows": [], + "security": { + "csp": { + "default-src": "'self' customprotocol: asset:", + "font-src": ["https://fonts.gstatic.com"], + "img-src": "'self' asset: https://asset.localhost blob: data:", + "style-src": "'unsafe-inline' 'self' https://fonts.googleapis.com" + }, + "freezePrototype": true + }, + "systemTray": { + "iconPath": "../../.icons/tray_icon_with_transparency.png", + "iconAsTemplate": true, + "menuOnLeftClick": false + } + } +} \ No newline at end of file diff --git a/tooling/cli/src/migrate/migrations/v1/fixtures/cli-template.tauri.conf.json b/tooling/cli/src/migrate/migrations/v1/fixtures/cli-template.tauri.conf.json new file mode 100644 index 000000000..02f77f622 --- /dev/null +++ b/tooling/cli/src/migrate/migrations/v1/fixtures/cli-template.tauri.conf.json @@ -0,0 +1,65 @@ +{ + "package": { + "productName": "{{ app_name }}", + "version": "0.1.0" + }, + "build": { + "distDir": "{{ dist_dir }}", + "devPath": "{{ dev_path }}", + "beforeDevCommand": "{{ before_dev_command }}", + "beforeBuildCommand": "{{ before_build_command }}" + }, + "tauri": { + "bundle": { + "active": true, + "targets": "all", + "identifier": "com.tauri.dev", + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ], + "resources": [], + "externalBin": [], + "copyright": "", + "category": "DeveloperTool", + "shortDescription": "", + "longDescription": "", + "deb": { + "depends": [] + }, + "macOS": { + "frameworks": [], + "exceptionDomain": "", + "signingIdentity": null, + "providerShortName": null, + "entitlements": null + }, + "windows": { + "certificateThumbprint": null, + "digestAlgorithm": "sha256", + "timestampUrl": "" + } + }, + "updater": { + "active": false + }, + "allowlist": { + "all": false + }, + "windows": [ + { + "title": "{{ window_title }}", + "width": 800, + "height": 600, + "resizable": true, + "fullscreen": false + } + ], + "security": { + "csp": null + } + } +} \ No newline at end of file