From d91bfa5cb921a078758edd45ef3eaff71358d1eb Mon Sep 17 00:00:00 2001 From: Lucas Fernandes Nogueira Date: Fri, 14 Mar 2025 12:04:58 -0300 Subject: [PATCH] feat(cli): allow merging multiple configuration values (#12970) * feat(cli): allow merging multiple configuration values Currently the dev/build/bundle commands can only merge a single Tauri configuration value (file or raw JSON string), which imposes a limitation in scenarios where you need more flexibility (like multiple app flavors and environments). This changes the config CLI option to allow multiple values, letting you merge multiple Tauri config files with the main one. * fix ios build --- .changes/cli-multiple-config.md | 6 +++ crates/tauri-cli/src/build.rs | 15 +++++-- crates/tauri-cli/src/bundle.rs | 15 +++++-- crates/tauri-cli/src/dev.rs | 38 ++++++++---------- crates/tauri-cli/src/helpers/config.rs | 40 ++++++++++++------- crates/tauri-cli/src/info/app.rs | 2 +- crates/tauri-cli/src/inspect.rs | 2 +- crates/tauri-cli/src/interface/rust.rs | 16 ++++---- .../mobile/android/android_studio_script.rs | 12 ++++-- crates/tauri-cli/src/mobile/android/build.rs | 16 ++++++-- crates/tauri-cli/src/mobile/android/dev.rs | 16 ++++++-- crates/tauri-cli/src/mobile/init.rs | 2 +- crates/tauri-cli/src/mobile/ios/build.rs | 12 ++++-- crates/tauri-cli/src/mobile/ios/dev.rs | 12 ++++-- .../tauri-cli/src/mobile/ios/xcode_script.rs | 12 ++++-- crates/tauri-cli/src/mobile/mod.rs | 35 +++++++--------- 16 files changed, 161 insertions(+), 90 deletions(-) create mode 100644 .changes/cli-multiple-config.md diff --git a/.changes/cli-multiple-config.md b/.changes/cli-multiple-config.md new file mode 100644 index 000000000..6f0882529 --- /dev/null +++ b/.changes/cli-multiple-config.md @@ -0,0 +1,6 @@ +--- +"@tauri-apps/cli": minor:feat +"tauri-cli": minor:feat +--- + +Allow merging multiple configuration values on `tauri dev`, `tauri build`, `tauri bundle`, `tauri android dev`, `tauri android build`, `tauri ios dev` and `tauri ios build`. diff --git a/crates/tauri-cli/src/build.rs b/crates/tauri-cli/src/build.rs index 52d30262c..6f905b951 100644 --- a/crates/tauri-cli/src/build.rs +++ b/crates/tauri-cli/src/build.rs @@ -47,9 +47,15 @@ pub struct Options { /// Skip the bundling step even if `bundle > active` is `true` in tauri config. #[clap(long)] pub no_bundle: bool, - /// JSON string or path to JSON file to merge with tauri.conf.json + /// JSON strings or path to JSON files to merge with the default configuration file + /// + /// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts. + /// + /// Note that a platform-specific file is looked up and merged with the default file by default + /// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json) + /// but you can use this for more specific use cases such as different build flavors. #[clap(short, long)] - pub config: Option, + pub config: Vec, /// Command line arguments passed to the runner. Use `--` to explicitly mark the start of the arguments. pub args: Vec, /// Skip prompting for values @@ -68,7 +74,10 @@ 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_ref().map(|c| &c.0))?; + let config = get_config( + target, + &options.config.iter().map(|c| &c.0).collect::>(), + )?; let mut interface = AppInterface::new( config.lock().unwrap().as_ref().unwrap(), diff --git a/crates/tauri-cli/src/bundle.rs b/crates/tauri-cli/src/bundle.rs index e94abb077..4d1b6e581 100644 --- a/crates/tauri-cli/src/bundle.rs +++ b/crates/tauri-cli/src/bundle.rs @@ -67,9 +67,15 @@ pub struct Options { /// Note that the `updater` bundle is not automatically added so you must specify it if the updater is enabled. #[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')] pub bundles: Option>, - /// JSON string or path to JSON file to merge with tauri.conf.json + /// JSON strings or path to JSON files to merge with the default configuration file + /// + /// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts. + /// + /// Note that a platform-specific file is looked up and merged with the default file by default + /// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json) + /// but you can use this for more specific use cases such as different build flavors. #[clap(short, long)] - pub config: Option, + pub config: Vec, /// Space or comma separated list of features, should be the same features passed to `tauri build` if any. #[clap(short, long, action = ArgAction::Append, num_args(0..))] pub features: Option>, @@ -109,7 +115,10 @@ pub fn command(options: Options, verbosity: u8) -> crate::Result<()> { .map(Target::from_triple) .unwrap_or_else(Target::current); - let config = get_config(target, options.config.as_ref().map(|c| &c.0))?; + let config = get_config( + target, + &options.config.iter().map(|c| &c.0).collect::>(), + )?; let interface = AppInterface::new( config.lock().unwrap().as_ref().unwrap(), diff --git a/crates/tauri-cli/src/dev.rs b/crates/tauri-cli/src/dev.rs index 841b3b3d5..de1fee5e2 100644 --- a/crates/tauri-cli/src/dev.rs +++ b/crates/tauri-cli/src/dev.rs @@ -59,9 +59,15 @@ pub struct Options { /// Exit on panic #[clap(short, long)] pub exit_on_panic: bool, - /// JSON string or path to JSON file to merge with tauri.conf.json + /// JSON strings or path to JSON files to merge with the default configuration file + /// + /// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts. + /// + /// Note that a platform-specific file is looked up and merged with the default file by default + /// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json) + /// but you can use this for more specific use cases such as different build flavors. #[clap(short, long)] - pub config: Option, + pub config: Vec, /// Run the code in release mode #[clap(long = "release")] pub release_mode: bool, @@ -104,7 +110,10 @@ fn command_internal(mut options: Options) -> Result<()> { .map(Target::from_triple) .unwrap_or_else(Target::current); - let config = get_config(target, options.config.as_ref().map(|c| &c.0))?; + let config = get_config( + target, + &options.config.iter().map(|c| &c.0).collect::>(), + )?; let mut interface = AppInterface::new( config.lock().unwrap().as_ref().unwrap(), @@ -262,26 +271,13 @@ pub fn setup(interface: &AppInterface, options: &mut Options, config: ConfigHand let server_url = format!("http://{server_url}"); dev_url = Some(server_url.parse().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()); + options.config.push(crate::ConfigValue(serde_json::json!({ + "build": { + "devUrl": server_url } - } else { - options - .config - .replace(crate::ConfigValue(serde_json::json!({ - "build": { - "devUrl": server_url - } - }))); - } + }))); - reload_config(options.config.as_ref().map(|c| &c.0))?; + reload_config(&options.config.iter().map(|c| &c.0).collect::>())?; } } } diff --git a/crates/tauri-cli/src/helpers/config.rs b/crates/tauri-cli/src/helpers/config.rs index 6df6db20a..f544c5e27 100644 --- a/crates/tauri-cli/src/helpers/config.rs +++ b/crates/tauri-cli/src/helpers/config.rs @@ -138,7 +138,7 @@ fn config_handle() -> &'static ConfigHandle { /// Gets the static parsed config from `tauri.conf.json`. fn get_internal( - merge_config: Option<&serde_json::Value>, + merge_configs: &[&serde_json::Value], reload: bool, target: Target, ) -> crate::Result { @@ -162,12 +162,17 @@ fn get_internal( ); } - if let Some(merge_config) = merge_config { + if !merge_configs.is_empty() { + let mut merge_config = serde_json::Value::Object(Default::default()); + for conf in merge_configs { + merge(&mut merge_config, conf); + } + let merge_config_str = serde_json::to_string(&merge_config).unwrap(); set_var("TAURI_CONFIG", merge_config_str); - merge(&mut config, merge_config); + merge(&mut config, &merge_config); extensions.insert(MERGE_CONFIG_EXTENSION_NAME.into(), merge_config.clone()); - }; + } if config_path.extension() == Some(OsStr::new("json")) || config_path.extension() == Some(OsStr::new("json5")) @@ -217,35 +222,42 @@ fn get_internal( Ok(config_handle().clone()) } -pub fn get( - target: Target, - merge_config: Option<&serde_json::Value>, -) -> crate::Result { - get_internal(merge_config, false, target) +pub fn get(target: Target, merge_configs: &[&serde_json::Value]) -> crate::Result { + get_internal(merge_configs, false, target) } -pub fn reload(merge_config: Option<&serde_json::Value>) -> crate::Result { +pub fn reload(merge_configs: &[&serde_json::Value]) -> crate::Result { let target = config_handle() .lock() .unwrap() .as_ref() .map(|conf| conf.target); if let Some(target) = target { - get_internal(merge_config, true, target) + get_internal(merge_configs, true, target) } else { Err(anyhow::anyhow!("config not loaded")) } } /// merges the loaded config with the given value -pub fn merge_with(merge_config: &serde_json::Value) -> crate::Result { +pub fn merge_with(merge_configs: &[&serde_json::Value]) -> crate::Result { let handle = config_handle(); + + if merge_configs.is_empty() { + return Ok(handle.clone()); + } + if let Some(config_metadata) = &mut *handle.lock().unwrap() { - let merge_config_str = serde_json::to_string(merge_config).unwrap(); + let mut merge_config = serde_json::Value::Object(Default::default()); + for conf in merge_configs { + merge(&mut merge_config, conf); + } + + let merge_config_str = serde_json::to_string(&merge_config).unwrap(); set_var("TAURI_CONFIG", merge_config_str); let mut value = serde_json::to_value(config_metadata.inner.clone())?; - merge(&mut value, merge_config); + merge(&mut value, &merge_config); config_metadata.inner = serde_json::from_value(value)?; Ok(handle.clone()) diff --git a/crates/tauri-cli/src/info/app.rs b/crates/tauri-cli/src/info/app.rs index 6987ff08b..624d79bd3 100644 --- a/crates/tauri-cli/src/info/app.rs +++ b/crates/tauri-cli/src/info/app.rs @@ -13,7 +13,7 @@ use tauri_utils::platform::Target; pub fn items(frontend_dir: Option<&PathBuf>, tauri_dir: Option<&Path>) -> Vec { let mut items = Vec::new(); if tauri_dir.is_some() { - if let Ok(config) = crate::helpers::config::get(Target::current(), None) { + if let Ok(config) = crate::helpers::config::get(Target::current(), &[]) { let config_guard = config.lock().unwrap(); let config = config_guard.as_ref().unwrap(); diff --git a/crates/tauri-cli/src/inspect.rs b/crates/tauri-cli/src/inspect.rs index 87c4ae951..d08a81835 100644 --- a/crates/tauri-cli/src/inspect.rs +++ b/crates/tauri-cli/src/inspect.rs @@ -31,7 +31,7 @@ fn wix_upgrade_code() -> Result<()> { crate::helpers::app_paths::resolve(); let target = tauri_utils::platform::Target::Windows; - let config = crate::helpers::config::get(target, None)?; + let config = crate::helpers::config::get(target, &[])?; let interface = AppInterface::new(config.lock().unwrap().as_ref().unwrap(), None)?; diff --git a/crates/tauri-cli/src/interface/rust.rs b/crates/tauri-cli/src/interface/rust.rs index f28b481a8..bfbf83bb6 100644 --- a/crates/tauri-cli/src/interface/rust.rs +++ b/crates/tauri-cli/src/interface/rust.rs @@ -51,7 +51,7 @@ pub struct Options { pub target: Option, pub features: Option>, pub args: Vec, - pub config: Option, + pub config: Vec, pub no_watch: bool, } @@ -101,7 +101,7 @@ pub struct MobileOptions { pub debug: bool, pub features: Option>, pub args: Vec, - pub config: Option, + pub config: Vec, pub no_watch: bool, } @@ -207,14 +207,14 @@ impl Interface for Rust { rx.recv().unwrap(); Ok(()) } else { - let config = options.config.clone().map(|c| c.0); + let merge_configs = options.config.iter().map(|c| &c.0).collect::>(); let run = Arc::new(|rust: &mut Rust| { let on_exit = on_exit.clone(); rust.run_dev(options.clone(), run_args.clone(), move |status, reason| { on_exit(status, reason) }) }); - self.run_dev_watcher(config, run) + self.run_dev_watcher(&merge_configs, run) } } @@ -236,9 +236,9 @@ impl Interface for Rust { runner(options)?; Ok(()) } else { - let config = options.config.clone().map(|c| c.0); + let merge_configs = options.config.iter().map(|c| &c.0).collect::>(); let run = Arc::new(|_rust: &mut Rust| runner(options.clone())); - self.run_dev_watcher(config, run) + self.run_dev_watcher(&merge_configs, run) } } @@ -487,7 +487,7 @@ impl Rust { fn run_dev_watcher crate::Result>>( &mut self, - config: Option, + merge_configs: &[&serde_json::Value], run: Arc, ) -> crate::Result<()> { let child = run(self)?; @@ -537,7 +537,7 @@ impl Rust { if let Some(event_path) = event.paths.first() { if !ignore_matcher.is_ignore(event_path, event_path.is_dir()) { if is_configuration_file(self.app_settings.target, event_path) { - if let Ok(config) = reload_config(config.as_ref()) { + if let Ok(config) = reload_config(merge_configs) { let (manifest, modified) = rewrite_manifest(config.lock().unwrap().as_ref().unwrap())?; if modified { diff --git a/crates/tauri-cli/src/mobile/android/android_studio_script.rs b/crates/tauri-cli/src/mobile/android/android_studio_script.rs index 32f936130..34aaa1060 100644 --- a/crates/tauri-cli/src/mobile/android/android_studio_script.rs +++ b/crates/tauri-cli/src/mobile/android/android_studio_script.rs @@ -46,7 +46,7 @@ pub fn command(options: Options) -> Result<()> { Profile::Debug }; - let tauri_config = get_tauri_config(tauri_utils::platform::Target::Android, None)?; + let tauri_config = get_tauri_config(tauri_utils::platform::Target::Android, &[])?; let (config, metadata, cli_options) = { let tauri_config_guard = tauri_config.lock().unwrap(); @@ -72,8 +72,14 @@ pub fn command(options: Options) -> Result<()> { MobileTarget::Android, )?; - if let Some(config) = &cli_options.config { - crate::helpers::config::merge_with(&config.0)?; + if !cli_options.config.is_empty() { + crate::helpers::config::merge_with( + &cli_options + .config + .iter() + .map(|conf| &conf.0) + .collect::>(), + )?; } let env = env()?; diff --git a/crates/tauri-cli/src/mobile/android/build.rs b/crates/tauri-cli/src/mobile/android/build.rs index f6ccf7eaf..26e27399d 100644 --- a/crates/tauri-cli/src/mobile/android/build.rs +++ b/crates/tauri-cli/src/mobile/android/build.rs @@ -49,9 +49,15 @@ pub struct Options { /// List of cargo features to activate #[clap(short, long, action = ArgAction::Append, num_args(0..))] pub features: Option>, - /// JSON string or path to JSON file to merge with tauri.conf.json + /// JSON strings or path to JSON files to merge with the default configuration file + /// + /// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts. + /// + /// Note that a platform-specific file is looked up and merged with the default file by default + /// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json) + /// but you can use this for more specific use cases such as different build flavors. #[clap(short, long)] - pub config: Option, + pub config: Vec, /// Whether to split the APKs and AABs per ABIs. #[clap(long)] pub split_per_abi: bool, @@ -105,7 +111,11 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> { let tauri_config = get_tauri_config( tauri_utils::platform::Target::Android, - options.config.as_ref().map(|c| &c.0), + &options + .config + .iter() + .map(|conf| &conf.0) + .collect::>(), )?; let (interface, config, metadata) = { let tauri_config_guard = tauri_config.lock().unwrap(); diff --git a/crates/tauri-cli/src/mobile/android/dev.rs b/crates/tauri-cli/src/mobile/android/dev.rs index 6899b21a1..0a8b13050 100644 --- a/crates/tauri-cli/src/mobile/android/dev.rs +++ b/crates/tauri-cli/src/mobile/android/dev.rs @@ -48,9 +48,15 @@ pub struct Options { /// Exit on panic #[clap(short, long)] exit_on_panic: bool, - /// JSON string or path to JSON file to merge with tauri.conf.json + /// JSON strings or path to JSON files to merge with the default configuration file + /// + /// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts. + /// + /// Note that a platform-specific file is looked up and merged with the default file by default + /// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json) + /// but you can use this for more specific use cases such as different build flavors. #[clap(short, long)] - pub config: Option, + pub config: Vec, /// Run the code in release mode #[clap(long = "release")] pub release_mode: bool, @@ -126,7 +132,11 @@ fn run_command(options: Options, noise_level: NoiseLevel) -> Result<()> { let tauri_config = get_tauri_config( tauri_utils::platform::Target::Android, - options.config.as_ref().map(|c| &c.0), + &options + .config + .iter() + .map(|conf| &conf.0) + .collect::>(), )?; let env = env()?; diff --git a/crates/tauri-cli/src/mobile/init.rs b/crates/tauri-cli/src/mobile/init.rs index c24ddbe84..eaa5ea991 100644 --- a/crates/tauri-cli/src/mobile/init.rs +++ b/crates/tauri-cli/src/mobile/init.rs @@ -43,7 +43,7 @@ pub fn exec( #[allow(unused_variables)] reinstall_deps: bool, skip_targets_install: bool, ) -> Result { - let tauri_config = get_tauri_config(target.platform_target(), None)?; + let tauri_config = get_tauri_config(target.platform_target(), &[])?; let tauri_config_guard = tauri_config.lock().unwrap(); let tauri_config_ = tauri_config_guard.as_ref().unwrap(); diff --git a/crates/tauri-cli/src/mobile/ios/build.rs b/crates/tauri-cli/src/mobile/ios/build.rs index 82a6d2eff..f33b5b81d 100644 --- a/crates/tauri-cli/src/mobile/ios/build.rs +++ b/crates/tauri-cli/src/mobile/ios/build.rs @@ -60,9 +60,15 @@ pub struct Options { /// List of cargo features to activate #[clap(short, long, action = ArgAction::Append, num_args(0..))] pub features: Option>, - /// JSON string or path to JSON file to merge with tauri.conf.json + /// JSON strings or path to JSON files to merge with the default configuration file + /// + /// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts. + /// + /// Note that a platform-specific file is looked up and merged with the default file by default + /// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json) + /// but you can use this for more specific use cases such as different build flavors. #[clap(short, long)] - pub config: Option, + pub config: Vec, /// Build number to append to the app version. #[clap(long)] pub build_number: Option, @@ -145,7 +151,7 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> { let tauri_config = get_tauri_config( tauri_utils::platform::Target::Ios, - options.config.as_ref().map(|c| &c.0), + &options.config.iter().map(|c| &c.0).collect::>(), )?; let (interface, mut config) = { let tauri_config_guard = tauri_config.lock().unwrap(); diff --git a/crates/tauri-cli/src/mobile/ios/dev.rs b/crates/tauri-cli/src/mobile/ios/dev.rs index 892afa76e..22f336110 100644 --- a/crates/tauri-cli/src/mobile/ios/dev.rs +++ b/crates/tauri-cli/src/mobile/ios/dev.rs @@ -54,9 +54,15 @@ pub struct Options { /// Exit on panic #[clap(short, long)] exit_on_panic: bool, - /// JSON string or path to JSON file to merge with tauri.conf.json + /// JSON strings or path to JSON files to merge with the default configuration file + /// + /// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts. + /// + /// Note that a platform-specific file is looked up and merged with the default file by default + /// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json) + /// but you can use this for more specific use cases such as different build flavors. #[clap(short, long)] - pub config: Option, + pub config: Vec, /// Run the code in release mode #[clap(long = "release")] pub release_mode: bool, @@ -148,7 +154,7 @@ fn run_command(options: Options, noise_level: NoiseLevel) -> Result<()> { let tauri_config = get_tauri_config( tauri_utils::platform::Target::Ios, - options.config.as_ref().map(|c| &c.0), + &options.config.iter().map(|c| &c.0).collect::>(), )?; let (interface, config) = { let tauri_config_guard = tauri_config.lock().unwrap(); diff --git a/crates/tauri-cli/src/mobile/ios/xcode_script.rs b/crates/tauri-cli/src/mobile/ios/xcode_script.rs index c9599f402..c3302b370 100644 --- a/crates/tauri-cli/src/mobile/ios/xcode_script.rs +++ b/crates/tauri-cli/src/mobile/ios/xcode_script.rs @@ -78,7 +78,7 @@ pub fn command(options: Options) -> Result<()> { let profile = profile_from_configuration(&options.configuration); let macos = macos_from_platform(&options.platform); - let tauri_config = get_tauri_config(tauri_utils::platform::Target::Ios, None)?; + let tauri_config = get_tauri_config(tauri_utils::platform::Target::Ios, &[])?; let (config, metadata, cli_options) = { let tauri_config_guard = tauri_config.lock().unwrap(); @@ -103,8 +103,14 @@ pub fn command(options: Options) -> Result<()> { MobileTarget::Ios, )?; - if let Some(config) = &cli_options.config { - crate::helpers::config::merge_with(&config.0)?; + if !cli_options.config.is_empty() { + crate::helpers::config::merge_with( + &cli_options + .config + .iter() + .map(|conf| &conf.0) + .collect::>(), + )?; } let env = env()?.explicit_env_vars(cli_options.vars); diff --git a/crates/tauri-cli/src/mobile/mod.rs b/crates/tauri-cli/src/mobile/mod.rs index d9e5a10db..10300a208 100644 --- a/crates/tauri-cli/src/mobile/mod.rs +++ b/crates/tauri-cli/src/mobile/mod.rs @@ -187,7 +187,7 @@ pub struct CliOptions { pub args: Vec, pub noise_level: NoiseLevel, pub vars: HashMap, - pub config: Option, + pub config: Vec, pub target_device: Option, } @@ -199,7 +199,7 @@ impl Default for CliOptions { args: vec!["--lib".into()], noise_level: Default::default(), vars: Default::default(), - config: None, + config: Vec::new(), target_device: None, } } @@ -292,26 +292,21 @@ fn use_network_address_for_dev_url( url.path() ))?; - if let Some(c) = &mut dev_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(), url.to_string().into()); - } - } else { - let mut build = serde_json::Map::new(); - build.insert("devUrl".into(), url.to_string().into()); + dev_options + .config + .push(crate::ConfigValue(serde_json::json!({ + "build": { + "devUrl": url + } + }))); - dev_options + reload_config( + &dev_options .config - .replace(crate::ConfigValue(serde_json::json!({ - "build": build - }))); - } - reload_config(dev_options.config.as_ref().map(|c| &c.0))?; + .iter() + .map(|conf| &conf.0) + .collect::>(), + )?; Some(ip) } else {