fix(cli/migrate): tolerate non-UTF-8 in migration (#9457)

This commit is contained in:
Kornel 2024-04-15 10:39:53 +01:00 committed by GitHub
parent 9331435a50
commit 73c1c2d338
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 40 additions and 28 deletions

View File

@ -0,0 +1,6 @@
---
'tauri-cli': 'patch:bug'
'@tauri-apps/cli': 'patch:bug'
---
Gracefully handle Non-UTF8 files when using `tauri migrate`

View File

@ -55,7 +55,7 @@ fn symlink_runner(create_symlinks: impl Fn(&Path) -> io::Result<Symlink>) -> Res
if output.status.success() { if output.status.success() {
// gather the output into a string // 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 // we expect the output to be the bin path, twice
assert_eq!(stdout, format!("{bin}\n{bin}\n", bin = bin.display())); 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<Symlink>) -> Res
not(feature = "process-relaunch-dangerous-allow-symlink-macos") not(feature = "process-relaunch-dangerous-allow-symlink-macos")
)) { )) {
// we expect this to fail on macOS without the dangerous symlink flag set // 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 // make sure it's the error that we expect
assert!(stderr.contains( assert!(stderr.contains(

View File

@ -92,8 +92,8 @@ pub fn run_collect(cmd: &[&str]) -> (String, String) {
stderr, stderr,
status, status,
} = prog.wait_with_output().expect("failed to wait on child"); } = prog.wait_with_output().expect("failed to wait on child");
let stdout = String::from_utf8(stdout).unwrap(); let stdout = String::from_utf8_lossy(&stdout);
let stderr = String::from_utf8(stderr).unwrap(); let stderr = String::from_utf8_lossy(&stderr);
if !status.success() { if !status.success() {
eprintln!("stdout: <<<{}>>>", stdout); eprintln!("stdout: <<<{}>>>", stdout);
eprintln!("stderr: <<<{}>>>", stderr); eprintln!("stderr: <<<{}>>>", stderr);

View File

@ -1127,7 +1127,7 @@ fn get_cargo_metadata() -> crate::Result<CargoMetadata> {
if !output.status.success() { if !output.status.success() {
return Err(anyhow::anyhow!( return Err(anyhow::anyhow!(
"cargo metadata command exited with a non zero exit code: {}", "cargo metadata command exited with a non zero exit code: {}",
String::from_utf8(output.stderr)? String::from_utf8_lossy(&output.stderr)
)); ));
} }

View File

@ -12,7 +12,7 @@ use tauri_utils::acl::{
use std::{ use std::{
collections::{BTreeMap, HashSet}, collections::{BTreeMap, HashSet},
fs::{create_dir_all, write}, fs,
path::Path, path::Path,
}; };
@ -21,7 +21,7 @@ pub fn migrate(tauri_dir: &Path) -> Result<MigratedConfig> {
tauri_utils_v1::config::parse::parse_value(tauri_dir.join("tauri.conf.json")) tauri_utils_v1::config::parse::parse_value(tauri_dir.join("tauri.conf.json"))
{ {
let migrated = migrate_config(&mut config)?; 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<PermissionEntry> = vec![ let mut permissions: Vec<PermissionEntry> = vec![
"path:default", "path:default",
@ -38,8 +38,8 @@ pub fn migrate(tauri_dir: &Path) -> Result<MigratedConfig> {
permissions.extend(migrated.permissions.clone()); permissions.extend(migrated.permissions.clone());
let capabilities_path = config_path.parent().unwrap().join("capabilities"); let capabilities_path = config_path.parent().unwrap().join("capabilities");
create_dir_all(&capabilities_path)?; fs::create_dir_all(&capabilities_path)?;
write( fs::write(
capabilities_path.join("migrated.json"), capabilities_path.join("migrated.json"),
serde_json::to_string_pretty(&Capability { serde_json::to_string_pretty(&Capability {
identifier: "migrated".to_string(), identifier: "migrated".to_string(),

View File

@ -6,15 +6,14 @@ use crate::{
helpers::{app_paths::walk_builder, cargo, npm::PackageManager}, helpers::{app_paths::walk_builder, cargo, npm::PackageManager},
Result, Result,
}; };
use anyhow::Context;
use std::{ use std::{fs, path::Path};
fs::{read_to_string, write},
path::Path,
};
const CORE_API_MODULES: &[&str] = &["dpi", "event", "path", "core", "window", "mocks"]; const CORE_API_MODULES: &[&str] = &["dpi", "event", "path", "core", "window", "mocks"];
const JS_EXTENSIONS: &[&str] = &["js", "jsx", "ts", "tsx", "mjs"]; 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<()> { pub fn migrate(app_dir: &Path, tauri_dir: &Path) -> Result<()> {
let mut new_npm_packages = Vec::new(); let mut new_npm_packages = Vec::new();
let mut new_cargo_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() .next()
.unwrap_or(PackageManager::Npm); .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() { for entry in walk_builder(app_dir).build().flatten() {
if entry.file_type().map(|t| t.is_file()).unwrap_or_default() { if entry.file_type().map(|t| t.is_file()).unwrap_or_default() {
let path = entry.path(); let path = entry.path();
let ext = path.extension().unwrap_or_default(); let ext = path.extension().unwrap_or_default();
if JS_EXTENSIONS.iter().any(|e| e == &ext) { if JS_EXTENSIONS.iter().any(|e| e == &ext) {
let js_contents = read_to_string(path)?; let js_contents = fs::read(path)?;
let new_contents = let new_contents =
tauri_api_import_regex.replace_all(&js_contents, |cap: &regex::Captures<'_>| { tauri_api_import_regex.replace_all(&js_contents, |cap: &regex::bytes::Captures<'_>| {
let module = cap.get(1).unwrap().as_str(); let module = cap.get(1).unwrap().as_bytes();
let original = cap.get(0).unwrap().as_str(); 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" { if module == "tauri" {
let new = "@tauri-apps/api/core".to_string(); 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(); let new = "@tauri-apps/api/webviewWindow".to_string();
log::info!("Replacing `{original}` with `{new}` on {}", path.display()); log::info!("Replacing `{original}` with `{new}` on {}", path.display());
new new
} else if CORE_API_MODULES.contains(&module) { } else if CORE_API_MODULES.contains(&module.as_str()) {
original.to_string() original
} else { } else {
let plugin = format!("@tauri-apps/plugin-{module}"); let plugin = format!("@tauri-apps/plugin-{module}");
log::info!( log::info!(
@ -61,7 +62,7 @@ pub fn migrate(app_dir: &Path, tauri_dir: &Path) -> Result<()> {
if module == "clipboard" { if module == "clipboard" {
"clipboard-manager" "clipboard-manager"
} else { } else {
module &module
} }
)); ));
@ -70,18 +71,21 @@ pub fn migrate(app_dir: &Path, tauri_dir: &Path) -> Result<()> {
}); });
if new_contents != js_contents { 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() { 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() { 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(()) Ok(())

View File

@ -6,6 +6,7 @@ use crate::{
helpers::app_paths::{app_dir, tauri_dir}, helpers::app_paths::{app_dir, tauri_dir},
Result, Result,
}; };
use anyhow::Context;
mod config; mod config;
mod frontend; mod frontend;
@ -15,18 +16,19 @@ pub fn command() -> Result<()> {
let tauri_dir = tauri_dir(); let tauri_dir = tauri_dir();
let app_dir = app_dir(); let app_dir = app_dir();
let migrated = config::migrate(&tauri_dir)?; let migrated = config::migrate(&tauri_dir).context("Could not migrate config")?;
manifest::migrate(&tauri_dir)?; manifest::migrate(&tauri_dir).context("Could not migrate manifest")?;
frontend::migrate(app_dir, &tauri_dir)?; frontend::migrate(app_dir, &tauri_dir)?;
// Add plugins // Add plugins
for plugin in migrated.plugins { for plugin in migrated.plugins {
crate::add::command(crate::add::Options { crate::add::command(crate::add::Options {
plugin, plugin: plugin.clone(),
branch: None, branch: None,
tag: None, tag: None,
rev: None, rev: None,
})? })
.with_context(|| format!("Could not migrate plugin '{plugin}'"))?
} }
Ok(()) Ok(())