Jelajahi Sumber

feat(cli): improve bundle identifier validation, closes #4589 (#4596)

Lucas Fernandes Nogueira 3 tahun lalu
induk
melakukan
8e3e7fc646

+ 6 - 0
.changes/improve-bundle-identifier-validation.md

@@ -0,0 +1,6 @@
+---
+"cli.rs": patch
+"cli.js": patch
+---
+
+Improved bundle identifier validation showing the exact source of the configuration value.

+ 5 - 0
.changes/utils-read-platform-config.md

@@ -0,0 +1,5 @@
+---
+"tauri-utils": patch
+---
+
+Added `config::parse::read_platform` and `config::parse::get_platform_config_filename`.

+ 19 - 5
core/tauri-utils/src/config/parse.rs

@@ -82,20 +82,34 @@ pub enum ConfigError {
 /// [JSON Merge Patch (RFC 7396)]: https://datatracker.ietf.org/doc/html/rfc7396.
 pub fn read_from(root_dir: PathBuf) -> Result<Value, ConfigError> {
   let mut config: Value = parse_value(root_dir.join("tauri.conf.json"))?;
+  if let Some(platform_config) = read_platform(root_dir)? {
+    merge(&mut config, &platform_config);
+  }
+  Ok(config)
+}
 
-  let platform_config_filename = if cfg!(target_os = "macos") {
+/// Gets the platform configuration file name.
+pub fn get_platform_config_filename() -> &'static str {
+  if cfg!(target_os = "macos") {
     "tauri.macos.conf.json"
   } else if cfg!(windows) {
     "tauri.windows.conf.json"
   } else {
     "tauri.linux.conf.json"
-  };
-  let platform_config_path = root_dir.join(platform_config_filename);
+  }
+}
+
+/// Reads the platform-specific configuration file from the given root directory if it exists.
+///
+/// Check [`read_from`] for more information.
+pub fn read_platform(root_dir: PathBuf) -> Result<Option<Value>, ConfigError> {
+  let platform_config_path = root_dir.join(get_platform_config_filename());
   if does_supported_extension_exist(&platform_config_path) {
     let platform_config: Value = parse_value(platform_config_path)?;
-    merge(&mut config, &platform_config);
+    Ok(Some(platform_config))
+  } else {
+    Ok(None)
   }
-  Ok(config)
 }
 
 /// Check if a supported config file exists at path.

+ 31 - 9
tooling/cli/src/build.rs

@@ -6,7 +6,7 @@ use crate::{
   helpers::{
     app_paths::{app_dir, tauri_dir},
     command_env,
-    config::{get as get_config, AppUrl, WindowUrl},
+    config::{get as get_config, AppUrl, WindowUrl, MERGE_CONFIG_EXTENSION_NAME},
     updater_signature::sign_file_from_env_variables,
   },
   interface::{AppInterface, AppSettings, Interface},
@@ -54,15 +54,22 @@ pub struct Options {
 }
 
 pub fn command(mut options: Options) -> Result<()> {
-  options.config = if let Some(config) = &options.config {
-    Some(if config.starts_with('{') {
-      config.to_string()
+  let (merge_config, merge_config_path) = if let Some(config) = &options.config {
+    if config.starts_with('{') {
+      (Some(config.to_string()), None)
     } else {
-      std::fs::read_to_string(&config).with_context(|| "failed to read custom configuration")?
-    })
+      (
+        Some(
+          std::fs::read_to_string(&config)
+            .with_context(|| "failed to read custom configuration")?,
+        ),
+        Some(config.clone()),
+      )
+    }
   } else {
-    None
+    (None, None)
   };
+  options.config = merge_config;
 
   let tauri_path = tauri_dir();
   set_current_dir(&tauri_path).with_context(|| "failed to change current working directory")?;
@@ -72,8 +79,19 @@ pub fn command(mut options: Options) -> Result<()> {
   let config_guard = config.lock().unwrap();
   let config_ = config_guard.as_ref().unwrap();
 
+  let bundle_identifier_source = match config_.find_bundle_identifier_overwriter() {
+    Some(source) if source == MERGE_CONFIG_EXTENSION_NAME => {
+      merge_config_path.unwrap_or_else(|| source.into())
+    }
+    Some(source) => source.into(),
+    None => "tauri.conf.json".into(),
+  };
+
   if config_.tauri.bundle.identifier == "com.tauri.dev" {
-    error!("You must change the bundle identifier in `tauri.conf.json > tauri > bundle > identifier`. The default value `com.tauri.dev` is not allowed as it must be unique across applications.");
+    error!(
+      "You must change the bundle identifier in `{} > tauri > bundle > identifier`. The default value `com.tauri.dev` is not allowed as it must be unique across applications.",
+      bundle_identifier_source
+    );
     std::process::exit(1);
   }
 
@@ -84,7 +102,11 @@ pub fn command(mut options: Options) -> Result<()> {
     .chars()
     .any(|ch| !(ch.is_alphanumeric() || ch == '-' || ch == '.'))
   {
-    error!("You must change the bundle identifier in `tauri.conf.json > tauri > bundle > identifier`. The bundle identifier string must contain only alphanumeric characters (A–Z, a–z, and 0–9), hyphens (-), and periods (.).");
+    error!(
+      "The bundle identifier \"{}\" set in `{} > tauri > bundle > identifier`. The bundle identifier string must contain only alphanumeric characters (A–Z, a–z, and 0–9), hyphens (-), and periods (.).",
+      config_.tauri.bundle.identifier,
+      bundle_identifier_source
+    );
     std::process::exit(1);
   }
 

+ 61 - 4
tooling/cli/src/helpers/config.rs

@@ -10,12 +10,54 @@ use serde_json::Value as JsonValue;
 pub use tauri_utils::config::*;
 
 use std::{
+  collections::HashMap,
   env::set_var,
   process::exit,
   sync::{Arc, Mutex},
 };
 
-pub type ConfigHandle = Arc<Mutex<Option<Config>>>;
+pub const MERGE_CONFIG_EXTENSION_NAME: &str = "--config";
+
+pub struct ConfigMetadata {
+  /// The actual configuration, merged with any extension.
+  inner: Config,
+  /// The config extensions (platform-specific config files or the config CLI argument).
+  /// Maps the extension name to its value.
+  extensions: HashMap<&'static str, JsonValue>,
+}
+
+impl std::ops::Deref for ConfigMetadata {
+  type Target = Config;
+
+  #[inline(always)]
+  fn deref(&self) -> &Config {
+    &self.inner
+  }
+}
+
+impl ConfigMetadata {
+  /// Checks which config is overwriting the bundle identifier.
+  pub fn find_bundle_identifier_overwriter(&self) -> Option<&'static str> {
+    for (ext, config) in &self.extensions {
+      if let Some(identifier) = config
+        .as_object()
+        .and_then(|config| config.get("tauri"))
+        .and_then(|tauri_config| tauri_config.as_object())
+        .and_then(|tauri_config| tauri_config.get("bundle"))
+        .and_then(|bundle_config| bundle_config.as_object())
+        .and_then(|bundle_config| bundle_config.get("identifier"))
+        .and_then(|id| id.as_str())
+      {
+        if identifier == self.inner.tauri.bundle.identifier {
+          return Some(ext);
+        }
+      }
+    }
+    None
+  }
+}
+
+pub type ConfigHandle = Arc<Mutex<Option<ConfigMetadata>>>;
 
 pub fn wix_settings(config: WixConfig) -> tauri_bundler::WixSettings {
   tauri_bundler::WixSettings {
@@ -63,13 +105,24 @@ fn get_internal(merge_config: Option<&str>, reload: bool) -> crate::Result<Confi
     return Ok(config_handle().clone());
   }
 
-  let mut config = tauri_utils::config::parse::read_from(super::app_paths::tauri_dir())?;
+  let tauri_dir = super::app_paths::tauri_dir();
+  let mut config = tauri_utils::config::parse::parse_value(tauri_dir.join("tauri.conf.json"))?;
+  let mut extensions = HashMap::new();
+
+  if let Some(platform_config) = tauri_utils::config::parse::read_platform(tauri_dir)? {
+    merge(&mut config, &platform_config);
+    extensions.insert(
+      tauri_utils::config::parse::get_platform_config_filename(),
+      platform_config,
+    );
+  }
 
   if let Some(merge_config) = merge_config {
     let merge_config: JsonValue =
       serde_json::from_str(merge_config).with_context(|| "failed to parse config to merge")?;
     merge(&mut config, &merge_config);
-  }
+    extensions.insert(MERGE_CONFIG_EXTENSION_NAME, merge_config);
+  };
 
   let schema: JsonValue = serde_json::from_str(include_str!("../../schema.json"))?;
   let mut scope = valico::json_schema::Scope::new();
@@ -93,7 +146,11 @@ fn get_internal(merge_config: Option<&str>, reload: bool) -> crate::Result<Confi
 
   let config: Config = serde_json::from_value(config)?;
   set_var("TAURI_CONFIG", serde_json::to_string(&config)?);
-  *config_handle().lock().unwrap() = Some(config);
+
+  *config_handle().lock().unwrap() = Some(ConfigMetadata {
+    inner: config,
+    extensions,
+  });
 
   Ok(config_handle().clone())
 }