use crate::{error::Context, ErrorExt, Result};
use serde_json::{Map, Value};
use tauri_utils::acl::{
capability::{Capability, PermissionEntry},
Scopes, Value as AclValue,
};
use std::{
collections::{BTreeMap, HashSet},
fs,
path::Path,
};
pub fn migrate(tauri_dir: &Path) -> Result<MigratedConfig> {
if let Ok((mut config, config_path)) =
tauri_utils::config_v1::parse::parse_value(tauri_dir.join("tauri.conf.json"))
{
let migrated = migrate_config(&mut config)?;
if config_path.extension().is_some_and(|ext| ext == "toml") {
fs::write(
&config_path,
toml::to_string_pretty(&config).context("failed to serialize config")?,
)
.fs_context("failed to write config", config_path.clone())?;
} else {
fs::write(
&config_path,
serde_json::to_string_pretty(&config).context("failed to serialize config")?,
)
.fs_context("failed to write config", config_path.clone())?;
}
let mut permissions: Vec<PermissionEntry> = vec!["core:default"]
.into_iter()
.map(|p| PermissionEntry::PermissionRef(p.to_string().try_into().unwrap()))
.collect();
permissions.extend(migrated.permissions.clone());
let capabilities_path = config_path.parent().unwrap().join("capabilities");
fs::create_dir_all(&capabilities_path).fs_context(
"failed to create capabilities directory",
capabilities_path.clone(),
)?;
fs::write(
capabilities_path.join("migrated.json"),
serde_json::to_string_pretty(&Capability {
identifier: "migrated".to_string(),
description: "permissions that were migrated from v1".into(),
local: true,
remote: None,
windows: vec!["main".into()],
webviews: vec![],
permissions,
platforms: None,
})
.context("failed to serialize capabilities")?,
)
.fs_context(
"failed to write capabilities",
capabilities_path.join("migrated.json"),
)?;
return Ok(migrated);
}
Ok(Default::default())
}
#[derive(Default)]
pub struct MigratedConfig {
pub permissions: Vec<PermissionEntry>,
pub plugins: HashSet<String>,
}
fn migrate_config(config: &mut Value) -> Result<MigratedConfig> {
let mut migrated = MigratedConfig {
permissions: Vec::new(),
plugins: HashSet::new(),
};
if let Some(config) = config.as_object_mut() {
process_package_metadata(config);
process_build(config);
let mut plugins = config
.entry("plugins")
.or_insert_with(|| Value::Object(Default::default()))
.as_object_mut()
.unwrap()
.clone();
if let Some(tauri_config) = config.get_mut("tauri").and_then(|c| c.as_object_mut()) {
if let Some(allowlist) = tauri_config.remove("allowlist") {
let allowlist = process_allowlist(tauri_config, allowlist)?;
let permissions = allowlist_to_permissions(allowlist);
migrated.plugins = plugins_from_permissions(&permissions);
migrated.permissions = permissions;
}
let dangerous_use_http = tauri_config
.get("security")
.and_then(|w| w.as_object())
.and_then(|w| {
w.get("dangerousUseHttpScheme")
.or_else(|| w.get("dangerous-use-http-scheme"))
})
.and_then(|v| v.as_bool())
.unwrap_or_default();
if let Some(windows) = tauri_config
.get_mut("windows")
.and_then(|w| w.as_array_mut())
{
for window in windows {
if let Some(window) = window.as_object_mut() {
window.insert("useHttpsScheme".to_string(), (!dangerous_use_http).into());
}
}
}
if let Some(security) = tauri_config
.get_mut("security")
.and_then(|c| c.as_object_mut())
{
process_security(security)?;
}
if let Some(pattern) = tauri_config.remove("pattern") {
tauri_config
.entry("security")
.or_insert_with(|| Value::Object(Default::default()))
.as_object_mut()
.map(|s| s.insert("pattern".into(), pattern));
}
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);
}
if let Some(cli) = tauri_config.remove("cli") {
process_cli(&mut plugins, cli)?;
}
process_updater(tauri_config, &mut plugins, &mut migrated)?;
}
process_bundle(config, &migrated);
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);
}
}
Ok(migrated)
}
fn process_package_metadata(config: &mut Map<String, Value>) {
if let Some(mut package_config) = config.remove("package") {
if let Some(package_config) = package_config.as_object_mut() {
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.clone());
config.insert("mainBinaryName".into(), product_name);
}
if let Some(version) = package_config.remove("version") {
config.insert("version".into(), version);
}
}
}
if let Some(bundle_config) = config
.get_mut("tauri")
.and_then(|t| t.get_mut("bundle"))
.and_then(|b| b.as_object_mut())
{
if let Some(identifier) = bundle_config.remove("identifier") {
config.insert("identifier".into(), identifier);
}
}
}
fn process_build(config: &mut Map<String, Value>) {
if let Some(build_config) = config.get_mut("build").and_then(|b| b.as_object_mut()) {
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, 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(key.into(), dev_path);
}
}
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(key.into(), with_global_tauri));
}
}
}
fn process_bundle(config: &mut Map<String, Value>, migrated: &MigratedConfig) {
let mut license_file = None;
if let Some(mut bundle_config) = config
.get_mut("tauri")
.and_then(|b| b.as_object_mut())
.and_then(|t| t.remove("bundle"))
{
if let Some(bundle_config) = bundle_config.as_object_mut() {
if let Some(deb) = bundle_config.remove("deb") {
bundle_config
.entry("linux")
.or_insert_with(|| Value::Object(Default::default()))
.as_object_mut()
.map(|l| l.insert("deb".into(), deb));
}
if let Some(appimage) = bundle_config.remove("appimage") {
bundle_config
.entry("linux")
.or_insert_with(|| Value::Object(Default::default()))
.as_object_mut()
.map(|l| l.insert("appimage".into(), appimage));
}
if let Some(rpm) = bundle_config.remove("rpm") {
bundle_config
.entry("linux")
.or_insert_with(|| Value::Object(Default::default()))
.as_object_mut()
.map(|l| l.insert("rpm".into(), rpm));
}
if let Some(dmg) = bundle_config.remove("dmg") {
bundle_config
.entry("macOS")
.or_insert_with(|| Value::Object(Default::default()))
.as_object_mut()
.map(|l| l.insert("dmg".into(), dmg));
}
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(windows) = windows.as_object_mut() {
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").and_then(|v| v.as_object_mut()) {
if let Some(license_path) = nsis.remove("license") {
license_file = Some(license_path);
}
}
if let Some((fixed_runtime_path, key)) = windows
.remove("webviewFixedRuntimePath")
.map(|v| (v, "webviewInstallMode"))
.or_else(|| {
windows
.remove("webview-fixed-runtime-path")
.map(|v| (v, "webview-install-mode"))
})
{
if !fixed_runtime_path.is_null() {
windows.insert(
key.into(),
serde_json::json!({
"type": "fixedRuntime",
"path": fixed_runtime_path
}),
);
}
}
}
}
if let Some(license_file) = license_file {
bundle_config.insert("licenseFile".into(), license_file);
}
if let Some(targets) = bundle_config.get_mut("targets") {
let should_migrate = if let Some(targets) = targets.as_array_mut() {
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() {
if target == "updater" {
bundle_config.remove("targets");
true
} else {
target == "all" && migrated.plugins.contains("updater")
}
} else {
false
};
if should_migrate {
bundle_config.insert("createUpdaterArtifacts".to_owned(), "v1Compatible".into());
}
}
}
config.insert("bundle".into(), bundle_config);
}
}
fn process_security(security: &mut Map<String, Value>) -> Result<()> {
if let Some(csp_value) = security.remove("csp") {
let csp = if csp_value.is_null() {
csp_value
} else {
let mut csp: tauri_utils::config_v1::Csp =
serde_json::from_value(csp_value).context("failed to deserialize CSP")?;
match &mut csp {
tauri_utils::config_v1::Csp::Policy(csp) => {
if csp.contains("connect-src") {
*csp = csp.replace("connect-src", "connect-src ipc: http://ipc.localhost");
} else {
*csp = format!("{csp}; connect-src ipc: http://ipc.localhost");
}
}
tauri_utils::config_v1::Csp::DirectiveMap(csp) => {
if let Some(connect_src) = csp.get_mut("connect-src") {
if !connect_src.contains("ipc: http://ipc.localhost") {
connect_src.push("ipc: http://ipc.localhost");
}
} else {
csp.insert(
"connect-src".into(),
tauri_utils::config_v1::CspDirectiveSources::List(vec![
"ipc: http://ipc.localhost".to_string()
]),
);
}
}
}
serde_json::to_value(csp).context("failed to serialize CSP")?
};
security.insert("csp".into(), csp);
}
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(())
}
fn process_allowlist(
tauri_config: &mut Map<String, Value>,
allowlist: Value,
) -> Result<tauri_utils::config_v1::AllowlistConfig> {
let allowlist: tauri_utils::config_v1::AllowlistConfig =
serde_json::from_value(allowlist).context("failed to deserialize allowlist")?;
if allowlist.protocol.asset_scope != Default::default() {
let security = tauri_config
.entry("security")
.or_insert_with(|| Value::Object(Default::default()))
.as_object_mut()
.unwrap();
let mut asset_protocol = Map::new();
asset_protocol.insert(
"scope".into(),
serde_json::to_value(allowlist.protocol.asset_scope.clone())
.context("failed to serialize asset scope")?,
);
if allowlist.protocol.asset {
asset_protocol.insert("enable".into(), true.into());
}
security.insert("assetProtocol".into(), asset_protocol.into());
}
Ok(allowlist)
}
fn allowlist_to_permissions(
allowlist: tauri_utils::config_v1::AllowlistConfig,
) -> Vec<PermissionEntry> {
macro_rules! permissions {
($allowlist: ident, $permissions_list: ident, $object: ident, $field: ident => $associated_permission: expr) => {{
if $allowlist.all || $allowlist.$object.all || $allowlist.$object.$field {
$permissions_list.push(PermissionEntry::PermissionRef(
$associated_permission.to_string().try_into().unwrap(),
));
true
} else {
false
}
}};
}
let mut permissions = Vec::new();
permissions!(allowlist, permissions, fs, read_file => "fs:allow-read-file");
permissions!(allowlist, permissions, fs, write_file => "fs:allow-write-file");
permissions!(allowlist, permissions, fs, read_dir => "fs:allow-read-dir");
permissions!(allowlist, permissions, fs, copy_file => "fs:allow-copy-file");
permissions!(allowlist, permissions, fs, create_dir => "fs:allow-mkdir");
permissions!(allowlist, permissions, fs, remove_dir => "fs:allow-remove");
permissions!(allowlist, permissions, fs, remove_file => "fs:allow-remove");
permissions!(allowlist, permissions, fs, rename_file => "fs:allow-rename");
permissions!(allowlist, permissions, fs, exists => "fs:allow-exists");
let (fs_allowed, fs_denied) = match allowlist.fs.scope {
tauri_utils::config_v1::FsAllowlistScope::AllowedPaths(paths) => (paths, Vec::new()),
tauri_utils::config_v1::FsAllowlistScope::Scope { allow, deny, .. } => (allow, deny),
};
if !(fs_allowed.is_empty() && fs_denied.is_empty()) {
let fs_allowed = fs_allowed
.into_iter()
.map(|p| AclValue::String(p.to_string_lossy().into()))
.collect::<Vec<_>>();
let fs_denied = fs_denied
.into_iter()
.map(|p| AclValue::String(p.to_string_lossy().into()))
.collect::<Vec<_>>();
permissions.push(PermissionEntry::ExtendedPermission {
identifier: "fs:scope".to_string().try_into().unwrap(),
scope: Scopes {
allow: if fs_allowed.is_empty() {
None
} else {
Some(fs_allowed)
},
deny: if fs_denied.is_empty() {
None
} else {
Some(fs_denied)
},
},
});
}
permissions!(allowlist, permissions, window, create => "core:window:allow-create");
permissions!(allowlist, permissions, window, center => "core:window:allow-center");
permissions!(allowlist, permissions, window, request_user_attention => "core:window:allow-request-user-attention");
permissions!(allowlist, permissions, window, set_resizable => "core:window:allow-set-resizable");
permissions!(allowlist, permissions, window, set_maximizable => "core:window:allow-set-maximizable");
permissions!(allowlist, permissions, window, set_minimizable => "core:window:allow-set-minimizable");
permissions!(allowlist, permissions, window, set_closable => "core:window:allow-set-closable");
permissions!(allowlist, permissions, window, set_title => "core:window:allow-set-title");
permissions!(allowlist, permissions, window, maximize => "core:window:allow-maximize");
permissions!(allowlist, permissions, window, unmaximize => "core:window:allow-unmaximize");
permissions!(allowlist, permissions, window, minimize => "core:window:allow-minimize");
permissions!(allowlist, permissions, window, unminimize => "core:window:allow-unminimize");
permissions!(allowlist, permissions, window, show => "core:window:allow-show");
permissions!(allowlist, permissions, window, hide => "core:window:allow-hide");
permissions!(allowlist, permissions, window, close => "core:window:allow-close");
permissions!(allowlist, permissions, window, set_decorations => "core:window:allow-set-decorations");
permissions!(allowlist, permissions, window, set_always_on_top => "core:window:allow-set-always-on-top");
permissions!(allowlist, permissions, window, set_content_protected => "core:window:allow-set-content-protected");
permissions!(allowlist, permissions, window, set_size => "core:window:allow-set-size");
permissions!(allowlist, permissions, window, set_min_size => "core:window:allow-set-min-size");
permissions!(allowlist, permissions, window, set_max_size => "core:window:allow-set-max-size");
permissions!(allowlist, permissions, window, set_position => "core:window:allow-set-position");
permissions!(allowlist, permissions, window, set_fullscreen => "core:window:allow-set-fullscreen");
permissions!(allowlist, permissions, window, set_focus => "core:window:allow-set-focus");
permissions!(allowlist, permissions, window, set_icon => "core:window:allow-set-icon");
permissions!(allowlist, permissions, window, set_skip_taskbar => "core:window:allow-set-skip-taskbar");
permissions!(allowlist, permissions, window, set_cursor_grab => "core:window:allow-set-cursor-grab");
permissions!(allowlist, permissions, window, set_cursor_visible => "core:window:allow-set-cursor-visible");
permissions!(allowlist, permissions, window, set_cursor_icon => "core:window:allow-set-cursor-icon");
permissions!(allowlist, permissions, window, set_cursor_position => "core:window:allow-set-cursor-position");
permissions!(allowlist, permissions, window, set_ignore_cursor_events => "core:window:allow-set-ignore-cursor-events");
permissions!(allowlist, permissions, window, start_dragging => "core:window:allow-start-dragging");
permissions!(allowlist, permissions, window, print => "core:webview:allow-print");
if allowlist.shell.scope.0.is_empty() {
let added = permissions!(allowlist, permissions, shell, execute => "shell:allow-execute");
if !added {
permissions!(allowlist, permissions, shell, sidecar => "shell:allow-execute");
}
} else {
let allowed = allowlist
.shell
.scope
.0
.into_iter()
.map(|p| serde_json::to_value(p).unwrap().into())
.collect::<Vec<_>>();
permissions.push(PermissionEntry::ExtendedPermission {
identifier: "shell:allow-execute".to_string().try_into().unwrap(),
scope: Scopes {
allow: Some(allowed),
deny: None,
},
});
}
if allowlist.all
|| allowlist.shell.all
|| !matches!(
allowlist.shell.open,
tauri_utils::config_v1::ShellAllowlistOpen::Flag(false)
)
{
permissions.push(PermissionEntry::PermissionRef(
"shell:allow-open".to_string().try_into().unwrap(),
));
}
permissions!(allowlist, permissions, dialog, open => "dialog:allow-open");
permissions!(allowlist, permissions, dialog, save => "dialog:allow-save");
permissions!(allowlist, permissions, dialog, message => "dialog:allow-message");
permissions!(allowlist, permissions, dialog, ask => "dialog:allow-ask");
permissions!(allowlist, permissions, dialog, confirm => "dialog:allow-confirm");
if allowlist.http.scope.0.is_empty() {
permissions!(allowlist, permissions, http, request => "http:default");
} else {
let allowed = allowlist
.http
.scope
.0
.into_iter()
.map(|p| {
let mut map = BTreeMap::new();
map.insert("url".to_string(), AclValue::String(p.to_string()));
AclValue::Map(map)
})
.collect::<Vec<_>>();
permissions.push(PermissionEntry::ExtendedPermission {
identifier: "http:default".to_string().try_into().unwrap(),
scope: Scopes {
allow: Some(allowed),
deny: None,
},
});
}
permissions!(allowlist, permissions, notification, all => "notification:default");
permissions!(allowlist, permissions, global_shortcut, all => "global-shortcut:allow-is-registered");
permissions!(allowlist, permissions, global_shortcut, all => "global-shortcut:allow-register");
permissions!(allowlist, permissions, global_shortcut, all => "global-shortcut:allow-register-all");
permissions!(allowlist, permissions, global_shortcut, all => "global-shortcut:allow-unregister");
permissions!(allowlist, permissions, global_shortcut, all => "global-shortcut:allow-unregister-all");
permissions!(allowlist, permissions, os, all => "os:allow-platform");
permissions!(allowlist, permissions, os, all => "os:allow-version");
permissions!(allowlist, permissions, os, all => "os:allow-os-type");
permissions!(allowlist, permissions, os, all => "os:allow-family");
permissions!(allowlist, permissions, os, all => "os:allow-arch");
permissions!(allowlist, permissions, os, all => "os:allow-exe-extension");
permissions!(allowlist, permissions, os, all => "os:allow-locale");
permissions!(allowlist, permissions, os, all => "os:allow-hostname");
permissions!(allowlist, permissions, process, relaunch => "process:allow-restart");
permissions!(allowlist, permissions, process, exit => "process:allow-exit");
permissions!(allowlist, permissions, clipboard, read_text => "clipboard-manager:allow-read-text");
permissions!(allowlist, permissions, clipboard, write_text => "clipboard-manager:allow-write-text");
permissions!(allowlist, permissions, app, show => "core:app:allow-app-show");
permissions!(allowlist, permissions, app, hide => "core:app:allow-app-hide");
permissions
}
fn process_cli(plugins: &mut Map<String, Value>, cli: Value) -> Result<()> {
if let Some(cli) = cli.as_object() {
plugins.insert(
"cli".into(),
serde_json::to_value(cli).context("failed to serialize CLI")?,
);
}
Ok(())
}
fn process_updater(
tauri_config: &mut Map<String, Value>,
plugins: &mut Map<String, Value>,
migrated: &mut MigratedConfig,
) -> Result<()> {
if let Some(mut updater) = tauri_config.remove("updater") {
if let Some(updater) = updater.as_object_mut() {
updater.remove("dialog");
if updater
.remove("active")
.and_then(|a| a.as_bool())
.unwrap_or_default()
|| updater.get("pubkey").is_some()
{
plugins.insert(
"updater".into(),
serde_json::to_value(updater).context("failed to serialize updater")?,
);
migrated.plugins.insert("updater".to_string());
}
}
}
Ok(())
}
const KNOWN_PLUGINS: &[&str] = &[
"fs",
"shell",
"dialog",
"http",
"notification",
"global-shortcut",
"os",
"process",
"clipboard-manager",
];
fn plugins_from_permissions(permissions: &Vec<PermissionEntry>) -> HashSet<String> {
let mut plugins = HashSet::new();
for permission in permissions {
let permission = permission.identifier().get();
for plugin in KNOWN_PLUGINS {
if permission.starts_with(plugin) {
plugins.insert(plugin.to_string());
break;
}
}
}
plugins
}
#[cfg(test)]
mod test {
fn migrate(original: &serde_json::Value) -> serde_json::Value {
let mut migrated = original.clone();
super::migrate_config(&mut migrated).expect("failed to migrate config");
if original.get("$schema").is_some() {
if let Some(map) = migrated.as_object_mut() {
map.insert(
"$schema".to_string(),
serde_json::Value::String("https://schema.tauri.app/config/2".to_string()),
);
}
}
if original
.get("tauri")
.and_then(|v| v.get("bundle"))
.and_then(|v| v.get("identifier"))
.is_none()
{
if let Some(map) = migrated.as_object_mut() {
map.insert(
"identifier".to_string(),
serde_json::Value::String("com.tauri.test-injected".to_string()),
);
}
}
if let Err(e) = serde_json::from_value::<tauri_utils::config::Config>(migrated.clone()) {
panic!("migrated config is not valid: {e}");
}
migrated
}
#[test]
fn migrate_full() {
let original = serde_json::json!({
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
"build": {
"distDir": "../dist",
"devPath": "http://localhost:1240",
"withGlobalTauri": true
},
"package": {
"productName": "Tauri app",
"version": "0.0.0"
},
"tauri": {
"bundle": {
"identifier": "com.tauri.test",
"deb": {
"depends": ["dep1"]
},
"appimage": {
"bundleMediaFramework": true
},
"macOS": {
"license": "license-file.txt"
},
"windows": {
"wix": {
"license": "license-file.txt"
},
"nsis": {
"license": "license-file.txt"
},
},
},
"cli": {
"description": "Tauri TEST"
},
"updater": {
"active": true,
"dialog": false,
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDE5QzMxNjYwNTM5OEUwNTgKUldSWTRKaFRZQmJER1h4d1ZMYVA3dnluSjdpN2RmMldJR09hUFFlZDY0SlFqckkvRUJhZDJVZXAK",
"endpoints": [
"https://tauri-update-server.vercel.app/update/{{target}}/{{current_version}}"
],
"windows": {
"installerArgs": ["arg1"],
"installMode": "passive"
}
},
"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+" }],
"sidecar": false
},
{
"name": "cmd",
"cmd": "cmd",
"args": ["/C", { "validator": "\\S+" }],
"sidecar": false
}
]
},
"protocol": {
"asset": true,
"assetScope": {
"allow": ["$APPDATA/db/**", "$RESOURCE/**"],
"deny": ["$APPDATA/db/*.stronghold"]
}
},
"http": {
"scope": ["http://localhost:3003/"]
}
},
"pattern": { "use": "brownfield" },
"security": {
"csp": "default-src 'self' tauri:"
},
"windows": [{}]
}
});
let migrated = migrate(&original);
assert_eq!(migrated["$schema"], "https://schema.tauri.app/config/2");
assert_eq!(
migrated["plugins"]["updater"]["endpoints"],
original["tauri"]["updater"]["endpoints"]
);
assert_eq!(
migrated["plugins"]["updater"]["pubkey"],
original["tauri"]["updater"]["pubkey"]
);
assert_eq!(
migrated["plugins"]["updater"]["windows"]["installMode"],
original["tauri"]["updater"]["windows"]["installMode"]
);
assert_eq!(
migrated["plugins"]["updater"]["windows"]["installerArgs"],
original["tauri"]["updater"]["windows"]["installerArgs"]
);
assert_eq!(migrated["plugins"]["cli"], original["tauri"]["cli"]);
assert_eq!(
migrated["app"]["security"]["assetProtocol"]["enable"],
original["tauri"]["allowlist"]["protocol"]["asset"]
);
assert_eq!(
migrated["app"]["security"]["assetProtocol"]["scope"]["allow"],
original["tauri"]["allowlist"]["protocol"]["assetScope"]["allow"]
);
assert_eq!(
migrated["app"]["security"]["assetProtocol"]["scope"]["deny"],
original["tauri"]["allowlist"]["protocol"]["assetScope"]["deny"]
);
assert_eq!(
migrated["app"]["security"]["csp"],
format!(
"{}; connect-src ipc: http://ipc.localhost",
original["tauri"]["security"]["csp"].as_str().unwrap()
)
);
assert_eq!(
migrated["app"]["security"]["pattern"],
original["tauri"]["pattern"]
);
assert_eq!(
migrated["bundle"]["licenseFile"],
original["tauri"]["bundle"]["macOS"]["license"]
);
assert_eq!(
migrated["bundle"]["licenseFile"],
original["tauri"]["bundle"]["windows"]["wix"]["license"]
);
assert_eq!(
migrated["bundle"]["licenseFile"],
original["tauri"]["bundle"]["windows"]["nsis"]["license"]
);
assert_eq!(
migrated["bundle"]["linux"]["deb"],
original["tauri"]["bundle"]["deb"]
);
assert_eq!(
migrated["bundle"]["linux"]["appimage"],
original["tauri"]["bundle"]["appimage"]
);
assert_eq!(migrated["productName"], original["package"]["productName"]);
assert_eq!(
migrated["mainBinaryName"],
original["package"]["productName"]
);
assert_eq!(migrated["version"], original["package"]["version"]);
assert_eq!(
migrated["identifier"],
original["tauri"]["bundle"]["identifier"]
);
assert_eq!(
migrated["build"]["frontendDist"],
original["build"]["distDir"]
);
assert_eq!(migrated["build"]["devUrl"], original["build"]["devPath"]);
assert_eq!(
migrated["app"]["withGlobalTauri"],
original["build"]["withGlobalTauri"]
);
assert_eq!(migrated["app"]["windows"][0]["useHttpsScheme"], true);
}
#[test]
fn skips_migrating_updater() {
let original = serde_json::json!({
"tauri": {
"updater": {
"active": false
}
}
});
let migrated = migrate(&original);
assert_eq!(migrated["plugins"]["updater"], serde_json::Value::Null);
}
#[test]
fn migrate_updater_pubkey() {
let original = serde_json::json!({
"tauri": {
"updater": {
"active": false,
"pubkey": "somekey"
}
}
});
let migrated = migrate(&original);
assert_eq!(
migrated["plugins"]["updater"]["pubkey"],
original["tauri"]["updater"]["pubkey"]
);
}
#[test]
fn migrate_dangerous_use_http_scheme() {
let original = serde_json::json!({
"tauri": {
"windows": [{}],
"security": {
"dangerousUseHttpScheme": true,
}
}
});
let migrated = migrate(&original);
assert_eq!(
!migrated["app"]["windows"][0]["useHttpsScheme"]
.as_bool()
.unwrap(),
original["tauri"]["security"]["dangerousUseHttpScheme"]
.as_bool()
.unwrap()
);
}
#[test]
fn can_migrate_default_config() {
let original = serde_json::to_value(tauri_utils::config_v1::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": {
"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::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": {
"targets": "all"
}
}
});
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");
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!({
"tauri": {
"security": {
"csp": {
"default-src": ["self", "tauri:"]
}
}
}
});
let migrated = migrate(&original);
assert_eq!(
migrated["app"]["security"]["csp"]["default-src"],
original["tauri"]["security"]["csp"]["default-src"]
);
assert!(migrated["app"]["security"]["csp"]["connect-src"]
.as_array()
.expect("connect-src isn't an array")
.contains(&"ipc: http://ipc.localhost".into()));
}
#[test]
fn migrate_csp_existing_connect_src_string() {
let original = serde_json::json!({
"tauri": {
"security": {
"csp": {
"default-src": ["self", "tauri:"],
"connect-src": "self"
}
}
}
});
let migrated = migrate(&original);
assert_eq!(
migrated["app"]["security"]["csp"]["default-src"],
original["tauri"]["security"]["csp"]["default-src"]
);
assert_eq!(
migrated["app"]["security"]["csp"]["connect-src"]
.as_str()
.expect("connect-src isn't a string"),
format!(
"{} ipc: http://ipc.localhost",
original["tauri"]["security"]["csp"]["connect-src"]
.as_str()
.unwrap()
)
);
}
#[test]
fn migrate_csp_existing_connect_src_array() {
let original = serde_json::json!({
"tauri": {
"security": {
"csp": {
"default-src": ["self", "tauri:"],
"connect-src": ["self", "asset:"]
}
}
}
});
let migrated = migrate(&original);
assert_eq!(
migrated["app"]["security"]["csp"]["default-src"],
original["tauri"]["security"]["csp"]["default-src"]
);
let migrated_connect_src = migrated["app"]["security"]["csp"]["connect-src"]
.as_array()
.expect("connect-src isn't an array");
let original_connect_src = original["tauri"]["security"]["csp"]["connect-src"]
.as_array()
.unwrap();
assert!(
migrated_connect_src
.iter()
.zip(original_connect_src.iter())
.all(|(a, b)| a == b),
"connect-src migration failed"
);
}
#[test]
fn migrate_invalid_url_dev_path() {
let original = serde_json::json!({
"build": {
"devPath": "../src",
"distDir": "../src"
}
});
let migrated = migrate(&original);
assert!(migrated["build"].get("devUrl").is_none());
assert_eq!(
migrated["build"]["distDir"],
original["build"]["frontendDist"]
);
}
#[test]
fn migrate_webview_fixed_runtime_path() {
let original = serde_json::json!({
"tauri": {
"bundle": {
"windows": {
"webviewFixedRuntimePath": "./path/to/runtime"
}
}
}
});
let migrated = migrate(&original);
assert_eq!(
migrated["bundle"]["windows"]["webviewInstallMode"]["type"],
"fixedRuntime"
);
assert_eq!(
migrated["bundle"]["windows"]["webviewInstallMode"]["path"],
original["tauri"]["bundle"]["windows"]["webviewFixedRuntimePath"]
);
}
}