Skip to content

Commit c093cb2

Browse files
committed
feat(cli): check plugin versions for incompatibilities
check core plugin versions for incompatibilities between Cargo and NPM releases a plugin NPM/cargo version is considered "incompatible" if their major or minor versions are not equal on dev we show an warning on build we error out (with a `--ignore-incompatible-plugins` flag to prevent that) this is an idea from @oscartbeaumont we've seen several plugin changes that require updates for both the cargo and the NPM releases of a plugin, and if they are not in sync, the functionality does not work e.g. tauri-apps/plugins-workspace#2573 where the change actually breaks the app updater if you miss the NPM update
1 parent 196ace3 commit c093cb2

File tree

9 files changed

+161
-2
lines changed

9 files changed

+161
-2
lines changed

.changes/check-plugin-versions.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"tauri-cli": minor:feat
3+
"@tauri-apps/cli": minor:feat
4+
---
5+
6+
Check installed plugin NPM/crate versions for incompatible releases.

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/tauri-cli/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ memchr = "2"
110110
tempfile = "3"
111111
uuid = { version = "1", features = ["v5"] }
112112
rand = "0.9"
113+
rayon = "1"
113114

114115
[dev-dependencies]
115116
insta = "1"

crates/tauri-cli/src/build.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ use crate::{
66
bundle::BundleFormat,
77
helpers::{
88
self,
9-
app_paths::tauri_dir,
9+
app_paths::{frontend_dir, tauri_dir},
1010
config::{get as get_config, ConfigHandle, FrontendDist},
11+
npm::PackageManager,
1112
},
1213
interface::{rust::get_cargo_target_dir, AppInterface, Interface},
1314
ConfigValue, Result,
@@ -60,6 +61,11 @@ pub struct Options {
6061
/// Skip prompting for values
6162
#[clap(long, env = "CI")]
6263
pub ci: bool,
64+
/// Do not error out if aversion incompatibility is detected on an installed plugin.
65+
///
66+
/// Only use this when you are sure the mismatch is incorrectly detected as incompatible plugin versions can lead to unknown behavior.
67+
#[clap(long)]
68+
pub ignore_incompatible_plugins: bool,
6369
}
6470

6571
pub fn command(mut options: Options, verbosity: u8) -> Result<()> {
@@ -121,6 +127,32 @@ pub fn setup(
121127
mobile: bool,
122128
) -> Result<()> {
123129
let tauri_path = tauri_dir();
130+
131+
log::info!("Looking up installed plugins to check incompatible versions...");
132+
let installed_plugins = crate::info::plugins::installed_plugins(
133+
frontend_dir(),
134+
tauri_path,
135+
PackageManager::from_project(frontend_dir()),
136+
);
137+
let incompatible_plugins = installed_plugins.incompatible();
138+
if !incompatible_plugins.is_empty() {
139+
let incompatible_text = incompatible_plugins
140+
.iter()
141+
.map(|p| {
142+
format!(
143+
"{} (v{}) : {} (v{})",
144+
p.crate_name, p.crate_version, p.npm_name, p.npm_version
145+
)
146+
})
147+
.collect::<Vec<_>>()
148+
.join("\n");
149+
if options.ignore_incompatible_plugins {
150+
log::error!("Found incompatible Tauri plugins. Make sure the NPM and crate versions are on the same major/minor releases:\n{}", incompatible_text);
151+
} else {
152+
anyhow::bail!("Found incompatible Tauri plugins. Make sure the NPM and crate versions are on the same major/minor releases:\n{}", incompatible_text);
153+
}
154+
}
155+
124156
set_current_dir(tauri_path).with_context(|| "failed to change current working directory")?;
125157

126158
let config_guard = config.lock().unwrap();

crates/tauri-cli/src/dev.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::{
99
config::{
1010
get as get_config, reload as reload_config, BeforeDevCommand, ConfigHandle, FrontendDist,
1111
},
12+
npm::PackageManager,
1213
},
1314
interface::{AppInterface, ExitReason, Interface},
1415
CommandExt, ConfigValue, Result,
@@ -135,6 +136,29 @@ fn command_internal(mut options: Options) -> Result<()> {
135136

136137
pub fn setup(interface: &AppInterface, options: &mut Options, config: ConfigHandle) -> Result<()> {
137138
let tauri_path = tauri_dir();
139+
140+
std::thread::spawn(|| {
141+
let installed_plugins = crate::info::plugins::installed_plugins(
142+
frontend_dir(),
143+
tauri_path,
144+
PackageManager::from_project(frontend_dir()),
145+
);
146+
let incompatible_plugins = installed_plugins.incompatible();
147+
if !incompatible_plugins.is_empty() {
148+
let incompatible_text = incompatible_plugins
149+
.iter()
150+
.map(|p| {
151+
format!(
152+
"{} (v{}) : {} (v{})",
153+
p.crate_name, p.crate_version, p.npm_name, p.npm_version
154+
)
155+
})
156+
.collect::<Vec<_>>()
157+
.join("\n");
158+
log::warn!("Found incompatible Tauri plugins. Make sure the NPM and crate versions are on the same major/minor releases:\n{}", incompatible_text);
159+
}
160+
});
161+
138162
set_current_dir(tauri_path).with_context(|| "failed to change current working directory")?;
139163

140164
if let Some(before_dev) = config

crates/tauri-cli/src/info/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ mod env_system;
2020
mod ios;
2121
mod packages_nodejs;
2222
mod packages_rust;
23-
mod plugins;
23+
pub mod plugins;
2424

2525
#[derive(Deserialize)]
2626
struct JsCliVersionMetadata {

crates/tauri-cli/src/info/plugins.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,91 @@ use crate::{
1616
interface::rust::get_workspace_dir,
1717
};
1818

19+
use rayon::prelude::*;
20+
1921
use super::{packages_nodejs, packages_rust, SectionItem};
2022

23+
#[derive(Debug)]
24+
pub struct InstalledPlugin {
25+
pub crate_name: String,
26+
pub npm_name: String,
27+
pub crate_version: semver::Version,
28+
pub npm_version: semver::Version,
29+
}
30+
31+
#[derive(Debug)]
32+
pub struct InstalledPlugins(Vec<InstalledPlugin>);
33+
34+
impl InstalledPlugins {
35+
pub fn incompatible(&self) -> Vec<&InstalledPlugin> {
36+
self
37+
.0
38+
.iter()
39+
.filter(|p| {
40+
p.crate_version.major != p.npm_version.major || p.crate_version.minor != p.npm_version.minor
41+
})
42+
.collect()
43+
}
44+
}
45+
46+
pub fn installed_plugins(
47+
frontend_dir: &Path,
48+
tauri_dir: &Path,
49+
package_manager: PackageManager,
50+
) -> InstalledPlugins {
51+
let manifest: Option<CargoManifest> =
52+
if let Ok(manifest_contents) = fs::read_to_string(tauri_dir.join("Cargo.toml")) {
53+
toml::from_str(&manifest_contents).ok()
54+
} else {
55+
None
56+
};
57+
58+
let lock: Option<CargoLock> = get_workspace_dir()
59+
.ok()
60+
.and_then(|p| fs::read_to_string(p.join("Cargo.lock")).ok())
61+
.and_then(|s| toml::from_str(&s).ok());
62+
63+
let installed_plugins = helpers::plugins::known_plugins()
64+
.into_par_iter()
65+
.filter_map(|(plugin, _)| {
66+
let crate_name = format!("tauri-plugin-{plugin}");
67+
let crate_version = crate_version(tauri_dir, manifest.as_ref(), lock.as_ref(), &crate_name);
68+
let Some(crate_version) = crate_version.version.and_then(|v| {
69+
semver::Version::parse(&v)
70+
.inspect_err(|_| {
71+
log::error!("Failed to parse version `{v}` for crate `{crate_name}`");
72+
})
73+
.ok()
74+
}) else {
75+
return None;
76+
};
77+
78+
let npm_name = format!("@tauri-apps/plugin-{plugin}");
79+
let npm_version = package_manager
80+
.current_package_version(&npm_name, &frontend_dir)
81+
.unwrap_or_default();
82+
if let Some(npm_version) = npm_version.and_then(|v| {
83+
semver::Version::parse(&v)
84+
.inspect_err(|_| {
85+
log::error!("Failed to parse version `{v}` for NPM package `{npm_name}`");
86+
})
87+
.ok()
88+
}) {
89+
return Some(InstalledPlugin {
90+
crate_name,
91+
npm_name,
92+
crate_version,
93+
npm_version,
94+
});
95+
}
96+
97+
None
98+
})
99+
.collect();
100+
101+
InstalledPlugins(installed_plugins)
102+
}
103+
21104
pub fn items(
22105
frontend_dir: Option<&PathBuf>,
23106
tauri_dir: Option<&Path>,

crates/tauri-cli/src/mobile/android/build.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ pub struct Options {
7878
/// e.g. `tauri android build -- [runnerArgs]`.
7979
#[clap(last(true))]
8080
pub args: Vec<String>,
81+
/// Do not error out if aversion incompatibility is detected on an installed plugin.
82+
///
83+
/// Only use this when you are sure the mismatch is incorrectly detected as incompatible plugin versions can lead to unknown behavior.
84+
#[clap(long)]
85+
pub ignore_incompatible_plugins: bool,
8186
}
8287

8388
impl From<Options> for BuildOptions {
@@ -92,6 +97,7 @@ impl From<Options> for BuildOptions {
9297
config: options.config,
9398
args: options.args,
9499
ci: options.ci,
100+
ignore_incompatible_plugins: options.ignore_incompatible_plugins,
95101
}
96102
}
97103
}

crates/tauri-cli/src/mobile/ios/build.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ pub struct Options {
8888
/// e.g. `tauri ios build -- [runnerArgs]`.
8989
#[clap(last(true))]
9090
pub args: Vec<String>,
91+
/// Do not error out if aversion incompatibility is detected on an installed plugin.
92+
///
93+
/// Only use this when you are sure the mismatch is incorrectly detected as incompatible plugin versions can lead to unknown behavior.
94+
#[clap(long)]
95+
pub ignore_incompatible_plugins: bool,
9196
}
9297

9398
#[derive(Debug, Clone, Copy, ValueEnum)]
@@ -132,6 +137,7 @@ impl From<Options> for BuildOptions {
132137
config: options.config,
133138
args: options.args,
134139
ci: options.ci,
140+
ignore_incompatible_plugins: options.ignore_incompatible_plugins,
135141
}
136142
}
137143
}

0 commit comments

Comments
 (0)