From d5511c3117b1a117cb0b7359c5fa09aa4795122b Mon Sep 17 00:00:00 2001 From: Lucas Fernandes Nogueira Date: Tue, 30 Jul 2024 16:32:59 -0300 Subject: [PATCH] feat(cli): add migration from 2.0.0-beta to 2.0.0-rc (#10395) * refactor(cli): check tauri version on migration * rc migration * license headers * fix tests * add path * update schema --- .changes/rc-migration.md | 6 + core/tauri-config-schema/schema.json | 18 +- tooling/cli/Cargo.lock | 1 + tooling/cli/Cargo.toml | 1 + tooling/cli/schema.json | 18 +- tooling/cli/src/add.rs | 2 +- tooling/cli/src/helpers/cargo_manifest.rs | 172 +++++++++++++++ tooling/cli/src/helpers/mod.rs | 1 + tooling/cli/src/helpers/npm.rs | 70 +++++- tooling/cli/src/info/packages_nodejs.rs | 72 +----- tooling/cli/src/info/packages_rust.rs | 206 +++--------------- tooling/cli/src/interface/rust/manifest.rs | 22 +- tooling/cli/src/migrate/migrations/mod.rs | 6 + .../src/migrate/{ => migrations/v1}/config.rs | 0 .../migrate/{ => migrations/v1}/frontend.rs | 25 ++- .../migrate/{ => migrations/v1}/manifest.rs | 27 +-- tooling/cli/src/migrate/migrations/v1/mod.rs | 36 +++ tooling/cli/src/migrate/migrations/v2_rc.rs | 196 +++++++++++++++++ tooling/cli/src/migrate/mod.rs | 52 +++-- 19 files changed, 619 insertions(+), 312 deletions(-) create mode 100644 .changes/rc-migration.md create mode 100644 tooling/cli/src/helpers/cargo_manifest.rs create mode 100644 tooling/cli/src/migrate/migrations/mod.rs rename tooling/cli/src/migrate/{ => migrations/v1}/config.rs (100%) rename tooling/cli/src/migrate/{ => migrations/v1}/frontend.rs (95%) rename tooling/cli/src/migrate/{ => migrations/v1}/manifest.rs (93%) create mode 100644 tooling/cli/src/migrate/migrations/v1/mod.rs create mode 100644 tooling/cli/src/migrate/migrations/v2_rc.rs diff --git a/.changes/rc-migration.md b/.changes/rc-migration.md new file mode 100644 index 000000000..3e117a741 --- /dev/null +++ b/.changes/rc-migration.md @@ -0,0 +1,6 @@ +--- +"tauri-cli": patch:feat +"@tauri-apps/cli": patch:feat +--- + +Added migration from `2.0.0-beta` to `2.0.0-rc`. diff --git a/core/tauri-config-schema/schema.json b/core/tauri-config-schema/schema.json index 518d42da3..c20e530d1 100644 --- a/core/tauri-config-schema/schema.json +++ b/core/tauri-config-schema/schema.json @@ -2551,7 +2551,7 @@ ] }, "changelog": { - "description": "Path of the uncompressed Changelog file, to be stored at /usr/share/doc/package-name/changelog.gz. See\n https://www.debian.org/doc/debian-policy/ch-docs.html#changelog-files-and-release-notes", + "description": "Path of the uncompressed Changelog file, to be stored at /usr/share/doc/package-name/changelog.gz. See\n ", "type": [ "string", "null" @@ -2565,28 +2565,28 @@ ] }, "preInstallScript": { - "description": "Path to script that will be executed before the package is unpacked. See\n https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html", + "description": "Path to script that will be executed before the package is unpacked. See\n ", "type": [ "string", "null" ] }, "postInstallScript": { - "description": "Path to script that will be executed after the package is unpacked. See\n https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html", + "description": "Path to script that will be executed after the package is unpacked. See\n ", "type": [ "string", "null" ] }, "preRemoveScript": { - "description": "Path to script that will be executed before the package is removed. See\n https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html", + "description": "Path to script that will be executed before the package is removed. See\n ", "type": [ "string", "null" ] }, "postRemoveScript": { - "description": "Path to script that will be executed after the package is removed. See\n https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html", + "description": "Path to script that will be executed after the package is removed. See\n ", "type": [ "string", "null" @@ -2667,28 +2667,28 @@ ] }, "preInstallScript": { - "description": "Path to script that will be executed before the package is unpacked. See\n http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html", + "description": "Path to script that will be executed before the package is unpacked. See\n ", "type": [ "string", "null" ] }, "postInstallScript": { - "description": "Path to script that will be executed after the package is unpacked. See\n http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html", + "description": "Path to script that will be executed after the package is unpacked. See\n ", "type": [ "string", "null" ] }, "preRemoveScript": { - "description": "Path to script that will be executed before the package is removed. See\n http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html", + "description": "Path to script that will be executed before the package is removed. See\n ", "type": [ "string", "null" ] }, "postRemoveScript": { - "description": "Path to script that will be executed after the package is removed. See\n http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html", + "description": "Path to script that will be executed after the package is removed. See\n ", "type": [ "string", "null" diff --git a/tooling/cli/Cargo.lock b/tooling/cli/Cargo.lock index 333331b13..f1581ba4e 100644 --- a/tooling/cli/Cargo.lock +++ b/tooling/cli/Cargo.lock @@ -5181,6 +5181,7 @@ dependencies = [ "toml_edit 0.22.6", "ureq", "url", + "walkdir", "windows-sys 0.52.0", ] diff --git a/tooling/cli/Cargo.toml b/tooling/cli/Cargo.toml index f3e097172..cc720012b 100644 --- a/tooling/cli/Cargo.toml +++ b/tooling/cli/Cargo.toml @@ -98,6 +98,7 @@ oxc_allocator = "0.16" oxc_ast = "0.16" magic_string = "0.3" phf = { version = "0.11", features = ["macros"] } +walkdir = "2" [target."cfg(windows)".dependencies.windows-sys] version = "0.52" diff --git a/tooling/cli/schema.json b/tooling/cli/schema.json index 518d42da3..c20e530d1 100644 --- a/tooling/cli/schema.json +++ b/tooling/cli/schema.json @@ -2551,7 +2551,7 @@ ] }, "changelog": { - "description": "Path of the uncompressed Changelog file, to be stored at /usr/share/doc/package-name/changelog.gz. See\n https://www.debian.org/doc/debian-policy/ch-docs.html#changelog-files-and-release-notes", + "description": "Path of the uncompressed Changelog file, to be stored at /usr/share/doc/package-name/changelog.gz. See\n ", "type": [ "string", "null" @@ -2565,28 +2565,28 @@ ] }, "preInstallScript": { - "description": "Path to script that will be executed before the package is unpacked. See\n https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html", + "description": "Path to script that will be executed before the package is unpacked. See\n ", "type": [ "string", "null" ] }, "postInstallScript": { - "description": "Path to script that will be executed after the package is unpacked. See\n https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html", + "description": "Path to script that will be executed after the package is unpacked. See\n ", "type": [ "string", "null" ] }, "preRemoveScript": { - "description": "Path to script that will be executed before the package is removed. See\n https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html", + "description": "Path to script that will be executed before the package is removed. See\n ", "type": [ "string", "null" ] }, "postRemoveScript": { - "description": "Path to script that will be executed after the package is removed. See\n https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html", + "description": "Path to script that will be executed after the package is removed. See\n ", "type": [ "string", "null" @@ -2667,28 +2667,28 @@ ] }, "preInstallScript": { - "description": "Path to script that will be executed before the package is unpacked. See\n http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html", + "description": "Path to script that will be executed before the package is unpacked. See\n ", "type": [ "string", "null" ] }, "postInstallScript": { - "description": "Path to script that will be executed after the package is unpacked. See\n http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html", + "description": "Path to script that will be executed after the package is unpacked. See\n ", "type": [ "string", "null" ] }, "preRemoveScript": { - "description": "Path to script that will be executed before the package is removed. See\n http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html", + "description": "Path to script that will be executed before the package is removed. See\n ", "type": [ "string", "null" ] }, "postRemoveScript": { - "description": "Path to script that will be executed after the package is removed. See\n http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html", + "description": "Path to script that will be executed after the package is removed. See\n ", "type": [ "string", "null" diff --git a/tooling/cli/src/add.rs b/tooling/cli/src/add.rs index f959d1393..95a77268f 100644 --- a/tooling/cli/src/add.rs +++ b/tooling/cli/src/add.rs @@ -146,7 +146,7 @@ pub fn command(options: Options) -> Result<()> { (None, None, None, None) => npm_name, _ => anyhow::bail!("Only one of --tag, --rev and --branch can be specified"), }; - manager.install(&[npm_spec])?; + manager.install(&[npm_spec], &tauri_dir)?; } let _ = acl::permission::add::command(acl::permission::add::Options { diff --git a/tooling/cli/src/helpers/cargo_manifest.rs b/tooling/cli/src/helpers/cargo_manifest.rs new file mode 100644 index 000000000..4633dac49 --- /dev/null +++ b/tooling/cli/src/helpers/cargo_manifest.rs @@ -0,0 +1,172 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::Deserialize; + +use std::{ + collections::HashMap, + fmt::Write, + fs::read_to_string, + path::{Path, PathBuf}, +}; + +#[derive(Clone, Deserialize)] +pub struct CargoLockPackage { + pub name: String, + pub version: String, + pub source: Option, +} + +#[derive(Deserialize)] +pub struct CargoLock { + pub package: Vec, +} + +#[derive(Clone, Deserialize)] +pub struct CargoManifestDependencyPackage { + pub version: Option, + pub git: Option, + pub branch: Option, + pub rev: Option, + pub path: Option, +} + +#[derive(Clone, Deserialize)] +#[serde(untagged)] +pub enum CargoManifestDependency { + Version(String), + Package(CargoManifestDependencyPackage), +} + +#[derive(Deserialize)] +pub struct CargoManifestPackage { + pub version: String, +} + +#[derive(Deserialize)] +pub struct CargoManifest { + pub package: CargoManifestPackage, + pub dependencies: HashMap, +} + +pub struct CrateVersion { + pub version: String, + pub found_crate_versions: Vec, +} + +pub fn crate_version( + tauri_dir: &Path, + manifest: Option<&CargoManifest>, + lock: Option<&CargoLock>, + name: &str, +) -> CrateVersion { + let crate_lock_packages: Vec = lock + .as_ref() + .map(|lock| { + lock + .package + .iter() + .filter(|p| p.name == name) + .cloned() + .collect() + }) + .unwrap_or_default(); + let (crate_version_string, found_crate_versions) = + match (&manifest, &lock, crate_lock_packages.len()) { + (Some(_manifest), Some(_lock), 1) => { + let crate_lock_package = crate_lock_packages.first().unwrap(); + let version_string = if let Some(s) = &crate_lock_package.source { + if s.starts_with("git") { + format!("{} ({})", s, crate_lock_package.version) + } else { + crate_lock_package.version.clone() + } + } else { + crate_lock_package.version.clone() + }; + (version_string, vec![crate_lock_package.version.clone()]) + } + (None, Some(_lock), 1) => { + let crate_lock_package = crate_lock_packages.first().unwrap(); + let version_string = if let Some(s) = &crate_lock_package.source { + if s.starts_with("git") { + format!("{} ({})", s, crate_lock_package.version) + } else { + crate_lock_package.version.clone() + } + } else { + crate_lock_package.version.clone() + }; + ( + format!("{version_string} (no manifest)"), + vec![crate_lock_package.version.clone()], + ) + } + _ => { + let mut found_crate_versions = Vec::new(); + let mut is_git = false; + let manifest_version = match manifest.and_then(|m| m.dependencies.get(name).cloned()) { + Some(tauri) => match tauri { + CargoManifestDependency::Version(v) => { + found_crate_versions.push(v.clone()); + v + } + CargoManifestDependency::Package(p) => { + if let Some(v) = p.version { + found_crate_versions.push(v.clone()); + v + } else if let Some(p) = p.path { + let manifest_path = tauri_dir.join(&p).join("Cargo.toml"); + let v = match read_to_string(manifest_path) + .map_err(|_| ()) + .and_then(|m| toml::from_str::(&m).map_err(|_| ())) + { + Ok(manifest) => manifest.package.version, + Err(_) => "unknown version".to_string(), + }; + format!("path:{p:?} [{v}]") + } else if let Some(g) = p.git { + is_git = true; + let mut v = format!("git:{g}"); + if let Some(branch) = p.branch { + let _ = write!(v, "&branch={branch}"); + } else if let Some(rev) = p.rev { + let _ = write!(v, "#{rev}"); + } + v + } else { + "unknown manifest".to_string() + } + } + }, + None => "no manifest".to_string(), + }; + + let lock_version = match (lock, crate_lock_packages.is_empty()) { + (Some(_lock), false) => crate_lock_packages + .iter() + .map(|p| p.version.clone()) + .collect::>() + .join(", "), + (Some(_lock), true) => "unknown lockfile".to_string(), + _ => "no lockfile".to_string(), + }; + + ( + format!( + "{} {}({})", + manifest_version, + if is_git { "(git manifest)" } else { "" }, + lock_version + ), + found_crate_versions, + ) + } + }; + + CrateVersion { + found_crate_versions, + version: crate_version_string, + } +} diff --git a/tooling/cli/src/helpers/mod.rs b/tooling/cli/src/helpers/mod.rs index 3a9495922..aa55463d5 100644 --- a/tooling/cli/src/helpers/mod.rs +++ b/tooling/cli/src/helpers/mod.rs @@ -4,6 +4,7 @@ pub mod app_paths; pub mod cargo; +pub mod cargo_manifest; pub mod config; pub mod flock; pub mod framework; diff --git a/tooling/cli/src/helpers/npm.rs b/tooling/cli/src/helpers/npm.rs index 37e527a31..916c0ae9b 100644 --- a/tooling/cli/src/helpers/npm.rs +++ b/tooling/cli/src/helpers/npm.rs @@ -81,7 +81,7 @@ impl PackageManager { } } - pub fn install(&self, dependencies: &[String]) -> crate::Result<()> { + pub fn install>(&self, dependencies: &[String], app_dir: P) -> crate::Result<()> { let dependencies_str = if dependencies.len() > 1 { "dependencies" } else { @@ -100,6 +100,7 @@ impl PackageManager { .cross_command() .arg("add") .args(dependencies) + .current_dir(app_dir) .status() .with_context(|| format!("failed to run {self}"))?; @@ -109,4 +110,71 @@ impl PackageManager { Ok(()) } + + pub fn current_package_version>( + &self, + name: &str, + app_dir: P, + ) -> crate::Result> { + let (output, regex) = match self { + PackageManager::Yarn => ( + cross_command("yarn") + .args(["list", "--pattern"]) + .arg(name) + .args(["--depth", "0"]) + .current_dir(app_dir) + .output()?, + None, + ), + PackageManager::YarnBerry => ( + cross_command("yarn") + .arg("info") + .arg(name) + .arg("--json") + .current_dir(app_dir) + .output()?, + Some(regex::Regex::new("\"Version\":\"([\\da-zA-Z\\-\\.]+)\"").unwrap()), + ), + PackageManager::Npm => ( + cross_command("npm") + .arg("list") + .arg(name) + .args(["version", "--depth", "0"]) + .current_dir(app_dir) + .output()?, + None, + ), + PackageManager::Pnpm => ( + cross_command("pnpm") + .arg("list") + .arg(name) + .args(["--parseable", "--depth", "0"]) + .current_dir(app_dir) + .output()?, + None, + ), + // Bun doesn't support `list` command + PackageManager::Bun => ( + cross_command("npm") + .arg("list") + .arg(name) + .args(["version", "--depth", "0"]) + .current_dir(app_dir) + .output()?, + None, + ), + }; + if output.status.success() { + let stdout = String::from_utf8_lossy(&output.stdout); + let regex = regex.unwrap_or_else(|| regex::Regex::new("@(\\d[\\da-zA-Z\\-\\.]+)").unwrap()); + Ok( + regex + .captures_iter(&stdout) + .last() + .and_then(|cap| cap.get(1).map(|v| v.as_str().to_string())), + ) + } else { + Ok(None) + } + } } diff --git a/tooling/cli/src/info/packages_nodejs.rs b/tooling/cli/src/info/packages_nodejs.rs index 52866c535..67dd83cba 100644 --- a/tooling/cli/src/info/packages_nodejs.rs +++ b/tooling/cli/src/info/packages_nodejs.rs @@ -6,7 +6,7 @@ use super::SectionItem; use super::{env_nodejs::manager_version, VersionMetadata}; use colored::Colorize; use serde::Deserialize; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use crate::helpers::{cross_command, npm::PackageManager}; @@ -87,73 +87,6 @@ fn npm_latest_version(pm: &PackageManager, name: &str) -> crate::Result>( - pm: &PackageManager, - name: &str, - app_dir: P, -) -> crate::Result> { - let (output, regex) = match pm { - PackageManager::Yarn => ( - cross_command("yarn") - .args(["list", "--pattern"]) - .arg(name) - .args(["--depth", "0"]) - .current_dir(app_dir) - .output()?, - None, - ), - PackageManager::YarnBerry => ( - cross_command("yarn") - .arg("info") - .arg(name) - .arg("--json") - .current_dir(app_dir) - .output()?, - Some(regex::Regex::new("\"Version\":\"([\\da-zA-Z\\-\\.]+)\"").unwrap()), - ), - PackageManager::Npm => ( - cross_command("npm") - .arg("list") - .arg(name) - .args(["version", "--depth", "0"]) - .current_dir(app_dir) - .output()?, - None, - ), - PackageManager::Pnpm => ( - cross_command("pnpm") - .arg("list") - .arg(name) - .args(["--parseable", "--depth", "0"]) - .current_dir(app_dir) - .output()?, - None, - ), - // Bun doesn't support `list` command - PackageManager::Bun => ( - cross_command("npm") - .arg("list") - .arg(name) - .args(["version", "--depth", "0"]) - .current_dir(app_dir) - .output()?, - None, - ), - }; - if output.status.success() { - let stdout = String::from_utf8_lossy(&output.stdout); - let regex = regex.unwrap_or_else(|| regex::Regex::new("@(\\d[\\da-zA-Z\\-\\.]+)").unwrap()); - Ok( - regex - .captures_iter(&stdout) - .last() - .and_then(|cap| cap.get(1).map(|v| v.as_str().to_string())), - ) - } else { - Ok(None) - } -} - fn get_package_manager>(app_dir_entries: &[T]) -> PackageManager { let mut use_npm = false; let mut use_pnpm = false; @@ -244,7 +177,8 @@ pub fn items(app_dir: Option<&PathBuf>, metadata: &VersionMetadata) -> Vec, -} - -#[derive(Deserialize)] -struct CargoLock { - package: Vec, -} - -#[derive(Clone, Deserialize)] -struct CargoManifestDependencyPackage { - version: Option, - git: Option, - branch: Option, - rev: Option, - path: Option, -} - -#[derive(Clone, Deserialize)] -#[serde(untagged)] -enum CargoManifestDependency { - Version(String), - Package(CargoManifestDependencyPackage), -} - -#[derive(Deserialize)] -struct CargoManifestPackage { - version: String, -} - -#[derive(Deserialize)] -struct CargoManifest { - package: CargoManifestPackage, - dependencies: HashMap, -} - fn crate_latest_version(name: &str) -> Option { let url = format!("https://docs.rs/crate/{name}/"); match ureq::get(&url).call() { @@ -61,138 +22,6 @@ fn crate_latest_version(name: &str) -> Option { } } -fn crate_version( - tauri_dir: &Path, - manifest: Option<&CargoManifest>, - lock: Option<&CargoLock>, - name: &str, -) -> (String, Option) { - let crate_lock_packages: Vec = lock - .as_ref() - .map(|lock| { - lock - .package - .iter() - .filter(|p| p.name == name) - .cloned() - .collect() - }) - .unwrap_or_default(); - let (crate_version_string, found_crate_versions) = - match (&manifest, &lock, crate_lock_packages.len()) { - (Some(_manifest), Some(_lock), 1) => { - let crate_lock_package = crate_lock_packages.first().unwrap(); - let version_string = if let Some(s) = &crate_lock_package.source { - if s.starts_with("git") { - format!("{} ({})", s, crate_lock_package.version) - } else { - crate_lock_package.version.clone() - } - } else { - crate_lock_package.version.clone() - }; - (version_string, vec![crate_lock_package.version.clone()]) - } - (None, Some(_lock), 1) => { - let crate_lock_package = crate_lock_packages.first().unwrap(); - let version_string = if let Some(s) = &crate_lock_package.source { - if s.starts_with("git") { - format!("{} ({})", s, crate_lock_package.version) - } else { - crate_lock_package.version.clone() - } - } else { - crate_lock_package.version.clone() - }; - ( - format!("{version_string} (no manifest)"), - vec![crate_lock_package.version.clone()], - ) - } - _ => { - let mut found_crate_versions = Vec::new(); - let mut is_git = false; - let manifest_version = match manifest.and_then(|m| m.dependencies.get(name).cloned()) { - Some(tauri) => match tauri { - CargoManifestDependency::Version(v) => { - found_crate_versions.push(v.clone()); - v - } - CargoManifestDependency::Package(p) => { - if let Some(v) = p.version { - found_crate_versions.push(v.clone()); - v - } else if let Some(p) = p.path { - let manifest_path = tauri_dir.join(&p).join("Cargo.toml"); - let v = match read_to_string(manifest_path) - .map_err(|_| ()) - .and_then(|m| toml::from_str::(&m).map_err(|_| ())) - { - Ok(manifest) => manifest.package.version, - Err(_) => "unknown version".to_string(), - }; - format!("path:{p:?} [{v}]") - } else if let Some(g) = p.git { - is_git = true; - let mut v = format!("git:{g}"); - if let Some(branch) = p.branch { - let _ = write!(v, "&branch={branch}"); - } else if let Some(rev) = p.rev { - let _ = write!(v, "#{rev}"); - } - v - } else { - "unknown manifest".to_string() - } - } - }, - None => "no manifest".to_string(), - }; - - let lock_version = match (lock, crate_lock_packages.is_empty()) { - (Some(_lock), false) => crate_lock_packages - .iter() - .map(|p| p.version.clone()) - .collect::>() - .join(", "), - (Some(_lock), true) => "unknown lockfile".to_string(), - _ => "no lockfile".to_string(), - }; - - ( - format!( - "{} {}({})", - manifest_version, - if is_git { "(git manifest)" } else { "" }, - lock_version - ), - found_crate_versions, - ) - } - }; - - let crate_version = found_crate_versions - .into_iter() - .map(|v| semver::Version::parse(&v).ok()) - .max(); - let suffix = match (crate_version, crate_latest_version(name)) { - (Some(Some(version)), Some(target_version)) => { - let target_version = semver::Version::parse(&target_version).unwrap(); - if version < target_version { - Some(format!( - " ({}, latest: {})", - "outdated".yellow(), - target_version.to_string().green() - )) - } else { - None - } - } - _ => None, - }; - (crate_version_string, suffix) -} - pub fn items(app_dir: Option<&PathBuf>, tauri_dir: Option<&Path>) -> Vec { let mut items = Vec::new(); @@ -210,13 +39,34 @@ pub fn items(app_dir: Option<&PathBuf>, tauri_dir: Option<&Path>) -> Vec
{ + let target_version = semver::Version::parse(&target_version).unwrap(); + if version < target_version { + Some(format!( + " ({}, latest: {})", + "outdated".yellow(), + target_version.to_string().green() + )) + } else { + None + } + } + _ => None, + }; + let item = SectionItem::new().description(format!( "{} {}: {}{}", dep, "[RUST]".dimmed(), - version_string, + version.version, version_suffix .clone() .map(|s| format!(",{s}")) diff --git a/tooling/cli/src/interface/rust/manifest.rs b/tooling/cli/src/interface/rust/manifest.rs index 737e83a0b..fccadb65a 100644 --- a/tooling/cli/src/interface/rust/manifest.rs +++ b/tooling/cli/src/interface/rust/manifest.rs @@ -96,6 +96,18 @@ pub fn read_manifest(manifest_path: &Path) -> crate::Result<(Document, String)> Ok((manifest, manifest_str)) } +pub fn serialize_manifest(manifest: &Document) -> String { + manifest + .to_string() + // apply some formatting fixes + .replace(r#"" ,features =["#, r#"", features = ["#) + .replace(r#"" , features"#, r#"", features"#) + .replace("]}", "] }") + .replace("={", "= {") + .replace("=[", "= [") + .replace(r#"",""#, r#"", ""#) +} + pub fn toml_array(features: &HashSet) -> Array { let mut f = Array::default(); let mut features: Vec = features.iter().map(|f| f.to_string()).collect(); @@ -301,15 +313,7 @@ pub fn rewrite_manifest(config: &Config) -> crate::Result<(Manifest, bool)> { .unwrap() .features; - let new_manifest_str = manifest - .to_string() - // apply some formatting fixes - .replace(r#"" ,features =["#, r#"", features = ["#) - .replace(r#"" , features"#, r#"", features"#) - .replace("]}", "] }") - .replace("={", "= {") - .replace("=[", "= [") - .replace(r#"",""#, r#"", ""#); + let new_manifest_str = serialize_manifest(&manifest); if persist && original_manifest_str != new_manifest_str { let mut manifest_file = diff --git a/tooling/cli/src/migrate/migrations/mod.rs b/tooling/cli/src/migrate/migrations/mod.rs new file mode 100644 index 000000000..59d702820 --- /dev/null +++ b/tooling/cli/src/migrate/migrations/mod.rs @@ -0,0 +1,6 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +pub mod v1; +pub mod v2_rc; diff --git a/tooling/cli/src/migrate/config.rs b/tooling/cli/src/migrate/migrations/v1/config.rs similarity index 100% rename from tooling/cli/src/migrate/config.rs rename to tooling/cli/src/migrate/migrations/v1/config.rs diff --git a/tooling/cli/src/migrate/frontend.rs b/tooling/cli/src/migrate/migrations/v1/frontend.rs similarity index 95% rename from tooling/cli/src/migrate/frontend.rs rename to tooling/cli/src/migrate/migrations/v1/frontend.rs index 3a9d0d97c..4e34cd3c4 100644 --- a/tooling/cli/src/migrate/frontend.rs +++ b/tooling/cli/src/migrate/migrations/v1/frontend.rs @@ -58,11 +58,34 @@ pub fn migrate(app_dir: &Path, tauri_dir: &Path) -> Result<()> { let mut new_npm_packages = Vec::new(); let mut new_cargo_packages = Vec::new(); + let pre = env!("CARGO_PKG_VERSION_PRE"); + let npm_version = if pre.is_empty() { + format!("{}.0.0", env!("CARGO_PKG_VERSION_MAJOR")) + } else { + format!( + "{}.{}.{}-{}.0", + env!("CARGO_PKG_VERSION_MAJOR"), + env!("CARGO_PKG_VERSION_MINOR"), + env!("CARGO_PKG_VERSION_PATCH"), + pre.split('.').next().unwrap() + ) + }; + let pm = PackageManager::from_project(app_dir) .into_iter() .next() .unwrap_or(PackageManager::Npm); + for pkg in ["@tauri-apps/cli", "@tauri-apps/api"] { + let version = pm + .current_package_version(pkg, app_dir) + .unwrap_or_default() + .unwrap_or_default(); + if version.starts_with("1") { + new_npm_packages.push(format!("{pkg}@^{npm_version}")); + } + } + 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(); @@ -86,7 +109,7 @@ pub fn migrate(app_dir: &Path, tauri_dir: &Path) -> Result<()> { new_npm_packages.sort(); new_npm_packages.dedup(); if !new_npm_packages.is_empty() { - pm.install(&new_npm_packages) + pm.install(&new_npm_packages, app_dir) .context("Error installing new npm packages")?; } diff --git a/tooling/cli/src/migrate/manifest.rs b/tooling/cli/src/migrate/migrations/v1/manifest.rs similarity index 93% rename from tooling/cli/src/migrate/manifest.rs rename to tooling/cli/src/migrate/migrations/v1/manifest.rs index cd23f8c6d..b834544d5 100644 --- a/tooling/cli/src/migrate/manifest.rs +++ b/tooling/cli/src/migrate/migrations/v1/manifest.rs @@ -2,14 +2,17 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use crate::{interface::rust::manifest::read_manifest, Result}; +use crate::{ + interface::rust::manifest::{read_manifest, serialize_manifest}, + Result, +}; use anyhow::Context; use itertools::Itertools; use tauri_utils_v1::config::Allowlist; use toml_edit::{Document, Entry, Item, Table, TableLike, Value}; -use std::{fs::File, io::Write, path::Path}; +use std::path::Path; const CRATE_TYPES: [&str; 3] = ["lib", "staticlib", "cdylib"]; @@ -18,20 +21,8 @@ pub fn migrate(tauri_dir: &Path) -> Result<()> { let (mut manifest, _) = read_manifest(&manifest_path)?; migrate_manifest(&mut manifest)?; - let mut manifest_file = - File::create(&manifest_path).with_context(|| "failed to open Cargo.toml for rewrite")?; - manifest_file.write_all( - manifest - .to_string() - // apply some formatting fixes - .replace(r#"" ,features =["#, r#"", features = ["#) - .replace(r#"" , features"#, r#"", features"#) - .replace("]}", "] }") - .replace("={", "= {") - .replace("=[", "= [") - .as_bytes(), - )?; - manifest_file.flush()?; + std::fs::write(&manifest_path, serialize_manifest(&manifest)) + .context("failed to rewrite Cargo manifest")?; Ok(()) } @@ -44,7 +35,7 @@ fn migrate_manifest(manifest: &mut Document) -> Result<()> { .entry("dependencies") .or_insert(Item::Table(Table::new())) .as_table_mut() - .expect("manifest dependencies isn't a table"); + .context("manifest dependencies isn't a table")?; migrate_dependency(dependencies, "tauri", &version, &features_to_remove()); @@ -53,7 +44,7 @@ fn migrate_manifest(manifest: &mut Document) -> Result<()> { .entry("build-dependencies") .or_insert(Item::Table(Table::new())) .as_table_mut() - .expect("manifest build-dependencies isn't a table"); + .context("manifest build-dependencies isn't a table")?; migrate_dependency(build_dependencies, "tauri-build", &version, &[]); diff --git a/tooling/cli/src/migrate/migrations/v1/mod.rs b/tooling/cli/src/migrate/migrations/v1/mod.rs new file mode 100644 index 000000000..ad9523f55 --- /dev/null +++ b/tooling/cli/src/migrate/migrations/v1/mod.rs @@ -0,0 +1,36 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use crate::{ + helpers::app_paths::{app_dir, tauri_dir}, + Result, +}; + +use anyhow::Context; + +mod config; +mod frontend; +mod manifest; + +pub fn run() -> Result<()> { + let tauri_dir = tauri_dir(); + let app_dir = app_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.clone(), + branch: None, + tag: None, + rev: None, + }) + .with_context(|| format!("Could not migrate plugin '{plugin}'"))?; + } + + Ok(()) +} diff --git a/tooling/cli/src/migrate/migrations/v2_rc.rs b/tooling/cli/src/migrate/migrations/v2_rc.rs new file mode 100644 index 000000000..54f9118d6 --- /dev/null +++ b/tooling/cli/src/migrate/migrations/v2_rc.rs @@ -0,0 +1,196 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use crate::{ + helpers::{ + app_paths::{app_dir, tauri_dir}, + npm::PackageManager, + }, + interface::rust::manifest::{read_manifest, serialize_manifest}, + Result, +}; + +use std::{fs::read_to_string, path::Path}; + +use anyhow::Context; +use toml_edit::{Document, Item, Table, TableLike, Value}; + +pub fn run() -> Result<()> { + let app_dir = app_dir(); + let tauri_dir = tauri_dir(); + + let manifest_path = tauri_dir.join("Cargo.toml"); + let (mut manifest, _) = read_manifest(&manifest_path)?; + migrate_manifest(&mut manifest)?; + + migrate_permissions(&tauri_dir)?; + + migrate_npm_dependencies(app_dir)?; + + std::fs::write(&manifest_path, serialize_manifest(&manifest)) + .context("failed to rewrite Cargo manifest")?; + + Ok(()) +} + +fn migrate_npm_dependencies(app_dir: &Path) -> Result<()> { + let pm = PackageManager::from_project(app_dir) + .into_iter() + .next() + .unwrap_or(PackageManager::Npm); + + let mut install_deps = Vec::new(); + for pkg in [ + "@tauri-apps/cli", + "@tauri-apps/api", + "@tauri-apps/plugin-authenticator", + "@tauri-apps/plugin-autostart", + "@tauri-apps/plugin-barcode-scanner", + "@tauri-apps/plugin-biometric", + "@tauri-apps/plugin-cli", + "@tauri-apps/plugin-clipboard-manager", + "@tauri-apps/plugin-deep-link", + "@tauri-apps/plugin-dialog", + "@tauri-apps/plugin-fs", + "@tauri-apps/plugin-global-shortcut", + "@tauri-apps/plugin-http", + "@tauri-apps/plugin-log", + "@tauri-apps/plugin-nfc", + "@tauri-apps/plugin-notification", + "@tauri-apps/plugin-os", + "@tauri-apps/plugin-positioner", + "@tauri-apps/plugin-process", + "@tauri-apps/plugin-shell", + "@tauri-apps/plugin-sql", + "@tauri-apps/plugin-store", + "@tauri-apps/plugin-stronghold", + "@tauri-apps/plugin-updater", + "@tauri-apps/plugin-upload", + "@tauri-apps/plugin-websocket", + "@tauri-apps/plugin-window-state", + ] { + let version = pm + .current_package_version(pkg, app_dir) + .unwrap_or_default() + .unwrap_or_default(); + if version.starts_with("1") { + install_deps.push(format!("{pkg}@^2.0.0-rc.0")); + } + } + + if !install_deps.is_empty() { + pm.install(&install_deps, app_dir)?; + } + + Ok(()) +} + +fn migrate_permissions(tauri_dir: &Path) -> Result<()> { + let core_plugins = [ + "app", + "event", + "image", + "menu", + "path", + "resources", + "tray", + "webview", + "window", + ]; + + for entry in walkdir::WalkDir::new(tauri_dir.join("capabilities")) { + let entry = entry?; + let path = entry.path(); + if path.extension().map_or(false, |ext| ext == "json") { + let mut capability = read_to_string(path).context("failed to read capability")?; + for plugin in core_plugins { + capability = capability.replace(&format!("\"{plugin}:"), &format!("\"core:{plugin}:")); + } + std::fs::write(path, capability).context("failed to rewrite capability")?; + } + } + Ok(()) +} + +fn migrate_manifest(manifest: &mut Document) -> Result<()> { + let version = "2.0.0-rc.0"; + + let dependencies = manifest + .as_table_mut() + .entry("dependencies") + .or_insert(Item::Table(Table::new())) + .as_table_mut() + .context("manifest dependencies isn't a table")?; + + for dep in [ + "tauri", + "tauri-plugin-authenticator", + "tauri-plugin-autostart", + "tauri-plugin-barcode-scanner", + "tauri-plugin-biometric", + "tauri-plugin-cli", + "tauri-plugin-clipboard-manager", + "tauri-plugin-deep-link", + "tauri-plugin-dialog", + "tauri-plugin-fs", + "tauri-plugin-global-shortcut", + "tauri-plugin-http", + "tauri-plugin-localhost", + "tauri-plugin-log", + "tauri-plugin-nfc", + "tauri-plugin-notification", + "tauri-plugin-os", + "tauri-plugin-persisted-scope", + "tauri-plugin-positioner", + "tauri-plugin-process", + "tauri-plugin-shell", + "tauri-plugin-single-instance", + "tauri-plugin-sql", + "tauri-plugin-store", + "tauri-plugin-stronghold", + "tauri-plugin-updater", + "tauri-plugin-upload", + "tauri-plugin-websocket", + "tauri-plugin-window-state", + ] { + migrate_dependency(dependencies, dep, version); + } + + let build_dependencies = manifest + .as_table_mut() + .entry("build-dependencies") + .or_insert(Item::Table(Table::new())) + .as_table_mut() + .context("manifest build-dependencies isn't a table")?; + + migrate_dependency(build_dependencies, "tauri-build", version); + + Ok(()) +} + +fn migrate_dependency(dependencies: &mut Table, name: &str, version: &str) { + let item = dependencies.entry(name).or_insert(Item::None); + + // do not rewrite if dependency uses workspace inheritance + if item + .get("workspace") + .and_then(|v| v.as_bool()) + .unwrap_or_default() + { + log::info!("`{name}` dependency has workspace inheritance enabled. The features array won't be automatically rewritten."); + return; + } + + if let Some(dep) = item.as_table_mut() { + migrate_dependency_table(dep, version); + } else if let Some(Value::InlineTable(table)) = item.as_value_mut() { + migrate_dependency_table(table, version); + } else if item.as_str().is_some() { + *item = Item::Value(version.into()); + } +} + +fn migrate_dependency_table(dep: &mut D, version: &str) { + *dep.entry("version").or_insert(Item::None) = Item::Value(version.into()); +} diff --git a/tooling/cli/src/migrate/mod.rs b/tooling/cli/src/migrate/mod.rs index 29351319c..96839deb2 100644 --- a/tooling/cli/src/migrate/mod.rs +++ b/tooling/cli/src/migrate/mod.rs @@ -3,32 +3,50 @@ // SPDX-License-Identifier: MIT use crate::{ - helpers::app_paths::{app_dir, tauri_dir}, + helpers::{ + app_paths::tauri_dir, + cargo_manifest::{crate_version, CargoLock, CargoManifest}, + }, + interface::rust::get_workspace_dir, Result, }; + +use std::{fs::read_to_string, str::FromStr}; + use anyhow::Context; -mod config; -mod frontend; -mod manifest; +mod migrations; pub fn command() -> Result<()> { let tauri_dir = tauri_dir(); - let app_dir = app_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)?; + let manifest_contents = + read_to_string(tauri_dir.join("Cargo.toml")).context("failed to read Cargo manifest")?; + let manifest = toml::from_str::(&manifest_contents) + .context("failed to parse Cargo manifest")?; - // Add plugins - for plugin in migrated.plugins { - crate::add::command(crate::add::Options { - plugin: plugin.clone(), - branch: None, - tag: None, - rev: None, - }) - .with_context(|| format!("Could not migrate plugin '{plugin}'"))? + let workspace_dir = get_workspace_dir()?; + let lock_path = workspace_dir.join("Cargo.lock"); + let lock = if lock_path.exists() { + let lockfile_contents = read_to_string(lock_path).context("failed to read Cargo lockfile")?; + let lock = + toml::from_str::(&lockfile_contents).context("failed to parse Cargo lockfile")?; + Some(lock) + } else { + None + }; + + let tauri_version = crate_version(&tauri_dir, Some(&manifest), lock.as_ref(), "tauri").version; + let tauri_version = semver::Version::from_str(&tauri_version)?; + + if tauri_version.major == 1 { + migrations::v1::run().context("failed to migrate from v1")?; + } else if tauri_version.major == 2 { + if let Some((pre, _number)) = tauri_version.pre.as_str().split_once('.') { + if pre == "beta" { + migrations::v2_rc::run().context("failed to migrate from v2 beta to rc")?; + } + } } Ok(())