Feat: CLI Settings (#2424)
* feat: cli settings * revision: lower msrv * add previous line about hotreload settings just in case * Fix issue with open --------- Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
This commit is contained in:
1 parent
2ef406982e
commit
9567674a63
13 files changed
+868
-672
No files matched your search
Generated
+657
-649
File diff suppressed because it is too large.
Load diff
@@ -125,6 +125,9 @@ ciborium = "0.2.1"
|
|||||||
base64 = "0.21.0"
|
base64 = "0.21.0"
|
||||||
once_cell = "1.17.1"
|
once_cell = "1.17.1"
|
||||||
|
|
||||||
|
# cli, cli-config
|
||||||
|
dirs = "5.0.1"
|
||||||
|
|
||||||
# speed up some macros by optimizing them
|
# speed up some macros by optimizing them
|
||||||
[profile.dev.package.insta]
|
[profile.dev.package.insta]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
|
|||||||
@@ -21,9 +21,11 @@ tracing = { workspace = true }
|
|||||||
tauri-bundler = { version = "=1.4.0", features = ["native-tls-vendored"], optional = true }
|
tauri-bundler = { version = "=1.4.0", features = ["native-tls-vendored"], optional = true }
|
||||||
tauri-utils = { version = "=1.5.*", optional = true }
|
tauri-utils = { version = "=1.5.*", optional = true }
|
||||||
|
|
||||||
|
dirs = { workspace = true, optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["read-config"]
|
default = ["read-config"]
|
||||||
cli = ["dep:tauri-bundler", "dep:tauri-utils", "dep:clap", "dep:toml", "dep:cargo_toml"]
|
cli = ["dep:tauri-bundler", "dep:tauri-utils", "dep:clap", "dep:toml", "dep:cargo_toml", "dep:dirs"]
|
||||||
read-config = []
|
read-config = []
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ pub struct DioxusConfig {
|
|||||||
#[cfg(feature = "cli")]
|
#[cfg(feature = "cli")]
|
||||||
#[serde(default = "default_plugin")]
|
#[serde(default = "default_plugin")]
|
||||||
pub plugin: toml::Value,
|
pub plugin: toml::Value,
|
||||||
|
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
|
pub cli_settings: Option<crate::CliSettings>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "cli")]
|
#[cfg(feature = "cli")]
|
||||||
@@ -116,6 +119,8 @@ impl DioxusConfig {
|
|||||||
/// Load the dioxus config from a path
|
/// Load the dioxus config from a path
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
pub fn load(bin: Option<PathBuf>) -> Result<Option<DioxusConfig>, CrateConfigError> {
|
pub fn load(bin: Option<PathBuf>) -> Result<Option<DioxusConfig>, CrateConfigError> {
|
||||||
|
use crate::CliSettings;
|
||||||
|
|
||||||
let crate_dir = crate::cargo::crate_root();
|
let crate_dir = crate::cargo::crate_root();
|
||||||
|
|
||||||
let crate_dir = match crate_dir {
|
let crate_dir = match crate_dir {
|
||||||
@@ -157,6 +162,23 @@ impl DioxusConfig {
|
|||||||
if cfg.bundle.publisher.is_none() {
|
if cfg.bundle.publisher.is_none() {
|
||||||
cfg.bundle.publisher = Some(name);
|
cfg.bundle.publisher = Some(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle Cli Settings
|
||||||
|
if cfg.cli_settings.is_none() {
|
||||||
|
cfg.cli_settings = Some(CliSettings::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
let cli_settings = cfg.cli_settings.as_mut().unwrap();
|
||||||
|
// If the project-level settings doesn't exist, let's grab it from global.
|
||||||
|
if let Some(global_cli_settings) = crate::CliSettings::from_global() {
|
||||||
|
if cli_settings.always_hot_reload.is_none() {
|
||||||
|
cli_settings.always_hot_reload = global_cli_settings.always_hot_reload;
|
||||||
|
}
|
||||||
|
if cli_settings.always_open_browser.is_none() {
|
||||||
|
cli_settings.always_open_browser = global_cli_settings.always_open_browser;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Some(cfg))
|
Ok(Some(cfg))
|
||||||
}
|
}
|
||||||
cfg => cfg,
|
cfg => cfg,
|
||||||
@@ -185,7 +207,6 @@ impl Default for DioxusConfig {
|
|||||||
default_platform: default_platform(),
|
default_platform: default_platform(),
|
||||||
out_dir: out_dir_default(),
|
out_dir: out_dir_default(),
|
||||||
asset_dir: asset_dir_default(),
|
asset_dir: asset_dir_default(),
|
||||||
hot_reload: hot_reload_default(),
|
|
||||||
|
|
||||||
#[cfg(feature = "cli")]
|
#[cfg(feature = "cli")]
|
||||||
tools: Default::default(),
|
tools: Default::default(),
|
||||||
@@ -223,6 +244,9 @@ impl Default for DioxusConfig {
|
|||||||
},
|
},
|
||||||
#[cfg(feature = "cli")]
|
#[cfg(feature = "cli")]
|
||||||
plugin: toml::Value::Table(toml::map::Map::new()),
|
plugin: toml::Value::Table(toml::map::Map::new()),
|
||||||
|
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
|
cli_settings: Some(crate::CliSettings::default()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -241,9 +265,6 @@ pub struct ApplicationConfig {
|
|||||||
#[serde(default = "asset_dir_default")]
|
#[serde(default = "asset_dir_default")]
|
||||||
pub asset_dir: PathBuf,
|
pub asset_dir: PathBuf,
|
||||||
|
|
||||||
#[serde(default = "hot_reload_default")]
|
|
||||||
pub hot_reload: bool,
|
|
||||||
|
|
||||||
#[cfg(feature = "cli")]
|
#[cfg(feature = "cli")]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub tools: std::collections::HashMap<String, toml::Value>,
|
pub tools: std::collections::HashMap<String, toml::Value>,
|
||||||
@@ -260,10 +281,6 @@ fn default_platform() -> Platform {
|
|||||||
Platform::Web
|
Platform::Web
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hot_reload_default() -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn asset_dir_default() -> PathBuf {
|
fn asset_dir_default() -> PathBuf {
|
||||||
PathBuf::from("public")
|
PathBuf::from("public")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,10 @@
|
|||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
pub use config::*;
|
pub use config::*;
|
||||||
|
|
||||||
mod bundle;
|
mod bundle;
|
||||||
pub use bundle::*;
|
pub use bundle::*;
|
||||||
|
|
||||||
mod cargo;
|
mod cargo;
|
||||||
pub use cargo::*;
|
pub use cargo::*;
|
||||||
#[cfg(feature = "cli")]
|
#[cfg(feature = "cli")]
|
||||||
@@ -13,6 +15,12 @@ mod serve;
|
|||||||
#[cfg(feature = "cli")]
|
#[cfg(feature = "cli")]
|
||||||
pub use serve::*;
|
pub use serve::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
|
mod settings;
|
||||||
|
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
|
pub use settings::*;
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub mod __private {
|
pub mod __private {
|
||||||
use crate::CrateConfig;
|
use crate::CrateConfig;
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
use crate::CrateConfigError;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{
|
||||||
|
fs,
|
||||||
|
io::{Error, ErrorKind},
|
||||||
|
path::PathBuf,
|
||||||
|
};
|
||||||
|
use tracing::{debug, error, warn};
|
||||||
|
|
||||||
|
const GLOBAL_SETTINGS_FILE_NAME: &str = "dioxus/settings.toml";
|
||||||
|
|
||||||
|
/// Describes cli settings from project or global level.
|
||||||
|
/// The order of priority goes:
|
||||||
|
/// 1. CLI Flags/Arguments
|
||||||
|
/// 2. Project-level Settings
|
||||||
|
/// 3. Global-level settings.
|
||||||
|
///
|
||||||
|
/// This allows users to control the cli settings with ease.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
pub struct CliSettings {
|
||||||
|
/// Describes whether hot reload should always be on.
|
||||||
|
pub always_hot_reload: Option<bool>,
|
||||||
|
/// Describes whether the CLI should always open the browser for Web targets.
|
||||||
|
pub always_open_browser: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CliSettings {
|
||||||
|
/// Get the current settings structure from global.
|
||||||
|
pub fn from_global() -> Option<Self> {
|
||||||
|
let Some(path) = dirs::data_local_dir() else {
|
||||||
|
warn!("failed to get local data directory, some config keys may be missing");
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let path = path.join(GLOBAL_SETTINGS_FILE_NAME);
|
||||||
|
let Some(data) = fs::read_to_string(path).ok() else {
|
||||||
|
// We use a debug here because we expect the file to not exist.
|
||||||
|
debug!("failed to read `{}` config file", GLOBAL_SETTINGS_FILE_NAME);
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let data = toml::from_str::<CliSettings>(&data).ok();
|
||||||
|
if data.is_none() {
|
||||||
|
warn!(
|
||||||
|
"failed to parse `{}` config file",
|
||||||
|
GLOBAL_SETTINGS_FILE_NAME
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
data
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Save the current structure to the global settings toml.
|
||||||
|
/// This does not save to project-level settings.
|
||||||
|
pub fn save(self) -> Result<Self, CrateConfigError> {
|
||||||
|
let path = Self::get_settings_path().ok_or_else(|| {
|
||||||
|
error!("failed to get settings path");
|
||||||
|
CrateConfigError::Io(Error::new(
|
||||||
|
ErrorKind::NotFound,
|
||||||
|
"failed to get settings path",
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let data = toml::to_string_pretty(&self).map_err(|e| {
|
||||||
|
error!(?self, "failed to parse config into toml");
|
||||||
|
CrateConfigError::Io(Error::new(ErrorKind::Other, e.to_string()))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let result = fs::write(path.clone(), data.clone());
|
||||||
|
if let Err(e) = result {
|
||||||
|
error!(?data, ?path, "failed to save global cli settings");
|
||||||
|
return Err(CrateConfigError::Io(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the path to the settings toml file.
|
||||||
|
pub fn get_settings_path() -> Option<PathBuf> {
|
||||||
|
let Some(path) = dirs::data_local_dir() else {
|
||||||
|
warn!("failed to get local data directory, some config keys may be missing");
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(path.join(GLOBAL_SETTINGS_FILE_NAME))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -50,7 +50,7 @@ headers = "0.3.7"
|
|||||||
walkdir = "2"
|
walkdir = "2"
|
||||||
|
|
||||||
# tools download
|
# tools download
|
||||||
dirs = "5.0.1"
|
dirs = { workspace = true }
|
||||||
reqwest = { workspace = true, features = [
|
reqwest = { workspace = true, features = [
|
||||||
"rustls-tls",
|
"rustls-tls",
|
||||||
"stream",
|
"stream",
|
||||||
|
|||||||
@@ -46,3 +46,7 @@ script = []
|
|||||||
|
|
||||||
[[web.proxy]]
|
[[web.proxy]]
|
||||||
backend = "http://localhost:8000/api/"
|
backend = "http://localhost:8000/api/"
|
||||||
|
|
||||||
|
# [cli_settings]
|
||||||
|
# always_hot_reload = true
|
||||||
|
# always_open_browser = true
|
||||||
@@ -91,10 +91,16 @@ pub struct ConfigOptsServe {
|
|||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
pub(crate) server_arguments: ServeArguments,
|
pub(crate) server_arguments: ServeArguments,
|
||||||
|
|
||||||
/// Open the app in the default browser [default: true]
|
// TODO: Somehow make this default to `true` if the flag was provided. e.g. `dx serve --open`
|
||||||
#[clap(long, default_value_t = false)]
|
// Currently it requires a value: `dx serve --open true`
|
||||||
#[serde(default)]
|
/// Open the app in the default browser [default: false - unless project or global settings are set]
|
||||||
pub open: bool,
|
#[clap(long)]
|
||||||
|
pub open: Option<bool>,
|
||||||
|
|
||||||
|
// TODO: See `open` field
|
||||||
|
/// Enable full hot reloading for the app [default: true - unless project or global settings are set]
|
||||||
|
#[clap(long, group = "release-incompatible")]
|
||||||
|
pub hot_reload: Option<bool>,
|
||||||
|
|
||||||
/// Build a example [default: ""]
|
/// Build a example [default: ""]
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
@@ -136,9 +142,6 @@ pub struct ConfigOptsServe {
|
|||||||
require_equals(true),
|
require_equals(true),
|
||||||
action = clap::ArgAction::Set,
|
action = clap::ArgAction::Set,
|
||||||
)]
|
)]
|
||||||
#[clap(group = "release-incompatible")]
|
|
||||||
#[serde(default)]
|
|
||||||
pub hot_reload: bool,
|
|
||||||
|
|
||||||
/// Set cross-origin-policy to same-origin [default: false]
|
/// Set cross-origin-policy to same-origin [default: false]
|
||||||
#[clap(name = "cross-origin-policy")]
|
#[clap(name = "cross-origin-policy")]
|
||||||
|
|||||||
@@ -24,6 +24,33 @@ pub enum Config {
|
|||||||
FormatPrint {},
|
FormatPrint {},
|
||||||
/// Create a custom html file.
|
/// Create a custom html file.
|
||||||
CustomHtml {},
|
CustomHtml {},
|
||||||
|
|
||||||
|
/// Set global cli settings.
|
||||||
|
SetGlobal { setting: Setting, value: Value },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, clap::ValueEnum)]
|
||||||
|
pub enum Setting {
|
||||||
|
/// Set the value of the always-hot-reload setting.
|
||||||
|
AlwaysHotReload,
|
||||||
|
/// Set the value of the always-open-browser setting.
|
||||||
|
AlwaysOpenBrowser,
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Unsure of an alternative to get the desired behavior with clap, if it exists.
|
||||||
|
#[derive(Debug, Clone, Deserialize, clap::ValueEnum)]
|
||||||
|
pub enum Value {
|
||||||
|
True,
|
||||||
|
False,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Value> for bool {
|
||||||
|
fn from(value: Value) -> Self {
|
||||||
|
match value {
|
||||||
|
Value::True => true,
|
||||||
|
Value::False => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
@@ -62,6 +89,20 @@ impl Config {
|
|||||||
file.write_all(content.as_bytes())?;
|
file.write_all(content.as_bytes())?;
|
||||||
tracing::info!("🚩 Create custom html file done.");
|
tracing::info!("🚩 Create custom html file done.");
|
||||||
}
|
}
|
||||||
|
Config::SetGlobal { setting, value } => {
|
||||||
|
let mut global_settings =
|
||||||
|
dioxus_cli_config::CliSettings::from_global().unwrap_or_default();
|
||||||
|
|
||||||
|
match setting {
|
||||||
|
Setting::AlwaysHotReload => {
|
||||||
|
global_settings.always_hot_reload = Some(value.into())
|
||||||
|
}
|
||||||
|
Setting::AlwaysOpenBrowser => {
|
||||||
|
global_settings.always_open_browser = Some(value.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
global_settings.save().unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,11 +16,26 @@ pub struct Serve {
|
|||||||
impl Serve {
|
impl Serve {
|
||||||
pub fn serve(self, bin: Option<PathBuf>) -> Result<()> {
|
pub fn serve(self, bin: Option<PathBuf>) -> Result<()> {
|
||||||
let mut crate_config = dioxus_cli_config::CrateConfig::new(bin)?;
|
let mut crate_config = dioxus_cli_config::CrateConfig::new(bin)?;
|
||||||
let serve_cfg = self.serve.clone();
|
let mut serve_cfg = self.serve.clone();
|
||||||
|
|
||||||
// change the release state.
|
// Handle cli settings
|
||||||
let hot_reload = self.serve.hot_reload || crate_config.dioxus_config.application.hot_reload;
|
let cli_settings = crate_config.dioxus_config.cli_settings.clone().unwrap();
|
||||||
crate_config.with_hot_reload(hot_reload);
|
|
||||||
|
if serve_cfg.hot_reload.is_none() {
|
||||||
|
// we're going to override the hot_reload setting in the project's cfg based on settings
|
||||||
|
//
|
||||||
|
// let hot_reload = self.serve.hot_reload || crate_config.dioxus_config.application.hot_reload;
|
||||||
|
|
||||||
|
let value = cli_settings.always_hot_reload.unwrap_or(true);
|
||||||
|
serve_cfg.hot_reload = Some(value);
|
||||||
|
crate_config.with_hot_reload(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if serve_cfg.open.is_none() {
|
||||||
|
serve_cfg.open = Some(cli_settings.always_open_browser.unwrap_or(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set config settings
|
||||||
crate_config.with_cross_origin_policy(self.serve.cross_origin_policy);
|
crate_config.with_cross_origin_policy(self.serve.cross_origin_policy);
|
||||||
crate_config.with_release(self.serve.release);
|
crate_config.with_release(self.serve.release);
|
||||||
crate_config.with_verbose(self.serve.verbose);
|
crate_config.with_verbose(self.serve.verbose);
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ impl Platform for FullstackPlatform {
|
|||||||
let desktop =
|
let desktop =
|
||||||
DesktopPlatform::start_with_options(build_result, &desktop_config, serve, desktop_env)?;
|
DesktopPlatform::start_with_options(build_result, &desktop_config, serve, desktop_env)?;
|
||||||
|
|
||||||
if serve.open {
|
if serve.open.unwrap_or_default() {
|
||||||
crate::server::web::open_browser(
|
crate::server::web::open_browser(
|
||||||
config,
|
config,
|
||||||
serve
|
serve
|
||||||
|
|||||||
@@ -90,7 +90,15 @@ pub fn serve(
|
|||||||
let router = setup_router(config.clone(), hot_reload_state).await?;
|
let router = setup_router(config.clone(), hot_reload_state).await?;
|
||||||
|
|
||||||
// Start server
|
// Start server
|
||||||
start_server(ip, port, router, opts.open, rustls_config, &config).await?;
|
start_server(
|
||||||
|
ip,
|
||||||
|
port,
|
||||||
|
router,
|
||||||
|
opts.open.unwrap_or_default(),
|
||||||
|
rustls_config,
|
||||||
|
&config,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in new issue
Block a user