浏览代码

fix(cli): regression on --config not accepting file paths (#8783)

* fix(cli): regression on --config not accepting file paths

* enhance dev server config parsing

* use serde_json::json!

* pass config to setup
Lucas Fernandes Nogueira 1 年之前
父节点
当前提交
fb0d997117

+ 6 - 0
.changes/fix-config-arg.md

@@ -0,0 +1,6 @@
+---
+"tauri-cli": patch:bug
+"@tauri-apps/cli": patch:bug
+---
+
+Fixes a regression on the `--config` argument not accepting file paths.

+ 8 - 8
examples/api/src-tauri/Cargo.lock

@@ -3677,7 +3677,7 @@ checksum = "69758bda2e78f098e4ccb393021a0963bb3442eac05f135c30f61b7370bbafae"
 
 [[package]]
 name = "tauri"
-version = "2.0.0-beta.1"
+version = "2.0.0-beta.2"
 dependencies = [
  "anyhow",
  "bytes",
@@ -3728,7 +3728,7 @@ dependencies = [
 
 [[package]]
 name = "tauri-build"
-version = "2.0.0-beta.0"
+version = "2.0.0-beta.1"
 dependencies = [
  "anyhow",
  "cargo_toml",
@@ -3750,7 +3750,7 @@ dependencies = [
 
 [[package]]
 name = "tauri-codegen"
-version = "2.0.0-beta.0"
+version = "2.0.0-beta.1"
 dependencies = [
  "base64",
  "brotli",
@@ -3774,7 +3774,7 @@ dependencies = [
 
 [[package]]
 name = "tauri-macros"
-version = "2.0.0-beta.0"
+version = "2.0.0-beta.1"
 dependencies = [
  "heck",
  "proc-macro2",
@@ -3786,7 +3786,7 @@ dependencies = [
 
 [[package]]
 name = "tauri-plugin"
-version = "2.0.0-beta.0"
+version = "2.0.0-beta.1"
 dependencies = [
  "anyhow",
  "glob",
@@ -3825,7 +3825,7 @@ dependencies = [
 
 [[package]]
 name = "tauri-runtime"
-version = "2.0.0-beta.0"
+version = "2.0.0-beta.1"
 dependencies = [
  "gtk",
  "http",
@@ -3841,7 +3841,7 @@ dependencies = [
 
 [[package]]
 name = "tauri-runtime-wry"
-version = "2.0.0-beta.0"
+version = "2.0.0-beta.1"
 dependencies = [
  "cocoa",
  "gtk",
@@ -3861,7 +3861,7 @@ dependencies = [
 
 [[package]]
 name = "tauri-utils"
-version = "2.0.0-beta.0"
+version = "2.0.0-beta.1"
 dependencies = [
  "aes-gcm",
  "brotli",

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

@@ -6,12 +6,11 @@ use crate::{
   helpers::{
     app_paths::{app_dir, tauri_dir},
     command_env,
-    config::{get as get_config, FrontendDist, HookCommand, MERGE_CONFIG_EXTENSION_NAME},
-    resolve_merge_config,
+    config::{get as get_config, ConfigHandle, FrontendDist, HookCommand},
     updater_signature::{secret_key as updater_secret_key, sign_file},
   },
   interface::{AppInterface, AppSettings, Interface},
-  CommandExt, Result,
+  CommandExt, ConfigValue, Result,
 };
 use anyhow::{bail, Context};
 use base64::Engine;
@@ -57,7 +56,7 @@ pub struct Options {
   pub bundles: Option<Vec<String>>,
   /// JSON string or path to JSON file to merge with tauri.conf.json
   #[clap(short, long)]
-  pub config: Option<String>,
+  pub config: Option<ConfigValue>,
   /// Command line arguments passed to the runner. Use `--` to explicitly mark the start of the arguments.
   pub args: Vec<String>,
   /// Skip prompting for values
@@ -75,14 +74,14 @@ pub fn command(mut options: Options, verbosity: u8) -> Result<()> {
     .map(Target::from_triple)
     .unwrap_or_else(Target::current);
 
-  let config = get_config(target, options.config.as_deref())?;
+  let config = get_config(target, options.config.as_ref().map(|c| &c.0))?;
 
   let mut interface = AppInterface::new(
     config.lock().unwrap().as_ref().unwrap(),
     options.target.clone(),
   )?;
 
-  setup(target, &interface, &mut options, false)?;
+  setup(&interface, &mut options, config.clone(), false)?;
 
   let config_guard = config.lock().unwrap();
   let config_ = config_guard.as_ref().unwrap();
@@ -267,27 +266,20 @@ pub fn command(mut options: Options, verbosity: u8) -> Result<()> {
 }
 
 pub fn setup(
-  target: Target,
   interface: &AppInterface,
   options: &mut Options,
+  config: ConfigHandle,
   mobile: bool,
 ) -> Result<()> {
-  let (merge_config, merge_config_path) = resolve_merge_config(&options.config)?;
-  options.config = merge_config;
-
-  let config = get_config(target, options.config.as_deref())?;
-
   let tauri_path = tauri_dir();
   set_current_dir(tauri_path).with_context(|| "failed to change current working directory")?;
 
   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(source),
-    Some(source) => source,
-    None => "tauri.conf.json".into(),
-  };
+  let bundle_identifier_source = config_
+    .find_bundle_identifier_overwriter()
+    .unwrap_or_else(|| "tauri.conf.json".into());
 
   if config_.identifier == "com.tauri.dev" {
     error!(

+ 25 - 18
tooling/cli/src/dev.rs

@@ -6,11 +6,12 @@ use crate::{
   helpers::{
     app_paths::{app_dir, tauri_dir},
     command_env,
-    config::{get as get_config, reload as reload_config, BeforeDevCommand, FrontendDist},
-    resolve_merge_config,
+    config::{
+      get as get_config, reload as reload_config, BeforeDevCommand, ConfigHandle, FrontendDist,
+    },
   },
   interface::{AppInterface, DevProcess, ExitReason, Interface},
-  CommandExt, Result,
+  CommandExt, ConfigValue, Result,
 };
 
 use anyhow::{bail, Context};
@@ -59,7 +60,7 @@ pub struct Options {
   pub exit_on_panic: bool,
   /// JSON string or path to JSON file to merge with tauri.conf.json
   #[clap(short, long)]
-  pub config: Option<String>,
+  pub config: Option<ConfigValue>,
   /// Run the code in release mode
   #[clap(long = "release")]
   pub release_mode: bool,
@@ -100,14 +101,14 @@ fn command_internal(mut options: Options) -> Result<()> {
     .map(Target::from_triple)
     .unwrap_or_else(Target::current);
 
-  let config = get_config(target, options.config.as_deref())?;
+  let config = get_config(target, options.config.as_ref().map(|c| &c.0))?;
 
   let mut interface = AppInterface::new(
     config.lock().unwrap().as_ref().unwrap(),
     options.target.clone(),
   )?;
 
-  setup(target, &interface, &mut options, false)?;
+  setup(&interface, &mut options, config, false)?;
 
   let exit_on_panic = options.exit_on_panic;
   let no_watch = options.no_watch;
@@ -160,16 +161,11 @@ pub fn local_ip_address(force: bool) -> &'static IpAddr {
 }
 
 pub fn setup(
-  target: Target,
   interface: &AppInterface,
   options: &mut Options,
+  config: ConfigHandle,
   mobile: bool,
 ) -> Result<()> {
-  let (merge_config, _merge_config_path) = resolve_merge_config(&options.config)?;
-  options.config = merge_config;
-
-  let config = get_config(target, options.config.as_deref())?;
-
   let tauri_path = tauri_dir();
   set_current_dir(tauri_path).with_context(|| "failed to change current working directory")?;
 
@@ -344,15 +340,26 @@ pub fn setup(
         let server_url = format!("http://{server_url}");
         dev_url = Some(server_url.parse().unwrap());
 
-        if let Some(c) = &options.config {
-          let mut c: tauri_utils::config::Config = serde_json::from_str(c)?;
-          c.build.dev_url = dev_url.clone();
-          options.config = Some(serde_json::to_string(&c).unwrap());
+        if let Some(c) = &mut options.config {
+          if let Some(build) = c
+            .0
+            .as_object_mut()
+            .and_then(|root| root.get_mut("build"))
+            .and_then(|build| build.as_object_mut())
+          {
+            build.insert("devUrl".into(), server_url.into());
+          }
         } else {
-          options.config = Some(format!(r#"{{ "build": {{ "devUrl": "{server_url}" }} }}"#))
+          options
+            .config
+            .replace(crate::ConfigValue(serde_json::json!({
+              "build": {
+                "devUrl": server_url
+              }
+            })));
         }
 
-        reload_config(options.config.as_deref())?;
+        reload_config(options.config.as_ref().map(|c| &c.0))?;
       }
     }
   }

+ 10 - 9
tooling/cli/src/helpers/config.rs

@@ -2,7 +2,6 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
-use anyhow::Context;
 use json_patch::merge;
 use log::error;
 use serde_json::Value as JsonValue;
@@ -118,7 +117,7 @@ fn config_handle() -> &'static ConfigHandle {
 
 /// Gets the static parsed config from `tauri.conf.json`.
 fn get_internal(
-  merge_config: Option<&str>,
+  merge_config: Option<&serde_json::Value>,
   reload: bool,
   target: Target,
 ) -> crate::Result<ConfigHandle> {
@@ -143,11 +142,10 @@ fn get_internal(
   }
 
   if let Some(merge_config) = merge_config {
-    set_var("TAURI_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.into(), merge_config);
+    let merge_config_str = serde_json::to_string(&merge_config).unwrap();
+    set_var("TAURI_CONFIG", merge_config_str);
+    merge(&mut config, merge_config);
+    extensions.insert(MERGE_CONFIG_EXTENSION_NAME.into(), merge_config.clone());
   };
 
   if config_path.extension() == Some(OsStr::new("json"))
@@ -198,11 +196,14 @@ fn get_internal(
   Ok(config_handle().clone())
 }
 
-pub fn get(target: Target, merge_config: Option<&str>) -> crate::Result<ConfigHandle> {
+pub fn get(
+  target: Target,
+  merge_config: Option<&serde_json::Value>,
+) -> crate::Result<ConfigHandle> {
   get_internal(merge_config, false, target)
 }
 
-pub fn reload(merge_config: Option<&str>) -> crate::Result<ConfigHandle> {
+pub fn reload(merge_config: Option<&serde_json::Value>) -> crate::Result<ConfigHandle> {
   let target = config_handle()
     .lock()
     .unwrap()

+ 0 - 15
tooling/cli/src/helpers/mod.rs

@@ -11,8 +11,6 @@ pub mod template;
 pub mod updater_signature;
 pub mod web_dev_server;
 
-use anyhow::Context;
-
 use std::{
   collections::HashMap,
   path::{Path, PathBuf},
@@ -54,16 +52,3 @@ pub fn cross_command(bin: &str) -> Command {
   let cmd = Command::new(bin);
   cmd
 }
-
-pub fn resolve_merge_config(
-  config: &Option<String>,
-) -> crate::Result<(Option<String>, Option<String>)> {
-  match config {
-    Some(config) if config.starts_with('{') => Ok((Some(config.to_string()), None)),
-    Some(config) => Ok((
-      Some(std::fs::read_to_string(config).with_context(|| "failed to read custom configuration")?),
-      Some(config.clone()),
-    )),
-    None => Ok((None, None)),
-  }
-}

+ 12 - 9
tooling/cli/src/interface/rust.rs

@@ -28,9 +28,12 @@ use tauri_bundler::{
 use tauri_utils::config::{parse::is_configuration_file, DeepLinkProtocol};
 
 use super::{AppSettings, DevProcess, ExitReason, Interface};
-use crate::helpers::{
-  app_paths::{app_dir, tauri_dir},
-  config::{nsis_settings, reload as reload_config, wix_settings, BundleResources, Config},
+use crate::{
+  helpers::{
+    app_paths::{app_dir, tauri_dir},
+    config::{nsis_settings, reload as reload_config, wix_settings, BundleResources, Config},
+  },
+  ConfigValue,
 };
 use tauri_utils::{display_path, platform::Target};
 
@@ -48,7 +51,7 @@ pub struct Options {
   pub target: Option<String>,
   pub features: Option<Vec<String>>,
   pub args: Vec<String>,
-  pub config: Option<String>,
+  pub config: Option<ConfigValue>,
   pub no_watch: bool,
 }
 
@@ -85,7 +88,7 @@ pub struct MobileOptions {
   pub debug: bool,
   pub features: Option<Vec<String>>,
   pub args: Vec<String>,
-  pub config: Option<String>,
+  pub config: Option<ConfigValue>,
   pub no_watch: bool,
 }
 
@@ -186,7 +189,7 @@ impl Interface for Rust {
       rx.recv().unwrap();
       Ok(())
     } else {
-      let config = options.config.clone();
+      let config = options.config.clone().map(|c| c.0);
       let run = Arc::new(|rust: &mut Rust| {
         let on_exit = on_exit.clone();
         rust.run_dev(options.clone(), run_args.clone(), move |status, reason| {
@@ -215,7 +218,7 @@ impl Interface for Rust {
       runner(options)?;
       Ok(())
     } else {
-      let config = options.config.clone();
+      let config = options.config.clone().map(|c| c.0);
       let run = Arc::new(|_rust: &mut Rust| runner(options.clone()));
       self.run_dev_watcher(config, run)
     }
@@ -438,7 +441,7 @@ impl Rust {
 
   fn run_dev_watcher<F: Fn(&mut Rust) -> crate::Result<Box<dyn DevProcess + Send>>>(
     &mut self,
-    config: Option<String>,
+    config: Option<serde_json::Value>,
     run: Arc<F>,
   ) -> crate::Result<()> {
     let child = run(self)?;
@@ -503,7 +506,7 @@ impl Rust {
 
           if !ignore_matcher.is_ignore(&event_path, event_path.is_dir()) {
             if is_configuration_file(self.app_settings.target, &event_path) {
-              match reload_config(config.as_deref()) {
+              match reload_config(config.as_ref()) {
                 Ok(config) => {
                   info!("Tauri configuration changed. Rewriting manifest...");
                   *self.app_settings.manifest.lock().unwrap() =

+ 30 - 0
tooling/cli/src/lib.rs

@@ -11,6 +11,7 @@
   html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png"
 )]
 
+use anyhow::Context;
 pub use anyhow::Result;
 
 mod add;
@@ -37,10 +38,39 @@ use std::process::{exit, Command, ExitStatus, Output, Stdio};
 use std::{
   ffi::OsString,
   fmt::Display,
+  fs::read_to_string,
   io::BufRead,
+  path::PathBuf,
+  str::FromStr,
   sync::{Arc, Mutex},
 };
 
+/// Tauri configuration argument option.
+#[derive(Debug, Clone)]
+pub struct ConfigValue(pub(crate) serde_json::Value);
+
+impl FromStr for ConfigValue {
+  type Err = anyhow::Error;
+
+  fn from_str(config: &str) -> std::result::Result<Self, Self::Err> {
+    if config.starts_with('{') {
+      Ok(Self(
+        serde_json::from_str(config).context("invalid configuration JSON")?,
+      ))
+    } else {
+      let path = PathBuf::from(config);
+      if path.exists() {
+        Ok(Self(serde_json::from_str(
+          &read_to_string(&path)
+            .with_context(|| format!("invalid configuration at file {config}"))?,
+        )?))
+      } else {
+        anyhow::bail!("provided configuration path does not exist")
+      }
+    }
+  }
+}
+
 #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
 pub enum RunMode {
   Desktop,

+ 6 - 14
tooling/cli/src/mobile/android/build.rs

@@ -11,11 +11,11 @@ use crate::{
   helpers::{
     app_paths::tauri_dir,
     config::{get as get_tauri_config, ConfigHandle},
-    flock, resolve_merge_config,
+    flock,
   },
   interface::{AppInterface, AppSettings, Interface, Options as InterfaceOptions},
   mobile::{write_options, CliOptions},
-  Result,
+  ConfigValue, Result,
 };
 use clap::{ArgAction, Parser};
 
@@ -51,7 +51,7 @@ pub struct Options {
   pub features: Option<Vec<String>>,
   /// JSON string or path to JSON file to merge with tauri.conf.json
   #[clap(short, long)]
-  pub config: Option<String>,
+  pub config: Option<ConfigValue>,
   /// Whether to split the APKs and AABs per ABIs.
   #[clap(long)]
   pub split_per_abi: bool,
@@ -81,12 +81,9 @@ impl From<Options> for BuildOptions {
   }
 }
 
-pub fn command(mut options: Options, noise_level: NoiseLevel) -> Result<()> {
+pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
   delete_codegen_vars();
 
-  let (merge_config, _merge_config_path) = resolve_merge_config(&options.config)?;
-  options.config = merge_config;
-
   let mut build_options: BuildOptions = options.clone().into();
   build_options.target = Some(
     Target::all()
@@ -98,7 +95,7 @@ pub fn command(mut options: Options, noise_level: NoiseLevel) -> Result<()> {
 
   let tauri_config = get_tauri_config(
     tauri_utils::platform::Target::Android,
-    options.config.as_deref(),
+    options.config.as_ref().map(|c| &c.0),
   )?;
   let (interface, app, config, metadata) = {
     let tauri_config_guard = tauri_config.lock().unwrap();
@@ -178,12 +175,7 @@ fn run_build(
     options.aab = true;
   }
 
-  crate::build::setup(
-    tauri_utils::platform::Target::Android,
-    &interface,
-    &mut build_options,
-    true,
-  )?;
+  crate::build::setup(&interface, &mut build_options, tauri_config.clone(), true)?;
 
   let interface_options = InterfaceOptions {
     debug: build_options.debug,

+ 6 - 14
tooling/cli/src/mobile/android/dev.rs

@@ -11,11 +11,11 @@ use crate::{
   helpers::{
     app_paths::tauri_dir,
     config::{get as get_tauri_config, ConfigHandle},
-    flock, resolve_merge_config,
+    flock,
   },
   interface::{AppInterface, AppSettings, Interface, MobileOptions, Options as InterfaceOptions},
   mobile::{write_options, CliOptions, DevChild, DevProcess},
-  Result,
+  ConfigValue, Result,
 };
 use clap::{ArgAction, Parser};
 
@@ -57,7 +57,7 @@ pub struct Options {
   exit_on_panic: bool,
   /// JSON string or path to JSON file to merge with tauri.conf.json
   #[clap(short, long)]
-  pub config: Option<String>,
+  pub config: Option<ConfigValue>,
   /// Run the code in release mode
   #[clap(long = "release")]
   pub release_mode: bool,
@@ -110,15 +110,12 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
   result
 }
 
-fn run_command(mut options: Options, noise_level: NoiseLevel) -> Result<()> {
+fn run_command(options: Options, noise_level: NoiseLevel) -> Result<()> {
   delete_codegen_vars();
 
-  let (merge_config, _merge_config_path) = resolve_merge_config(&options.config)?;
-  options.config = merge_config;
-
   let tauri_config = get_tauri_config(
     tauri_utils::platform::Target::Android,
-    options.config.as_deref(),
+    options.config.as_ref().map(|c| &c.0),
   )?;
 
   let env = env()?;
@@ -195,12 +192,7 @@ fn run_dev(
     options.force_ip_prompt,
   )?;
 
-  crate::dev::setup(
-    tauri_utils::platform::Target::Android,
-    &interface,
-    &mut dev_options,
-    true,
-  )?;
+  crate::dev::setup(&interface, &mut dev_options, tauri_config.clone(), true)?;
 
   let interface_options = InterfaceOptions {
     debug: !dev_options.release_mode,

+ 6 - 14
tooling/cli/src/mobile/ios/build.rs

@@ -11,11 +11,11 @@ use crate::{
   helpers::{
     app_paths::tauri_dir,
     config::{get as get_tauri_config, ConfigHandle},
-    flock, resolve_merge_config,
+    flock,
   },
   interface::{AppInterface, AppSettings, Interface, Options as InterfaceOptions},
   mobile::{write_options, CliOptions},
-  Result,
+  ConfigValue, Result,
 };
 use clap::{ArgAction, Parser};
 
@@ -53,7 +53,7 @@ pub struct Options {
   pub features: Option<Vec<String>>,
   /// JSON string or path to JSON file to merge with tauri.conf.json
   #[clap(short, long)]
-  pub config: Option<String>,
+  pub config: Option<ConfigValue>,
   /// Build number to append to the app version.
   #[clap(long)]
   pub build_number: Option<u32>,
@@ -77,10 +77,7 @@ impl From<Options> for BuildOptions {
   }
 }
 
-pub fn command(mut options: Options, noise_level: NoiseLevel) -> Result<()> {
-  let (merge_config, _merge_config_path) = resolve_merge_config(&options.config)?;
-  options.config = merge_config;
-
+pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
   let mut build_options: BuildOptions = options.clone().into();
   build_options.target = Some(
     Target::all()
@@ -92,7 +89,7 @@ pub fn command(mut options: Options, noise_level: NoiseLevel) -> Result<()> {
 
   let tauri_config = get_tauri_config(
     tauri_utils::platform::Target::Ios,
-    options.config.as_deref(),
+    options.config.as_ref().map(|c| &c.0),
   )?;
   let (interface, app, config) = {
     let tauri_config_guard = tauri_config.lock().unwrap();
@@ -159,12 +156,7 @@ fn run_build(
     Profile::Release
   };
 
-  crate::build::setup(
-    tauri_utils::platform::Target::Ios,
-    &interface,
-    &mut build_options,
-    true,
-  )?;
+  crate::build::setup(&interface, &mut build_options, tauri_config.clone(), true)?;
 
   let app_settings = interface.app_settings();
   let bin_path = app_settings.app_binary_path(&InterfaceOptions {

+ 6 - 14
tooling/cli/src/mobile/ios/dev.rs

@@ -11,11 +11,11 @@ use crate::{
   helpers::{
     app_paths::tauri_dir,
     config::{get as get_tauri_config, ConfigHandle},
-    flock, resolve_merge_config,
+    flock,
   },
   interface::{AppInterface, AppSettings, Interface, MobileOptions, Options as InterfaceOptions},
   mobile::{write_options, CliOptions, DevChild, DevProcess},
-  Result,
+  ConfigValue, Result,
 };
 use clap::{ArgAction, Parser};
 
@@ -44,7 +44,7 @@ pub struct Options {
   exit_on_panic: bool,
   /// JSON string or path to JSON file to merge with tauri.conf.json
   #[clap(short, long)]
-  pub config: Option<String>,
+  pub config: Option<ConfigValue>,
   /// Run the code in release mode
   #[clap(long = "release")]
   pub release_mode: bool,
@@ -97,7 +97,7 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
   result
 }
 
-fn run_command(mut options: Options, noise_level: NoiseLevel) -> Result<()> {
+fn run_command(options: Options, noise_level: NoiseLevel) -> Result<()> {
   if var_os(APPLE_DEVELOPMENT_TEAM_ENV_VAR_NAME).is_none() {
     if let Ok(teams) = find_development_teams() {
       let index = match teams.len() {
@@ -142,9 +142,6 @@ fn run_command(mut options: Options, noise_level: NoiseLevel) -> Result<()> {
     }
   };
 
-  let (merge_config, _merge_config_path) = resolve_merge_config(&options.config)?;
-  options.config = merge_config;
-
   let mut dev_options: DevOptions = options.clone().into();
   let target_triple = device
     .as_ref()
@@ -154,7 +151,7 @@ fn run_command(mut options: Options, noise_level: NoiseLevel) -> Result<()> {
 
   let tauri_config = get_tauri_config(
     tauri_utils::platform::Target::Ios,
-    options.config.as_deref(),
+    options.config.as_ref().map(|c| &c.0),
   )?;
   let (interface, app, config) = {
     let tauri_config_guard = tauri_config.lock().unwrap();
@@ -216,12 +213,7 @@ fn run_dev(
     options.force_ip_prompt,
   )?;
 
-  crate::dev::setup(
-    tauri_utils::platform::Target::Ios,
-    &interface,
-    &mut dev_options,
-    true,
-  )?;
+  crate::dev::setup(&interface, &mut dev_options, tauri_config.clone(), true)?;
 
   let app_settings = interface.app_settings();
   let bin_path = app_settings.app_binary_path(&InterfaceOptions {

+ 20 - 7
tooling/cli/src/mobile/mod.rs

@@ -8,6 +8,7 @@ use crate::{
     config::{get as get_config, reload as reload_config, Config as TauriConfig},
   },
   interface::{AppInterface, AppSettings, DevProcess, Interface, Options as InterfaceOptions},
+  ConfigValue,
 };
 use anyhow::{bail, Result};
 use heck::ToSnekCase;
@@ -152,10 +153,13 @@ impl Default for CliOptions {
 
 fn setup_dev_config(
   target: Target,
-  config_extension: &mut Option<String>,
+  config_extension: &mut Option<ConfigValue>,
   force_ip_prompt: bool,
 ) -> crate::Result<()> {
-  let config = get_config(target.platform_target(), config_extension.as_deref())?;
+  let config = get_config(
+    target.platform_target(),
+    config_extension.as_ref().map(|c| &c.0),
+  )?;
 
   let mut dev_url = config
     .lock()
@@ -178,13 +182,22 @@ fn setup_dev_config(
       let ip = crate::dev::local_ip_address(force_ip_prompt);
       url.set_host(Some(&ip.to_string())).unwrap();
       if let Some(c) = config_extension {
-        let mut c: tauri_utils::config::Config = serde_json::from_str(c)?;
-        c.build.dev_url = dev_url.clone();
-        config_extension.replace(serde_json::to_string(&c).unwrap());
+        if let Some(build) = c
+          .0
+          .as_object_mut()
+          .and_then(|root| root.get_mut("build"))
+          .and_then(|build| build.as_object_mut())
+        {
+          build.insert("devUrl".into(), url.to_string().into());
+        }
       } else {
-        config_extension.replace(format!(r#"{{ "build": {{ "devUrl": "{url}" }} }}"#));
+        config_extension.replace(crate::ConfigValue(serde_json::json!({
+          "build": {
+            "devUrl": url
+          }
+        })));
       }
-      reload_config(config_extension.as_deref())?;
+      reload_config(config_extension.as_ref().map(|c| &c.0))?;
     }
   }