diff --git a/.changes/cli-migrate-non-utf8.md b/.changes/cli-migrate-non-utf8.md new file mode 100644 index 000000000..5d36cc18b --- /dev/null +++ b/.changes/cli-migrate-non-utf8.md @@ -0,0 +1,6 @@ +--- +'tauri-cli': 'patch:bug' +'@tauri-apps/cli': 'patch:bug' +--- + +Gracefully handle Non-UTF8 files when using `tauri migrate` \ No newline at end of file diff --git a/core/tests/restart/tests/restart.rs b/core/tests/restart/tests/restart.rs index 8b380cd23..94ff2c8b3 100644 --- a/core/tests/restart/tests/restart.rs +++ b/core/tests/restart/tests/restart.rs @@ -55,7 +55,7 @@ fn symlink_runner(create_symlinks: impl Fn(&Path) -> io::Result) -> Res if output.status.success() { // gather the output into a string - let stdout = String::from_utf8(output.stdout)?; + let stdout = String::from_utf8_lossy(&output.stdout); // we expect the output to be the bin path, twice assert_eq!(stdout, format!("{bin}\n{bin}\n", bin = bin.display())); @@ -64,7 +64,7 @@ fn symlink_runner(create_symlinks: impl Fn(&Path) -> io::Result) -> Res not(feature = "process-relaunch-dangerous-allow-symlink-macos") )) { // we expect this to fail on macOS without the dangerous symlink flag set - let stderr = String::from_utf8(output.stderr)?; + let stderr = String::from_utf8_lossy(&output.stderr); // make sure it's the error that we expect assert!(stderr.contains( diff --git a/tooling/bench/src/utils.rs b/tooling/bench/src/utils.rs index 46e3b5a75..4cb2cabcb 100644 --- a/tooling/bench/src/utils.rs +++ b/tooling/bench/src/utils.rs @@ -92,8 +92,8 @@ pub fn run_collect(cmd: &[&str]) -> (String, String) { stderr, status, } = prog.wait_with_output().expect("failed to wait on child"); - let stdout = String::from_utf8(stdout).unwrap(); - let stderr = String::from_utf8(stderr).unwrap(); + let stdout = String::from_utf8_lossy(&stdout); + let stderr = String::from_utf8_lossy(&stderr); if !status.success() { eprintln!("stdout: <<<{}>>>", stdout); eprintln!("stderr: <<<{}>>>", stderr); diff --git a/tooling/cli/src/interface/rust.rs b/tooling/cli/src/interface/rust.rs index 1eea9b54d..4ef204118 100644 --- a/tooling/cli/src/interface/rust.rs +++ b/tooling/cli/src/interface/rust.rs @@ -1127,7 +1127,7 @@ fn get_cargo_metadata() -> crate::Result { if !output.status.success() { return Err(anyhow::anyhow!( "cargo metadata command exited with a non zero exit code: {}", - String::from_utf8(output.stderr)? + String::from_utf8_lossy(&output.stderr) )); } diff --git a/tooling/cli/src/migrate/config.rs b/tooling/cli/src/migrate/config.rs index b5380a297..41fd20639 100644 --- a/tooling/cli/src/migrate/config.rs +++ b/tooling/cli/src/migrate/config.rs @@ -12,7 +12,7 @@ use tauri_utils::acl::{ use std::{ collections::{BTreeMap, HashSet}, - fs::{create_dir_all, write}, + fs, path::Path, }; @@ -21,7 +21,7 @@ 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)?; - write(&config_path, serde_json::to_string_pretty(&config)?)?; + fs::write(&config_path, serde_json::to_string_pretty(&config)?)?; let mut permissions: Vec = vec![ "path:default", @@ -38,8 +38,8 @@ pub fn migrate(tauri_dir: &Path) -> Result { permissions.extend(migrated.permissions.clone()); let capabilities_path = config_path.parent().unwrap().join("capabilities"); - create_dir_all(&capabilities_path)?; - write( + fs::create_dir_all(&capabilities_path)?; + fs::write( capabilities_path.join("migrated.json"), serde_json::to_string_pretty(&Capability { identifier: "migrated".to_string(), diff --git a/tooling/cli/src/migrate/frontend.rs b/tooling/cli/src/migrate/frontend.rs index 0dd404339..57bcb8292 100644 --- a/tooling/cli/src/migrate/frontend.rs +++ b/tooling/cli/src/migrate/frontend.rs @@ -6,15 +6,14 @@ use crate::{ helpers::{app_paths::walk_builder, cargo, npm::PackageManager}, Result, }; +use anyhow::Context; -use std::{ - fs::{read_to_string, write}, - path::Path, -}; +use std::{fs, path::Path}; const CORE_API_MODULES: &[&str] = &["dpi", "event", "path", "core", "window", "mocks"]; const JS_EXTENSIONS: &[&str] = &["js", "jsx", "ts", "tsx", "mjs"]; +/// Returns a list of paths that could not be migrated pub fn migrate(app_dir: &Path, tauri_dir: &Path) -> Result<()> { let mut new_npm_packages = Vec::new(); let mut new_cargo_packages = Vec::new(); @@ -24,19 +23,21 @@ pub fn migrate(app_dir: &Path, tauri_dir: &Path) -> Result<()> { .next() .unwrap_or(PackageManager::Npm); - let tauri_api_import_regex = regex::Regex::new(r"@tauri-apps/api/(\w+)").unwrap(); + let tauri_api_import_regex = regex::bytes::Regex::new(r"@tauri-apps/api/(\w+)").unwrap(); for entry in walk_builder(app_dir).build().flatten() { if entry.file_type().map(|t| t.is_file()).unwrap_or_default() { let path = entry.path(); let ext = path.extension().unwrap_or_default(); if JS_EXTENSIONS.iter().any(|e| e == &ext) { - let js_contents = read_to_string(path)?; + let js_contents = fs::read(path)?; let new_contents = - tauri_api_import_regex.replace_all(&js_contents, |cap: ®ex::Captures<'_>| { - let module = cap.get(1).unwrap().as_str(); - let original = cap.get(0).unwrap().as_str(); + tauri_api_import_regex.replace_all(&js_contents, |cap: ®ex::bytes::Captures<'_>| { + let module = cap.get(1).unwrap().as_bytes(); + let module = String::from_utf8_lossy(module).to_string(); + let original = cap.get(0).unwrap().as_bytes(); + let original = String::from_utf8_lossy(original).to_string(); if module == "tauri" { let new = "@tauri-apps/api/core".to_string(); @@ -46,8 +47,8 @@ pub fn migrate(app_dir: &Path, tauri_dir: &Path) -> Result<()> { let new = "@tauri-apps/api/webviewWindow".to_string(); log::info!("Replacing `{original}` with `{new}` on {}", path.display()); new - } else if CORE_API_MODULES.contains(&module) { - original.to_string() + } else if CORE_API_MODULES.contains(&module.as_str()) { + original } else { let plugin = format!("@tauri-apps/plugin-{module}"); log::info!( @@ -61,7 +62,7 @@ pub fn migrate(app_dir: &Path, tauri_dir: &Path) -> Result<()> { if module == "clipboard" { "clipboard-manager" } else { - module + &module } )); @@ -70,18 +71,21 @@ pub fn migrate(app_dir: &Path, tauri_dir: &Path) -> Result<()> { }); if new_contents != js_contents { - write(path, new_contents.as_bytes())?; + fs::write(path, new_contents) + .with_context(|| format!("Error writing {}", path.display()))?; } } } } if !new_npm_packages.is_empty() { - pm.install(&new_npm_packages)?; + pm.install(&new_npm_packages) + .context("Error installing new npm packages")?; } if !new_cargo_packages.is_empty() { - cargo::install(&new_cargo_packages, Some(tauri_dir))?; + cargo::install(&new_cargo_packages, Some(tauri_dir)) + .context("Error installing new Cargo packages")?; } Ok(()) diff --git a/tooling/cli/src/migrate/mod.rs b/tooling/cli/src/migrate/mod.rs index 3601605bc..29351319c 100644 --- a/tooling/cli/src/migrate/mod.rs +++ b/tooling/cli/src/migrate/mod.rs @@ -6,6 +6,7 @@ use crate::{ helpers::app_paths::{app_dir, tauri_dir}, Result, }; +use anyhow::Context; mod config; mod frontend; @@ -15,18 +16,19 @@ pub fn command() -> Result<()> { let tauri_dir = tauri_dir(); let app_dir = app_dir(); - let migrated = config::migrate(&tauri_dir)?; - manifest::migrate(&tauri_dir)?; + let migrated = config::migrate(&tauri_dir).context("Could not migrate config")?; + manifest::migrate(&tauri_dir).context("Could not migrate manifest")?; frontend::migrate(app_dir, &tauri_dir)?; // Add plugins for plugin in migrated.plugins { crate::add::command(crate::add::Options { - plugin, + plugin: plugin.clone(), branch: None, tag: None, rev: None, - })? + }) + .with_context(|| format!("Could not migrate plugin '{plugin}'"))? } Ok(())