Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changes/check-plugin-versions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"tauri-cli": minor:feat
"@tauri-apps/cli": minor:feat
---

Check installed plugin NPM/crate versions for incompatible releases.
29 changes: 22 additions & 7 deletions crates/tauri-cli/src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ use crate::{
bundle::BundleFormat,
helpers::{
self,
app_paths::tauri_dir,
app_paths::{frontend_dir, tauri_dir},
config::{get as get_config, ConfigHandle, FrontendDist},
},
info::plugins::check_mismatched_packages,
interface::{rust::get_cargo_target_dir, AppInterface, Interface},
ConfigValue, Result,
};
Expand Down Expand Up @@ -70,6 +71,11 @@ pub struct Options {
/// On subsequent runs, it's recommended to disable this setting again.
#[clap(long)]
pub skip_stapling: bool,
/// Do not error out if a version mismatch is detected on a Tauri package.
///
/// Only use this when you are sure the mismatch is incorrectly detected as version mismatched Tauri packages can lead to unknown behavior.
#[clap(long)]
pub ignore_version_mismatches: bool,
}

pub fn command(mut options: Options, verbosity: u8) -> Result<()> {
Expand Down Expand Up @@ -131,6 +137,18 @@ pub fn setup(
mobile: bool,
) -> Result<()> {
let tauri_path = tauri_dir();

// TODO: Maybe optimize this to run in parallel in the future
// see https://github.com/tauri-apps/tauri/pull/13993#discussion_r2280697117
log::info!("Looking up installed tauri packages to check mismatched versions...");
if let Err(error) = check_mismatched_packages(frontend_dir(), tauri_path) {
if options.ignore_version_mismatches {
log::error!("{error}");
} else {
return Err(error);
}
}

set_current_dir(tauri_path).with_context(|| "failed to change current working directory")?;

let config_guard = config.lock().unwrap();
Expand All @@ -141,24 +159,21 @@ pub fn setup(
.unwrap_or_else(|| "tauri.conf.json".into());

if config_.identifier == "com.tauri.dev" {
log::error!(
"You must change the bundle identifier in `{} identifier`. The default value `com.tauri.dev` is not allowed as it must be unique across applications.",
bundle_identifier_source
anyhow::bail!(
"You must change the bundle identifier in `{bundle_identifier_source} identifier`. The default value `com.tauri.dev` is not allowed as it must be unique across applications.",
);
std::process::exit(1);
}

if config_
.identifier
.chars()
.any(|ch| !(ch.is_alphanumeric() || ch == '-' || ch == '.'))
{
log::error!(
anyhow::bail!(
"The bundle identifier \"{}\" set in `{} identifier`. The bundle identifier string must contain only alphanumeric characters (A-Z, a-z, and 0-9), hyphens (-), and periods (.).",
config_.identifier,
bundle_identifier_source
);
std::process::exit(1);
}

if config_.identifier.ends_with(".app") {
Expand Down
8 changes: 8 additions & 0 deletions crates/tauri-cli/src/dev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::{
get as get_config, reload as reload_config, BeforeDevCommand, ConfigHandle, FrontendDist,
},
},
info::plugins::check_mismatched_packages,
interface::{AppInterface, ExitReason, Interface},
CommandExt, ConfigValue, Result,
};
Expand Down Expand Up @@ -135,6 +136,13 @@ fn command_internal(mut options: Options) -> Result<()> {

pub fn setup(interface: &AppInterface, options: &mut Options, config: ConfigHandle) -> Result<()> {
let tauri_path = tauri_dir();

std::thread::spawn(|| {
if let Err(error) = check_mismatched_packages(frontend_dir(), tauri_path) {
log::error!("{error}");
}
});

set_current_dir(tauri_path).with_context(|| "failed to change current working directory")?;

if let Some(before_dev) = config
Expand Down
157 changes: 156 additions & 1 deletion crates/tauri-cli/src/helpers/npm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
// SPDX-License-Identifier: MIT

use anyhow::Context;
use serde::Deserialize;

use crate::helpers::cross_command;
use std::{fmt::Display, path::Path, process::Command};
use std::{collections::HashMap, fmt::Display, path::Path, process::Command};

pub fn manager_version(package_manager: &str) -> Option<String> {
cross_command(package_manager)
Expand Down Expand Up @@ -197,6 +198,7 @@ impl PackageManager {
Ok(())
}

// TODO: Use `current_package_versions` as much as possible for better speed
pub fn current_package_version<P: AsRef<Path>>(
&self,
name: &str,
Expand Down Expand Up @@ -254,4 +256,157 @@ impl PackageManager {
Ok(None)
}
}

pub fn current_package_versions(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably want to migrate the other ones to use this one as much as possible to speed things up, could push that to the future though

&self,
packages: &[String],
frontend_dir: &Path,
) -> crate::Result<HashMap<String, semver::Version>> {
let output = match self {
PackageManager::Yarn => return yarn_package_versions(packages, frontend_dir),
PackageManager::YarnBerry => return yarn_berry_package_versions(packages, frontend_dir),
PackageManager::Pnpm => cross_command("pnpm")
.arg("list")
.args(packages)
.args(["--json", "--depth", "0"])
.current_dir(frontend_dir)
.output()?,
// Bun and Deno don't support `list` command
PackageManager::Npm | PackageManager::Bun | PackageManager::Deno => cross_command("npm")
.arg("list")
.args(packages)
.args(["--json", "--depth", "0"])
.current_dir(frontend_dir)
.output()?,
};

let mut versions = HashMap::new();
let stdout = String::from_utf8_lossy(&output.stdout);
if !output.status.success() {
return Ok(versions);
}

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct ListOutput {
#[serde(default)]
dependencies: HashMap<String, ListDependency>,
#[serde(default)]
dev_dependencies: HashMap<String, ListDependency>,
}

#[derive(Deserialize)]
struct ListDependency {
version: String,
}

let json: ListOutput = serde_json::from_str(&stdout)?;
for (package, dependency) in json.dependencies.into_iter().chain(json.dev_dependencies) {
let version = dependency.version;
if let Ok(version) = semver::Version::parse(&version) {
versions.insert(package, version);
} else {
log::error!("Failed to parse version `{version}` for NPM package `{package}`");
}
}
Ok(versions)
}
}

fn yarn_package_versions(
packages: &[String],
frontend_dir: &Path,
) -> crate::Result<HashMap<String, semver::Version>> {
let output = cross_command("yarn")
.arg("list")
.args(packages)
.args(["--json", "--depth", "0"])
.current_dir(frontend_dir)
.output()?;

let mut versions = HashMap::new();
let stdout = String::from_utf8_lossy(&output.stdout);
if !output.status.success() {
return Ok(versions);
}

#[derive(Deserialize)]
struct YarnListOutput {
data: YarnListOutputData,
}

#[derive(Deserialize)]
struct YarnListOutputData {
trees: Vec<YarnListOutputDataTree>,
}

#[derive(Deserialize)]
struct YarnListOutputDataTree {
name: String,
}

for line in stdout.lines() {
if let Ok(tree) = serde_json::from_str::<YarnListOutput>(line) {
for tree in tree.data.trees {
let Some((name, version)) = tree.name.rsplit_once('@') else {
continue;
};
if let Ok(version) = semver::Version::parse(version) {
versions.insert(name.to_owned(), version);
} else {
log::error!("Failed to parse version `{version}` for NPM package `{name}`");
}
}
return Ok(versions);
}
}

Ok(versions)
}

fn yarn_berry_package_versions(
packages: &[String],
frontend_dir: &Path,
) -> crate::Result<HashMap<String, semver::Version>> {
let output = cross_command("yarn")
.args(["info", "--json"])
.current_dir(frontend_dir)
.output()?;

let mut versions = HashMap::new();
let stdout = String::from_utf8_lossy(&output.stdout);
if !output.status.success() {
return Ok(versions);
}

#[derive(Deserialize)]
struct YarnBerryInfoOutput {
value: String,
children: YarnBerryInfoOutputChildren,
}

#[derive(Deserialize)]
#[serde(rename_all = "PascalCase")]
struct YarnBerryInfoOutputChildren {
version: String,
}

for line in stdout.lines() {
if let Ok(info) = serde_json::from_str::<YarnBerryInfoOutput>(line) {
let Some((name, _)) = info.value.rsplit_once('@') else {
continue;
};
if !packages.iter().any(|package| package == name) {
continue;
}
let version = info.children.version;
if let Ok(version) = semver::Version::parse(&version) {
versions.insert(name.to_owned(), version);
} else {
log::error!("Failed to parse version `{version}` for NPM package `{name}`");
}
}
}

Ok(versions)
}
2 changes: 1 addition & 1 deletion crates/tauri-cli/src/info/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ mod env_system;
mod ios;
mod packages_nodejs;
mod packages_rust;
mod plugins;
pub mod plugins;

#[derive(Deserialize)]
struct JsCliVersionMetadata {
Expand Down
Loading
Loading