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
This commit is contained in:
Lucas Fernandes Nogueira 2024-07-30 16:32:59 -03:00 committed by GitHub
parent 426d14bb41
commit d5511c3117
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 619 additions and 312 deletions

6
.changes/rc-migration.md Normal file
View File

@ -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`.

View File

@ -2551,7 +2551,7 @@
] ]
}, },
"changelog": { "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 <https://www.debian.org/doc/debian-policy/ch-docs.html#changelog-files-and-release-notes>",
"type": [ "type": [
"string", "string",
"null" "null"
@ -2565,28 +2565,28 @@
] ]
}, },
"preInstallScript": { "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 <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>",
"type": [ "type": [
"string", "string",
"null" "null"
] ]
}, },
"postInstallScript": { "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 <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>",
"type": [ "type": [
"string", "string",
"null" "null"
] ]
}, },
"preRemoveScript": { "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 <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>",
"type": [ "type": [
"string", "string",
"null" "null"
] ]
}, },
"postRemoveScript": { "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 <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>",
"type": [ "type": [
"string", "string",
"null" "null"
@ -2667,28 +2667,28 @@
] ]
}, },
"preInstallScript": { "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 <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>",
"type": [ "type": [
"string", "string",
"null" "null"
] ]
}, },
"postInstallScript": { "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 <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>",
"type": [ "type": [
"string", "string",
"null" "null"
] ]
}, },
"preRemoveScript": { "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 <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>",
"type": [ "type": [
"string", "string",
"null" "null"
] ]
}, },
"postRemoveScript": { "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 <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>",
"type": [ "type": [
"string", "string",
"null" "null"

View File

@ -5181,6 +5181,7 @@ dependencies = [
"toml_edit 0.22.6", "toml_edit 0.22.6",
"ureq", "ureq",
"url", "url",
"walkdir",
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]

View File

@ -98,6 +98,7 @@ oxc_allocator = "0.16"
oxc_ast = "0.16" oxc_ast = "0.16"
magic_string = "0.3" magic_string = "0.3"
phf = { version = "0.11", features = ["macros"] } phf = { version = "0.11", features = ["macros"] }
walkdir = "2"
[target."cfg(windows)".dependencies.windows-sys] [target."cfg(windows)".dependencies.windows-sys]
version = "0.52" version = "0.52"

View File

@ -2551,7 +2551,7 @@
] ]
}, },
"changelog": { "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 <https://www.debian.org/doc/debian-policy/ch-docs.html#changelog-files-and-release-notes>",
"type": [ "type": [
"string", "string",
"null" "null"
@ -2565,28 +2565,28 @@
] ]
}, },
"preInstallScript": { "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 <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>",
"type": [ "type": [
"string", "string",
"null" "null"
] ]
}, },
"postInstallScript": { "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 <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>",
"type": [ "type": [
"string", "string",
"null" "null"
] ]
}, },
"preRemoveScript": { "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 <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>",
"type": [ "type": [
"string", "string",
"null" "null"
] ]
}, },
"postRemoveScript": { "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 <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>",
"type": [ "type": [
"string", "string",
"null" "null"
@ -2667,28 +2667,28 @@
] ]
}, },
"preInstallScript": { "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 <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>",
"type": [ "type": [
"string", "string",
"null" "null"
] ]
}, },
"postInstallScript": { "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 <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>",
"type": [ "type": [
"string", "string",
"null" "null"
] ]
}, },
"preRemoveScript": { "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 <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>",
"type": [ "type": [
"string", "string",
"null" "null"
] ]
}, },
"postRemoveScript": { "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 <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>",
"type": [ "type": [
"string", "string",
"null" "null"

View File

@ -146,7 +146,7 @@ pub fn command(options: Options) -> Result<()> {
(None, None, None, None) => npm_name, (None, None, None, None) => npm_name,
_ => anyhow::bail!("Only one of --tag, --rev and --branch can be specified"), _ => 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 { let _ = acl::permission::add::command(acl::permission::add::Options {

View File

@ -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<String>,
}
#[derive(Deserialize)]
pub struct CargoLock {
pub package: Vec<CargoLockPackage>,
}
#[derive(Clone, Deserialize)]
pub struct CargoManifestDependencyPackage {
pub version: Option<String>,
pub git: Option<String>,
pub branch: Option<String>,
pub rev: Option<String>,
pub path: Option<PathBuf>,
}
#[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<String, CargoManifestDependency>,
}
pub struct CrateVersion {
pub version: String,
pub found_crate_versions: Vec<String>,
}
pub fn crate_version(
tauri_dir: &Path,
manifest: Option<&CargoManifest>,
lock: Option<&CargoLock>,
name: &str,
) -> CrateVersion {
let crate_lock_packages: Vec<CargoLockPackage> = 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::<CargoManifest>(&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::<Vec<String>>()
.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,
}
}

View File

@ -4,6 +4,7 @@
pub mod app_paths; pub mod app_paths;
pub mod cargo; pub mod cargo;
pub mod cargo_manifest;
pub mod config; pub mod config;
pub mod flock; pub mod flock;
pub mod framework; pub mod framework;

View File

@ -81,7 +81,7 @@ impl PackageManager {
} }
} }
pub fn install(&self, dependencies: &[String]) -> crate::Result<()> { pub fn install<P: AsRef<Path>>(&self, dependencies: &[String], app_dir: P) -> crate::Result<()> {
let dependencies_str = if dependencies.len() > 1 { let dependencies_str = if dependencies.len() > 1 {
"dependencies" "dependencies"
} else { } else {
@ -100,6 +100,7 @@ impl PackageManager {
.cross_command() .cross_command()
.arg("add") .arg("add")
.args(dependencies) .args(dependencies)
.current_dir(app_dir)
.status() .status()
.with_context(|| format!("failed to run {self}"))?; .with_context(|| format!("failed to run {self}"))?;
@ -109,4 +110,71 @@ impl PackageManager {
Ok(()) Ok(())
} }
pub fn current_package_version<P: AsRef<Path>>(
&self,
name: &str,
app_dir: P,
) -> crate::Result<Option<String>> {
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)
}
}
} }

View File

@ -6,7 +6,7 @@ use super::SectionItem;
use super::{env_nodejs::manager_version, VersionMetadata}; use super::{env_nodejs::manager_version, VersionMetadata};
use colored::Colorize; use colored::Colorize;
use serde::Deserialize; use serde::Deserialize;
use std::path::{Path, PathBuf}; use std::path::PathBuf;
use crate::helpers::{cross_command, npm::PackageManager}; use crate::helpers::{cross_command, npm::PackageManager};
@ -87,73 +87,6 @@ fn npm_latest_version(pm: &PackageManager, name: &str) -> crate::Result<Option<S
} }
} }
fn npm_package_version<P: AsRef<Path>>(
pm: &PackageManager,
name: &str,
app_dir: P,
) -> crate::Result<Option<String>> {
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<T: AsRef<str>>(app_dir_entries: &[T]) -> PackageManager { fn get_package_manager<T: AsRef<str>>(app_dir_entries: &[T]) -> PackageManager {
let mut use_npm = false; let mut use_npm = false;
let mut use_pnpm = false; let mut use_pnpm = false;
@ -244,7 +177,8 @@ pub fn items(app_dir: Option<&PathBuf>, metadata: &VersionMetadata) -> Vec<Secti
let app_dir = app_dir.clone(); let app_dir = app_dir.clone();
let item = SectionItem::new().action(move || { let item = SectionItem::new().action(move || {
let version = version.clone().unwrap_or_else(|| { let version = version.clone().unwrap_or_else(|| {
npm_package_version(&package_manager, package, &app_dir) package_manager
.current_package_version(package, &app_dir)
.unwrap_or_default() .unwrap_or_default()
.unwrap_or_default() .unwrap_or_default()
}); });

View File

@ -3,53 +3,14 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
use super::{ActionResult, SectionItem}; use super::{ActionResult, SectionItem};
use crate::interface::rust::get_workspace_dir; use crate::{
helpers::cargo_manifest::{crate_version, CargoLock, CargoManifest},
interface::rust::get_workspace_dir,
};
use colored::Colorize; use colored::Colorize;
use serde::Deserialize;
use std::collections::HashMap;
use std::fmt::Write;
use std::fs::read_to_string; use std::fs::read_to_string;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
#[derive(Clone, Deserialize)]
struct CargoLockPackage {
name: String,
version: String,
source: Option<String>,
}
#[derive(Deserialize)]
struct CargoLock {
package: Vec<CargoLockPackage>,
}
#[derive(Clone, Deserialize)]
struct CargoManifestDependencyPackage {
version: Option<String>,
git: Option<String>,
branch: Option<String>,
rev: Option<String>,
path: Option<PathBuf>,
}
#[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<String, CargoManifestDependency>,
}
fn crate_latest_version(name: &str) -> Option<String> { fn crate_latest_version(name: &str) -> Option<String> {
let url = format!("https://docs.rs/crate/{name}/"); let url = format!("https://docs.rs/crate/{name}/");
match ureq::get(&url).call() { match ureq::get(&url).call() {
@ -61,138 +22,6 @@ fn crate_latest_version(name: &str) -> Option<String> {
} }
} }
fn crate_version(
tauri_dir: &Path,
manifest: Option<&CargoManifest>,
lock: Option<&CargoLock>,
name: &str,
) -> (String, Option<String>) {
let crate_lock_packages: Vec<CargoLockPackage> = 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::<CargoManifest>(&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::<Vec<String>>()
.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<SectionItem> { pub fn items(app_dir: Option<&PathBuf>, tauri_dir: Option<&Path>) -> Vec<SectionItem> {
let mut items = Vec::new(); let mut items = Vec::new();
@ -210,13 +39,34 @@ pub fn items(app_dir: Option<&PathBuf>, tauri_dir: Option<&Path>) -> Vec<Section
.and_then(|s| toml::from_str(&s).ok()); .and_then(|s| toml::from_str(&s).ok());
for dep in ["tauri", "tauri-build", "wry", "tao"] { for dep in ["tauri", "tauri-build", "wry", "tao"] {
let (version_string, version_suffix) = let version = crate_version(tauri_dir, manifest.as_ref(), lock.as_ref(), dep);
crate_version(tauri_dir, manifest.as_ref(), lock.as_ref(), dep); let crate_version = version
.found_crate_versions
.into_iter()
.map(|v| semver::Version::parse(&v).ok())
.max();
let version_suffix = match (crate_version, crate_latest_version(dep)) {
(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,
};
let item = SectionItem::new().description(format!( let item = SectionItem::new().description(format!(
"{} {}: {}{}", "{} {}: {}{}",
dep, dep,
"[RUST]".dimmed(), "[RUST]".dimmed(),
version_string, version.version,
version_suffix version_suffix
.clone() .clone()
.map(|s| format!(",{s}")) .map(|s| format!(",{s}"))

View File

@ -96,6 +96,18 @@ pub fn read_manifest(manifest_path: &Path) -> crate::Result<(Document, String)>
Ok((manifest, manifest_str)) 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<String>) -> Array { pub fn toml_array(features: &HashSet<String>) -> Array {
let mut f = Array::default(); let mut f = Array::default();
let mut features: Vec<String> = features.iter().map(|f| f.to_string()).collect(); let mut features: Vec<String> = features.iter().map(|f| f.to_string()).collect();
@ -301,15 +313,7 @@ pub fn rewrite_manifest(config: &Config) -> crate::Result<(Manifest, bool)> {
.unwrap() .unwrap()
.features; .features;
let new_manifest_str = manifest let new_manifest_str = serialize_manifest(&manifest);
.to_string()
// apply some formatting fixes
.replace(r#"" ,features =["#, r#"", features = ["#)
.replace(r#"" , features"#, r#"", features"#)
.replace("]}", "] }")
.replace("={", "= {")
.replace("=[", "= [")
.replace(r#"",""#, r#"", ""#);
if persist && original_manifest_str != new_manifest_str { if persist && original_manifest_str != new_manifest_str {
let mut manifest_file = let mut manifest_file =

View File

@ -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;

View File

@ -58,11 +58,34 @@ 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();
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) let pm = PackageManager::from_project(app_dir)
.into_iter() .into_iter()
.next() .next()
.unwrap_or(PackageManager::Npm); .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() { 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();
@ -86,7 +109,7 @@ pub fn migrate(app_dir: &Path, tauri_dir: &Path) -> Result<()> {
new_npm_packages.sort(); new_npm_packages.sort();
new_npm_packages.dedup(); new_npm_packages.dedup();
if !new_npm_packages.is_empty() { if !new_npm_packages.is_empty() {
pm.install(&new_npm_packages) pm.install(&new_npm_packages, app_dir)
.context("Error installing new npm packages")?; .context("Error installing new npm packages")?;
} }

View File

@ -2,14 +2,17 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT // 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 anyhow::Context;
use itertools::Itertools; use itertools::Itertools;
use tauri_utils_v1::config::Allowlist; use tauri_utils_v1::config::Allowlist;
use toml_edit::{Document, Entry, Item, Table, TableLike, Value}; 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"]; 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)?; let (mut manifest, _) = read_manifest(&manifest_path)?;
migrate_manifest(&mut manifest)?; migrate_manifest(&mut manifest)?;
let mut manifest_file = std::fs::write(&manifest_path, serialize_manifest(&manifest))
File::create(&manifest_path).with_context(|| "failed to open Cargo.toml for rewrite")?; .context("failed to rewrite Cargo manifest")?;
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()?;
Ok(()) Ok(())
} }
@ -44,7 +35,7 @@ fn migrate_manifest(manifest: &mut Document) -> Result<()> {
.entry("dependencies") .entry("dependencies")
.or_insert(Item::Table(Table::new())) .or_insert(Item::Table(Table::new()))
.as_table_mut() .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()); migrate_dependency(dependencies, "tauri", &version, &features_to_remove());
@ -53,7 +44,7 @@ fn migrate_manifest(manifest: &mut Document) -> Result<()> {
.entry("build-dependencies") .entry("build-dependencies")
.or_insert(Item::Table(Table::new())) .or_insert(Item::Table(Table::new()))
.as_table_mut() .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, &[]); migrate_dependency(build_dependencies, "tauri-build", &version, &[]);

View File

@ -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(())
}

View File

@ -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<D: TableLike>(dep: &mut D, version: &str) {
*dep.entry("version").or_insert(Item::None) = Item::Value(version.into());
}

View File

@ -3,32 +3,50 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
use crate::{ 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, Result,
}; };
use std::{fs::read_to_string, str::FromStr};
use anyhow::Context; use anyhow::Context;
mod config; mod migrations;
mod frontend;
mod manifest;
pub fn command() -> Result<()> { pub fn command() -> Result<()> {
let tauri_dir = tauri_dir(); let tauri_dir = tauri_dir();
let app_dir = app_dir();
let migrated = config::migrate(&tauri_dir).context("Could not migrate config")?; let manifest_contents =
manifest::migrate(&tauri_dir).context("Could not migrate manifest")?; read_to_string(tauri_dir.join("Cargo.toml")).context("failed to read Cargo manifest")?;
frontend::migrate(app_dir, &tauri_dir)?; let manifest = toml::from_str::<CargoManifest>(&manifest_contents)
.context("failed to parse Cargo manifest")?;
// Add plugins let workspace_dir = get_workspace_dir()?;
for plugin in migrated.plugins { let lock_path = workspace_dir.join("Cargo.lock");
crate::add::command(crate::add::Options { let lock = if lock_path.exists() {
plugin: plugin.clone(), let lockfile_contents = read_to_string(lock_path).context("failed to read Cargo lockfile")?;
branch: None, let lock =
tag: None, toml::from_str::<CargoLock>(&lockfile_contents).context("failed to parse Cargo lockfile")?;
rev: None, Some(lock)
}) } else {
.with_context(|| format!("Could not migrate plugin '{plugin}'"))? 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(()) Ok(())