diff --git a/Cargo.toml b/Cargo.toml index 7ef10d612..df558e69e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "packages/dioxus", "packages/core", "packages/cli", + "packages/cli-config", "packages/core-macro", "packages/router-macro", "packages/extension", @@ -79,6 +80,7 @@ dioxus-native-core = { path = "packages/native-core", version = "0.4.0" } dioxus-native-core-macro = { path = "packages/native-core-macro", version = "0.4.0" } rsx-rosetta = { path = "packages/rsx-rosetta", version = "0.4.0" } dioxus-signals = { path = "packages/signals" } +dioxus-cli-config = { path = "packages/cli-config", version = "0.4.1" } generational-box = { path = "packages/generational-box", version = "0.4.3" } dioxus-hot-reload = { path = "packages/hot-reload", version = "0.4.0" } dioxus-fullstack = { path = "packages/fullstack", version = "0.4.1" } diff --git a/packages/cli-config/.gitignore b/packages/cli-config/.gitignore new file mode 100644 index 000000000..6700b1332 --- /dev/null +++ b/packages/cli-config/.gitignore @@ -0,0 +1,4 @@ +/target +Cargo.lock +.DS_Store +.idea/ diff --git a/packages/cli-config/Cargo.toml b/packages/cli-config/Cargo.toml new file mode 100644 index 000000000..d6f422b3c --- /dev/null +++ b/packages/cli-config/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "dioxus-cli-config" +version = "0.4.1" +authors = ["Jonathan Kelley"] +edition = "2021" +description = "Configuration for the Dioxus CLI" +repository = "https://github.com/DioxusLabs/dioxus/" +license = "MIT OR Apache-2.0" +keywords = ["react", "gui", "cli", "dioxus", "wasm"] + +[dependencies] +clap = { version = "4.2", features = ["derive"], optional = true } +serde = { version = "1.0.136", features = ["derive"] } +serde_json = "1.0.79" +toml = { version = "0.5.8", optional = true } +cargo_toml = { version = "0.16.0", optional = true } +once_cell = "1.18.0" +tracing.workspace = true + +# bundling +tauri-bundler = { version = "=1.4.0", features = ["native-tls-vendored"], optional = true } +tauri-utils = { version = "=1.5.*", optional = true } + +[features] +default = [] +cli = ["tauri-bundler", "tauri-utils", "clap", "toml", "cargo_toml"] diff --git a/packages/cli-config/README.md b/packages/cli-config/README.md new file mode 100644 index 000000000..ef2435713 --- /dev/null +++ b/packages/cli-config/README.md @@ -0,0 +1,5 @@ +
+

📦✨ Dioxus CLI Configuration

+
+ +The **dioxus-cli-config** contains the configuration for the **dioxus-cli**. diff --git a/packages/cli-config/src/bundle.rs b/packages/cli-config/src/bundle.rs new file mode 100644 index 000000000..07c4fec21 --- /dev/null +++ b/packages/cli-config/src/bundle.rs @@ -0,0 +1,260 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::path::PathBuf; + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct BundleConfig { + pub identifier: Option, + pub publisher: Option, + pub icon: Option>, + pub resources: Option>, + pub copyright: Option, + pub category: Option, + pub short_description: Option, + pub long_description: Option, + pub external_bin: Option>, + pub deb: Option, + pub macos: Option, + pub windows: Option, +} + +#[cfg(feature = "cli")] +impl From for tauri_bundler::BundleSettings { + fn from(val: BundleConfig) -> Self { + tauri_bundler::BundleSettings { + identifier: val.identifier, + publisher: val.publisher, + icon: val.icon, + resources: val.resources, + copyright: val.copyright, + category: val.category.and_then(|c| c.parse().ok()), + short_description: val.short_description, + long_description: val.long_description, + external_bin: val.external_bin, + deb: val.deb.map(Into::into).unwrap_or_default(), + macos: val.macos.map(Into::into).unwrap_or_default(), + windows: val.windows.map(Into::into).unwrap_or_default(), + ..Default::default() + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct DebianSettings { + pub depends: Option>, + pub files: HashMap, + pub nsis: Option, +} + +#[cfg(feature = "cli")] +impl From for tauri_bundler::DebianSettings { + fn from(val: DebianSettings) -> Self { + tauri_bundler::DebianSettings { + depends: val.depends, + files: val.files, + desktop_template: None, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct WixSettings { + pub language: Vec<(String, Option)>, + pub template: Option, + pub fragment_paths: Vec, + pub component_group_refs: Vec, + pub component_refs: Vec, + pub feature_group_refs: Vec, + pub feature_refs: Vec, + pub merge_refs: Vec, + pub skip_webview_install: bool, + pub license: Option, + pub enable_elevated_update_task: bool, + pub banner_path: Option, + pub dialog_image_path: Option, + pub fips_compliant: bool, +} + +#[cfg(feature = "cli")] +impl From for tauri_bundler::WixSettings { + fn from(val: WixSettings) -> Self { + tauri_bundler::WixSettings { + language: tauri_bundler::bundle::WixLanguage({ + let mut languages: Vec<_> = val + .language + .iter() + .map(|l| { + ( + l.0.clone(), + tauri_bundler::bundle::WixLanguageConfig { + locale_path: l.1.clone(), + }, + ) + }) + .collect(); + if languages.is_empty() { + languages.push(("en-US".into(), Default::default())); + } + languages + }), + template: val.template, + fragment_paths: val.fragment_paths, + component_group_refs: val.component_group_refs, + component_refs: val.component_refs, + feature_group_refs: val.feature_group_refs, + feature_refs: val.feature_refs, + merge_refs: val.merge_refs, + skip_webview_install: val.skip_webview_install, + license: val.license, + enable_elevated_update_task: val.enable_elevated_update_task, + banner_path: val.banner_path, + dialog_image_path: val.dialog_image_path, + fips_compliant: val.fips_compliant, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct MacOsSettings { + pub frameworks: Option>, + pub minimum_system_version: Option, + pub license: Option, + pub exception_domain: Option, + pub signing_identity: Option, + pub provider_short_name: Option, + pub entitlements: Option, + pub info_plist_path: Option, +} + +#[cfg(feature = "cli")] +impl From for tauri_bundler::MacOsSettings { + fn from(val: MacOsSettings) -> Self { + tauri_bundler::MacOsSettings { + frameworks: val.frameworks, + minimum_system_version: val.minimum_system_version, + license: val.license, + exception_domain: val.exception_domain, + signing_identity: val.signing_identity, + provider_short_name: val.provider_short_name, + entitlements: val.entitlements, + info_plist_path: val.info_plist_path, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct WindowsSettings { + pub digest_algorithm: Option, + pub certificate_thumbprint: Option, + pub timestamp_url: Option, + pub tsp: bool, + pub wix: Option, + pub icon_path: Option, + pub webview_install_mode: WebviewInstallMode, + pub webview_fixed_runtime_path: Option, + pub allow_downgrades: bool, + pub nsis: Option, +} + +#[cfg(feature = "cli")] +impl From for tauri_bundler::WindowsSettings { + fn from(val: WindowsSettings) -> Self { + tauri_bundler::WindowsSettings { + digest_algorithm: val.digest_algorithm, + certificate_thumbprint: val.certificate_thumbprint, + timestamp_url: val.timestamp_url, + tsp: val.tsp, + wix: val.wix.map(Into::into), + icon_path: val.icon_path.unwrap_or("icons/icon.ico".into()), + webview_install_mode: val.webview_install_mode.into(), + webview_fixed_runtime_path: val.webview_fixed_runtime_path, + allow_downgrades: val.allow_downgrades, + nsis: val.nsis.map(Into::into), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NsisSettings { + pub template: Option, + pub license: Option, + pub header_image: Option, + pub sidebar_image: Option, + pub installer_icon: Option, + pub install_mode: NSISInstallerMode, + pub languages: Option>, + pub custom_language_files: Option>, + pub display_language_selector: bool, +} + +#[cfg(feature = "cli")] +impl From for tauri_bundler::NsisSettings { + fn from(val: NsisSettings) -> Self { + tauri_bundler::NsisSettings { + license: val.license, + header_image: val.header_image, + sidebar_image: val.sidebar_image, + installer_icon: val.installer_icon, + install_mode: val.install_mode.into(), + languages: val.languages, + display_language_selector: val.display_language_selector, + custom_language_files: None, + template: None, + compression: None, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum NSISInstallerMode { + CurrentUser, + PerMachine, + Both, +} + +#[cfg(feature = "cli")] +impl From for tauri_utils::config::NSISInstallerMode { + fn from(val: NSISInstallerMode) -> Self { + match val { + NSISInstallerMode::CurrentUser => tauri_utils::config::NSISInstallerMode::CurrentUser, + NSISInstallerMode::PerMachine => tauri_utils::config::NSISInstallerMode::PerMachine, + NSISInstallerMode::Both => tauri_utils::config::NSISInstallerMode::Both, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum WebviewInstallMode { + Skip, + DownloadBootstrapper { silent: bool }, + EmbedBootstrapper { silent: bool }, + OfflineInstaller { silent: bool }, + FixedRuntime { path: PathBuf }, +} + +#[cfg(feature = "cli")] +impl WebviewInstallMode { + fn into(self) -> tauri_utils::config::WebviewInstallMode { + match self { + Self::Skip => tauri_utils::config::WebviewInstallMode::Skip, + Self::DownloadBootstrapper { silent } => { + tauri_utils::config::WebviewInstallMode::DownloadBootstrapper { silent } + } + Self::EmbedBootstrapper { silent } => { + tauri_utils::config::WebviewInstallMode::EmbedBootstrapper { silent } + } + Self::OfflineInstaller { silent } => { + tauri_utils::config::WebviewInstallMode::OfflineInstaller { silent } + } + Self::FixedRuntime { path } => { + tauri_utils::config::WebviewInstallMode::FixedRuntime { path } + } + } + } +} + +impl Default for WebviewInstallMode { + fn default() -> Self { + Self::OfflineInstaller { silent: false } + } +} diff --git a/packages/cli/src/cargo.rs b/packages/cli-config/src/cargo.rs similarity index 69% rename from packages/cli/src/cargo.rs rename to packages/cli-config/src/cargo.rs index b52fa4b09..97e4040db 100644 --- a/packages/cli/src/cargo.rs +++ b/packages/cli-config/src/cargo.rs @@ -1,12 +1,33 @@ //! Utilities for working with cargo and rust files -use crate::error::{Error, Result}; +use std::error::Error; use std::{ - env, fs, + env, + fmt::{Display, Formatter}, + fs, path::{Path, PathBuf}, process::Command, str, }; +#[derive(Debug, Clone)] +pub struct CargoError { + msg: String, +} + +impl CargoError { + pub fn new(msg: String) -> Self { + Self { msg } + } +} + +impl Display for CargoError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "CargoError: {}", self.msg) + } +} + +impl Error for CargoError {} + /// How many parent folders are searched for a `Cargo.toml` const MAX_ANCESTORS: u32 = 10; @@ -19,7 +40,7 @@ pub struct Metadata { /// Returns the root of the crate that the command is run from /// /// If the command is run from the workspace root, this will return the top-level Cargo.toml -pub fn crate_root() -> Result { +pub fn crate_root() -> Result { // From the current directory we work our way up, looking for `Cargo.toml` env::current_dir() .ok() @@ -35,7 +56,7 @@ pub fn crate_root() -> Result { None }) .ok_or_else(|| { - Error::CargoError("Failed to find directory containing Cargo.toml".to_string()) + CargoError::new("Failed to find directory containing Cargo.toml".to_string()) }) } @@ -53,11 +74,11 @@ fn contains_manifest(path: &Path) -> bool { impl Metadata { /// Returns the struct filled from `cargo metadata` output /// TODO @Jon, find a different way that doesn't rely on the cargo metadata command (it's slow) - pub fn get() -> Result { + pub fn get() -> Result { let output = Command::new("cargo") .args(["metadata"]) .output() - .map_err(|_| Error::CargoError("Manifset".to_string()))?; + .map_err(|_| CargoError::new("Manifset".to_string()))?; if !output.status.success() { let mut msg = str::from_utf8(&output.stderr).unwrap().trim(); @@ -65,22 +86,22 @@ impl Metadata { msg = &msg[7..]; } - return Err(Error::CargoError(msg.to_string())); + return Err(CargoError::new(msg.to_string())); } let stdout = str::from_utf8(&output.stdout).unwrap(); if let Some(line) = stdout.lines().next() { let meta: serde_json::Value = serde_json::from_str(line) - .map_err(|_| Error::CargoError("InvalidOutput".to_string()))?; + .map_err(|_| CargoError::new("InvalidOutput".to_string()))?; let workspace_root = meta["workspace_root"] .as_str() - .ok_or_else(|| Error::CargoError("InvalidOutput".to_string()))? + .ok_or_else(|| CargoError::new("InvalidOutput".to_string()))? .into(); let target_directory = meta["target_directory"] .as_str() - .ok_or_else(|| Error::CargoError("InvalidOutput".to_string()))? + .ok_or_else(|| CargoError::new("InvalidOutput".to_string()))? .into(); return Ok(Self { @@ -89,6 +110,6 @@ impl Metadata { }); } - Err(Error::CargoError("InvalidOutput".to_string())) + Err(CargoError::new("InvalidOutput".to_string())) } } diff --git a/packages/cli-config/src/config.rs b/packages/cli-config/src/config.rs new file mode 100644 index 000000000..716b2bac4 --- /dev/null +++ b/packages/cli-config/src/config.rs @@ -0,0 +1,492 @@ +use crate::BundleConfig; +use crate::CargoError; +use core::fmt::{Display, Formatter}; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Debug)] +#[cfg_attr(feature = "cli", derive(clap::ValueEnum))] +pub enum Platform { + #[cfg_attr(feature = "cli", clap(name = "web"))] + #[serde(rename = "web")] + Web, + #[cfg_attr(feature = "cli", clap(name = "desktop"))] + #[serde(rename = "desktop")] + Desktop, + #[cfg_attr(feature = "cli", clap(name = "fullstack"))] + #[serde(rename = "fullstack")] + Fullstack, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DioxusConfig { + pub application: ApplicationConfig, + + pub web: WebConfig, + + #[serde(default)] + pub bundle: BundleConfig, + + #[cfg(feature = "cli")] + #[serde(default = "default_plugin")] + pub plugin: toml::Value, +} + +#[cfg(feature = "cli")] +fn default_plugin() -> toml::Value { + toml::Value::Boolean(true) +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct LoadDioxusConfigError { + location: String, + error: String, +} + +impl std::fmt::Display for LoadDioxusConfigError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{} {}", self.location, self.error) + } +} + +impl std::error::Error for LoadDioxusConfigError {} + +#[derive(Debug)] +pub enum CrateConfigError { + Cargo(CargoError), + Io(std::io::Error), + #[cfg(feature = "cli")] + Toml(toml::de::Error), + LoadDioxusConfig(LoadDioxusConfigError), +} + +impl From for CrateConfigError { + fn from(err: CargoError) -> Self { + Self::Cargo(err) + } +} + +impl From for CrateConfigError { + fn from(err: std::io::Error) -> Self { + Self::Io(err) + } +} + +#[cfg(feature = "cli")] +impl From for CrateConfigError { + fn from(err: toml::de::Error) -> Self { + Self::Toml(err) + } +} + +impl From for CrateConfigError { + fn from(err: LoadDioxusConfigError) -> Self { + Self::LoadDioxusConfig(err) + } +} + +impl Display for CrateConfigError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::Cargo(err) => write!(f, "{}", err), + Self::Io(err) => write!(f, "{}", err), + #[cfg(feature = "cli")] + Self::Toml(err) => write!(f, "{}", err), + Self::LoadDioxusConfig(err) => write!(f, "{}", err), + } + } +} + +impl std::error::Error for CrateConfigError {} + +impl DioxusConfig { + #[cfg(feature = "cli")] + /// Load the dioxus config from a path + pub fn load(bin: Option) -> Result, CrateConfigError> { + let crate_dir = crate::cargo::crate_root(); + + let crate_dir = match crate_dir { + Ok(dir) => { + if let Some(bin) = bin { + dir.join(bin) + } else { + dir + } + } + Err(_) => return Ok(None), + }; + let crate_dir = crate_dir.as_path(); + + let Some(dioxus_conf_file) = acquire_dioxus_toml(crate_dir) else { + return Ok(None); + }; + + let dioxus_conf_file = dioxus_conf_file.as_path(); + let cfg = toml::from_str::(&std::fs::read_to_string(dioxus_conf_file)?) + .map_err(|err| { + let error_location = dioxus_conf_file + .strip_prefix(crate_dir) + .unwrap_or(dioxus_conf_file) + .display(); + CrateConfigError::LoadDioxusConfig(LoadDioxusConfigError { + location: error_location.to_string(), + error: err.to_string(), + }) + }) + .map(Some); + match cfg { + Ok(Some(mut cfg)) => { + let name = cfg.application.name.clone(); + if cfg.bundle.identifier.is_none() { + cfg.bundle.identifier = Some(format!("io.github.{name}")); + } + if cfg.bundle.publisher.is_none() { + cfg.bundle.publisher = Some(name); + } + Ok(Some(cfg)) + } + cfg => cfg, + } + } +} + +#[cfg(feature = "cli")] +fn acquire_dioxus_toml(dir: &std::path::Path) -> Option { + // prefer uppercase + let uppercase_conf = dir.join("Dioxus.toml"); + if uppercase_conf.is_file() { + return Some(uppercase_conf); + } + + // lowercase is fine too + let lowercase_conf = dir.join("dioxus.toml"); + if lowercase_conf.is_file() { + return Some(lowercase_conf); + } + + None +} + +impl Default for DioxusConfig { + fn default() -> Self { + let name = default_name(); + Self { + application: ApplicationConfig { + name: name.clone(), + default_platform: default_platform(), + out_dir: out_dir_default(), + asset_dir: asset_dir_default(), + + #[cfg(feature = "cli")] + tools: Default::default(), + + sub_package: None, + }, + web: WebConfig { + app: WebAppConfig { + title: default_title(), + base_path: None, + }, + proxy: vec![], + watcher: Default::default(), + resource: WebResourceConfig { + dev: WebDevResourceConfig { + style: vec![], + script: vec![], + }, + style: Some(vec![]), + script: Some(vec![]), + }, + https: WebHttpsConfig { + enabled: None, + mkcert: None, + key_path: None, + cert_path: None, + }, + }, + bundle: BundleConfig { + identifier: Some(format!("io.github.{name}")), + publisher: Some(name), + ..Default::default() + }, + #[cfg(feature = "cli")] + plugin: toml::Value::Table(toml::map::Map::new()), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ApplicationConfig { + #[serde(default = "default_name")] + pub name: String, + #[serde(default = "default_platform")] + pub default_platform: Platform, + #[serde(default = "out_dir_default")] + pub out_dir: PathBuf, + #[serde(default = "asset_dir_default")] + pub asset_dir: PathBuf, + + #[cfg(feature = "cli")] + #[serde(default)] + pub tools: std::collections::HashMap, + + #[serde(default)] + pub sub_package: Option, +} + +fn default_name() -> String { + "name".into() +} + +fn default_platform() -> Platform { + Platform::Web +} + +fn asset_dir_default() -> PathBuf { + PathBuf::from("public") +} + +fn out_dir_default() -> PathBuf { + PathBuf::from("dist") +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct WebConfig { + #[serde(default)] + pub app: WebAppConfig, + #[serde(default)] + pub proxy: Vec, + #[serde(default)] + pub watcher: WebWatcherConfig, + #[serde(default)] + pub resource: WebResourceConfig, + #[serde(default)] + pub https: WebHttpsConfig, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct WebAppConfig { + #[serde(default = "default_title")] + pub title: String, + pub base_path: Option, +} + +impl Default for WebAppConfig { + fn default() -> Self { + Self { + title: default_title(), + base_path: None, + } + } +} + +fn default_title() -> String { + "dioxus | ⛺".into() +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct WebProxyConfig { + pub backend: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct WebWatcherConfig { + #[serde(default = "watch_path_default")] + pub watch_path: Vec, + #[serde(default)] + pub reload_html: bool, + #[serde(default = "true_bool")] + pub index_on_404: bool, +} + +impl Default for WebWatcherConfig { + fn default() -> Self { + Self { + watch_path: watch_path_default(), + reload_html: false, + index_on_404: true, + } + } +} + +fn watch_path_default() -> Vec { + vec![PathBuf::from("src"), PathBuf::from("examples")] +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct WebResourceConfig { + pub dev: WebDevResourceConfig, + pub style: Option>, + pub script: Option>, +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct WebDevResourceConfig { + #[serde(default)] + pub style: Vec, + #[serde(default)] + pub script: Vec, +} + +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct WebHttpsConfig { + pub enabled: Option, + pub mkcert: Option, + pub key_path: Option, + pub cert_path: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CrateConfig { + pub out_dir: PathBuf, + pub crate_dir: PathBuf, + pub workspace_dir: PathBuf, + pub target_dir: PathBuf, + pub asset_dir: PathBuf, + #[cfg(feature = "cli")] + pub manifest: cargo_toml::Manifest, + pub executable: ExecutableType, + pub dioxus_config: DioxusConfig, + pub release: bool, + pub hot_reload: bool, + pub cross_origin_policy: bool, + pub verbose: bool, + pub custom_profile: Option, + pub features: Option>, + pub target: Option, + pub cargo_args: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum ExecutableType { + Binary(String), + Lib(String), + Example(String), +} + +impl CrateConfig { + #[cfg(feature = "cli")] + pub fn new(bin: Option) -> Result { + let dioxus_config = DioxusConfig::load(bin.clone())?.unwrap_or_default(); + + let crate_root = crate::crate_root()?; + + let crate_dir = if let Some(package) = &dioxus_config.application.sub_package { + crate_root.join(package) + } else if let Some(bin) = bin { + crate_root.join(bin) + } else { + crate_root + }; + + let meta = crate::Metadata::get()?; + let workspace_dir = meta.workspace_root; + let target_dir = meta.target_directory; + + let out_dir = crate_dir.join(&dioxus_config.application.out_dir); + + let cargo_def = &crate_dir.join("Cargo.toml"); + + let asset_dir = crate_dir.join(&dioxus_config.application.asset_dir); + + let manifest = cargo_toml::Manifest::from_path(cargo_def).unwrap(); + + let mut output_filename = String::from("dioxus_app"); + if let Some(package) = &manifest.package.as_ref() { + output_filename = match &package.default_run { + Some(default_run_target) => default_run_target.to_owned(), + None => manifest + .bin + .iter() + .find(|b| b.name == manifest.package.as_ref().map(|pkg| pkg.name.clone())) + .or(manifest + .bin + .iter() + .find(|b| b.path == Some("src/main.rs".to_owned()))) + .or(manifest.bin.first()) + .or(manifest.lib.as_ref()) + .and_then(|prod| prod.name.clone()) + .unwrap_or(String::from("dioxus_app")), + }; + } + + let executable = ExecutableType::Binary(output_filename); + + let release = false; + let hot_reload = false; + let verbose = false; + let custom_profile = None; + let features = None; + let target = None; + let cargo_args = vec![]; + + Ok(Self { + out_dir, + crate_dir, + workspace_dir, + target_dir, + asset_dir, + #[cfg(feature = "cli")] + manifest, + executable, + release, + dioxus_config, + hot_reload, + cross_origin_policy: false, + custom_profile, + features, + verbose, + target, + cargo_args, + }) + } + + pub fn as_example(&mut self, example_name: String) -> &mut Self { + self.executable = ExecutableType::Example(example_name); + self + } + + pub fn with_release(&mut self, release: bool) -> &mut Self { + self.release = release; + self + } + + pub fn with_hot_reload(&mut self, hot_reload: bool) -> &mut Self { + self.hot_reload = hot_reload; + self + } + + pub fn with_cross_origin_policy(&mut self, cross_origin_policy: bool) -> &mut Self { + self.cross_origin_policy = cross_origin_policy; + self + } + + pub fn with_verbose(&mut self, verbose: bool) -> &mut Self { + self.verbose = verbose; + self + } + + pub fn set_profile(&mut self, profile: String) -> &mut Self { + self.custom_profile = Some(profile); + self + } + + pub fn set_features(&mut self, features: Vec) -> &mut Self { + self.features = Some(features); + self + } + + pub fn set_target(&mut self, target: String) -> &mut Self { + self.target = Some(target); + self + } + + pub fn set_cargo_args(&mut self, cargo_args: Vec) -> &mut Self { + self.cargo_args = cargo_args; + self + } +} + +fn true_bool() -> bool { + true +} diff --git a/packages/cli-config/src/lib.rs b/packages/cli-config/src/lib.rs new file mode 100644 index 000000000..da7446918 --- /dev/null +++ b/packages/cli-config/src/lib.rs @@ -0,0 +1,58 @@ +#![doc = include_str!("../README.md")] +#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")] +#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")] + +mod config; +pub use config::*; +mod bundle; +pub use bundle::*; +mod cargo; +pub use cargo::*; + +#[doc(hidden)] +pub mod __private { + use crate::CrateConfig; + + pub const CONFIG_ENV: &str = "DIOXUS_CONFIG"; + + pub fn save_config(config: &CrateConfig) -> CrateConfigDropGuard { + std::env::set_var(CONFIG_ENV, serde_json::to_string(config).unwrap()); + CrateConfigDropGuard + } + + /// A guard that removes the config from the environment when dropped. + pub struct CrateConfigDropGuard; + + impl Drop for CrateConfigDropGuard { + fn drop(&mut self) { + std::env::remove_var(CONFIG_ENV); + } + } +} + +/// An error that occurs when the dioxus CLI was not used to build the application. +#[derive(Debug)] +pub struct DioxusCLINotUsed; + +impl std::fmt::Display for DioxusCLINotUsed { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("dioxus CLI was not used to build the application") + } +} + +impl std::error::Error for DioxusCLINotUsed {} + +/// The current crate's configuration. +pub static CURRENT_CONFIG: once_cell::sync::Lazy< + Result, +> = once_cell::sync::Lazy::new(|| { + CURRENT_CONFIG_JSON + .and_then(|config| serde_json::from_str(config).ok()) + .ok_or_else(|| { + tracing::error!("A library is trying to access the crate's configuration, but the dioxus CLI was not used to build the application."); + DioxusCLINotUsed + }) +}); + +/// The current crate's configuration. +pub const CURRENT_CONFIG_JSON: Option<&str> = std::option_env!("DIOXUS_CONFIG"); diff --git a/packages/cli/Cargo.toml b/packages/cli/Cargo.toml index 37dcb6e48..d22ac7031 100644 --- a/packages/cli/Cargo.toml +++ b/packages/cli/Cargo.toml @@ -14,6 +14,7 @@ clap = { version = "4.2", features = ["derive"] } thiserror = { workspace = true } wasm-bindgen-cli-support = "0.2" colored = "2.0.0" +dioxus-cli-config = { workspace = true, features = ["cli"] } # features log = "0.4.14" @@ -75,7 +76,7 @@ toml_edit = "0.19.11" tauri-bundler = { version = "=1.4.*", features = ["native-tls-vendored"] } tauri-utils = "=1.5.*" -manganis-cli-support= { git = "https://github.com/DioxusLabs/collect-assets", features = ["webp", "html"] } +manganis-cli-support = { git = "https://github.com/DioxusLabs/collect-assets", features = ["webp", "html"] } dioxus-autofmt = { workspace = true } dioxus-check = { workspace = true } diff --git a/packages/cli/src/assets.rs b/packages/cli/src/assets.rs new file mode 100644 index 000000000..081c1c85c --- /dev/null +++ b/packages/cli/src/assets.rs @@ -0,0 +1,60 @@ +use std::{fs::File, io::Write, path::PathBuf}; + +use crate::Result; +use dioxus_cli_config::CrateConfig; +use manganis_cli_support::{AssetManifest, AssetManifestExt}; + +pub fn asset_manifest(crate_config: &CrateConfig) -> AssetManifest { + AssetManifest::load_from_path( + crate_config.crate_dir.join("Cargo.toml"), + crate_config.workspace_dir.join("Cargo.lock"), + ) +} + +/// Create a head file that contains all of the imports for assets that the user project uses +pub fn create_assets_head(config: &CrateConfig) -> Result<()> { + let manifest = asset_manifest(config); + let mut file = File::create(config.out_dir.join("__assets_head.html"))?; + file.write_all(manifest.head().as_bytes())?; + Ok(()) +} + +/// Process any assets collected from the binary +pub(crate) fn process_assets(config: &CrateConfig) -> anyhow::Result<()> { + let manifest = asset_manifest(config); + + let static_asset_output_dir = PathBuf::from( + config + .dioxus_config + .web + .app + .base_path + .clone() + .unwrap_or_default(), + ); + let static_asset_output_dir = config.out_dir.join(static_asset_output_dir); + + manifest.copy_static_assets_to(static_asset_output_dir)?; + + Ok(()) +} + +/// A guard that sets up the environment for the web renderer to compile in. This guard sets the location that assets will be served from +pub(crate) struct WebAssetConfigDropGuard; + +impl WebAssetConfigDropGuard { + pub fn new() -> Self { + // Set up the collect asset config + manganis_cli_support::Config::default() + .with_assets_serve_location("/") + .save(); + Self {} + } +} + +impl Drop for WebAssetConfigDropGuard { + fn drop(&mut self) { + // Reset the config + manganis_cli_support::Config::default().save(); + } +} diff --git a/packages/cli/src/builder.rs b/packages/cli/src/builder.rs index 6d1ccea05..bed60ff5a 100644 --- a/packages/cli/src/builder.rs +++ b/packages/cli/src/builder.rs @@ -1,16 +1,18 @@ use crate::{ - config::{CrateConfig, ExecutableType}, + assets::{asset_manifest, create_assets_head, process_assets, WebAssetConfigDropGuard}, error::{Error, Result}, tools::Tool, }; use cargo_metadata::{diagnostic::Diagnostic, Message}; +use dioxus_cli_config::crate_root; +use dioxus_cli_config::CrateConfig; +use dioxus_cli_config::ExecutableType; use indicatif::{ProgressBar, ProgressStyle}; use lazy_static::lazy_static; -use manganis_cli_support::AssetManifestExt; use serde::Serialize; use std::{ fs::{copy, create_dir_all, File}, - io::{Read, Write}, + io::Read, panic, path::PathBuf, time::Duration, @@ -51,6 +53,7 @@ pub fn build(config: &CrateConfig, _: bool, skip_assets: bool) -> Result Result cmd.arg("--bin").arg(name), - crate::ExecutableType::Lib(name) => cmd.arg("--lib").arg(name), - crate::ExecutableType::Example(name) => cmd.arg("--example").arg(name), + ExecutableType::Binary(name) => cmd.arg("--bin").arg(name), + ExecutableType::Lib(name) => cmd.arg("--lib").arg(name), + ExecutableType::Example(name) => cmd.arg("--example").arg(name), }; let warning_messages = prettier_build(cmd)?; @@ -326,7 +330,7 @@ pub fn build_desktop( let file_name: String; let mut res_path = match &config.executable { - crate::ExecutableType::Binary(name) | crate::ExecutableType::Lib(name) => { + ExecutableType::Binary(name) | ExecutableType::Lib(name) => { file_name = name.clone(); config .target_dir @@ -334,7 +338,7 @@ pub fn build_desktop( .join(release_type) .join(name) } - crate::ExecutableType::Example(name) => { + ExecutableType::Example(name) => { file_name = name.clone(); config .target_dir @@ -399,13 +403,7 @@ pub fn build_desktop( log::info!( "🚩 Build completed: [./{}]", - config - .dioxus_config - .application - .out_dir - .clone() - .unwrap_or_else(|| PathBuf::from("dist")) - .display() + config.dioxus_config.application.out_dir.clone().display() ); println!("build desktop done"); @@ -416,13 +414,6 @@ pub fn build_desktop( }) } -fn create_assets_head(config: &CrateConfig) -> Result<()> { - let manifest = config.asset_manifest(); - let mut file = File::create(config.out_dir.join("__assets_head.html"))?; - file.write_all(manifest.head().as_bytes())?; - Ok(()) -} - fn prettier_build(cmd: subprocess::Exec) -> anyhow::Result> { let mut warning_messages: Vec = vec![]; @@ -482,7 +473,7 @@ fn prettier_build(cmd: subprocess::Exec) -> anyhow::Result> { pub fn gen_page(config: &CrateConfig, serve: bool, skip_assets: bool) -> String { let _gaurd = WebAssetConfigDropGuard::new(); - let crate_root = crate::cargo::crate_root().unwrap(); + let crate_root = crate_root().unwrap(); let custom_html_file = crate_root.join("index.html"); let mut html = if custom_html_file.is_file() { let mut buf = String::new(); @@ -502,8 +493,8 @@ pub fn gen_page(config: &CrateConfig, serve: bool, skip_assets: bool) -> String let mut script_list = resources.script.unwrap_or_default(); if serve { - let mut dev_style = resources.dev.style.clone().unwrap_or_default(); - let mut dev_script = resources.dev.script.unwrap_or_default(); + let mut dev_style = resources.dev.style.clone(); + let mut dev_script = resources.dev.script.clone(); style_list.append(&mut dev_style); script_list.append(&mut dev_script); } @@ -520,13 +511,12 @@ pub fn gen_page(config: &CrateConfig, serve: bool, skip_assets: bool) -> String .application .tools .clone() - .unwrap_or_default() .contains_key("tailwindcss") { style_str.push_str("\n"); } if !skip_assets { - let manifest = config.asset_manifest(); + let manifest = asset_manifest(config); style_str.push_str(&manifest.head()); } @@ -577,13 +567,7 @@ pub fn gen_page(config: &CrateConfig, serve: bool, skip_assets: bool) -> String ); } - let title = config - .dioxus_config - .web - .app - .title - .clone() - .unwrap_or_else(|| "dioxus | ⛺".into()); + let title = config.dioxus_config.web.app.title.clone(); replace_or_insert_before("{app_title}", &title, " Result> { let mut result = vec![]; let dioxus_config = &config.dioxus_config; - let dioxus_tools = dioxus_config.application.tools.clone().unwrap_or_default(); + let dioxus_tools = dioxus_config.application.tools.clone(); // check sass tool state let sass = Tool::Sass; @@ -748,42 +732,3 @@ fn build_assets(config: &CrateConfig) -> Result> { Ok(result) } - -/// Process any assets collected from the binary -fn process_assets(config: &CrateConfig) -> anyhow::Result<()> { - let manifest = config.asset_manifest(); - - let static_asset_output_dir = PathBuf::from( - config - .dioxus_config - .web - .app - .base_path - .clone() - .unwrap_or_default(), - ); - let static_asset_output_dir = config.out_dir.join(static_asset_output_dir); - - manifest.copy_static_assets_to(static_asset_output_dir)?; - - Ok(()) -} - -pub(crate) struct WebAssetConfigDropGuard; - -impl WebAssetConfigDropGuard { - pub fn new() -> Self { - // Set up the collect asset config - manganis_cli_support::Config::default() - .with_assets_serve_location("/") - .save(); - Self {} - } -} - -impl Drop for WebAssetConfigDropGuard { - fn drop(&mut self) { - // Reset the config - manganis_cli_support::Config::default().save(); - } -} diff --git a/packages/cli/src/cli/autoformat.rs b/packages/cli/src/cli/autoformat.rs index 812a4c325..016b5e87d 100644 --- a/packages/cli/src/cli/autoformat.rs +++ b/packages/cli/src/cli/autoformat.rs @@ -139,7 +139,7 @@ async fn format_file( /// /// Doesn't do mod-descending, so it will still try to format unreachable files. TODO. async fn autoformat_project(check: bool) -> Result<()> { - let crate_config = crate::CrateConfig::new(None)?; + let crate_config = dioxus_cli_config::CrateConfig::new(None)?; let files_to_format = get_project_files(&crate_config); diff --git a/packages/cli/src/cli/build.rs b/packages/cli/src/cli/build.rs index d8032f034..fb0bdfb24 100644 --- a/packages/cli/src/cli/build.rs +++ b/packages/cli/src/cli/build.rs @@ -1,8 +1,9 @@ +use crate::assets::WebAssetConfigDropGuard; #[cfg(feature = "plugin")] use crate::plugin::PluginManager; use crate::server::fullstack::FullstackServerEnvGuard; use crate::server::fullstack::FullstackWebEnvGuard; -use crate::{cfg::Platform, WebAssetConfigDropGuard}; +use dioxus_cli_config::Platform; use super::*; @@ -16,7 +17,7 @@ pub struct Build { impl Build { pub fn build(self, bin: Option, target_dir: Option<&std::path::Path>) -> Result<()> { - let mut crate_config = crate::CrateConfig::new(bin)?; + let mut crate_config = dioxus_cli_config::CrateConfig::new(bin)?; if let Some(target_dir) = target_dir { crate_config.target_dir = target_dir.to_path_buf(); } @@ -96,14 +97,7 @@ impl Build { let mut file = std::fs::File::create( crate_config .crate_dir - .join( - crate_config - .dioxus_config - .application - .out_dir - .clone() - .unwrap_or_else(|| PathBuf::from("dist")), - ) + .join(crate_config.dioxus_config.application.out_dir.clone()) .join("index.html"), )?; file.write_all(temp.as_bytes())?; diff --git a/packages/cli/src/cli/bundle.rs b/packages/cli/src/cli/bundle.rs index aaa823373..a2231b31f 100644 --- a/packages/cli/src/cli/bundle.rs +++ b/packages/cli/src/cli/bundle.rs @@ -1,4 +1,5 @@ use core::panic; +use dioxus_cli_config::ExecutableType; use std::{fs::create_dir_all, str::FromStr}; use tauri_bundler::{BundleSettings, PackageSettings, SettingsBuilder}; @@ -62,7 +63,7 @@ impl From for tauri_bundler::PackageType { impl Bundle { pub fn bundle(self, bin: Option) -> Result<()> { - let mut crate_config = crate::CrateConfig::new(bin)?; + let mut crate_config = dioxus_cli_config::CrateConfig::new(bin)?; // change the release state. crate_config.with_release(self.build.release); @@ -89,9 +90,9 @@ impl Bundle { let package = crate_config.manifest.package.unwrap(); let mut name: PathBuf = match &crate_config.executable { - crate::ExecutableType::Binary(name) - | crate::ExecutableType::Lib(name) - | crate::ExecutableType::Example(name) => name, + ExecutableType::Binary(name) + | ExecutableType::Lib(name) + | ExecutableType::Example(name) => name, } .into(); if cfg!(windows) { diff --git a/packages/cli/src/cli/cfg.rs b/packages/cli/src/cli/cfg.rs index 62212e574..877fdff33 100644 --- a/packages/cli/src/cli/cfg.rs +++ b/packages/cli/src/cli/cfg.rs @@ -1,5 +1,4 @@ -use clap::ValueEnum; -use serde::Serialize; +use dioxus_cli_config::Platform; use super::*; @@ -154,19 +153,6 @@ pub struct ConfigOptsServe { pub cargo_args: Vec, } -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Deserialize, Debug)] -pub enum Platform { - #[clap(name = "web")] - #[serde(rename = "web")] - Web, - #[clap(name = "desktop")] - #[serde(rename = "desktop")] - Desktop, - #[clap(name = "fullstack")] - #[serde(rename = "fullstack")] - Fullstack, -} - /// Config options for the bundling system. #[derive(Clone, Debug, Default, Deserialize, Parser)] pub struct ConfigOptsBundle { diff --git a/packages/cli/src/cli/check.rs b/packages/cli/src/cli/check.rs index a4d84addb..0832647f2 100644 --- a/packages/cli/src/cli/check.rs +++ b/packages/cli/src/cli/check.rs @@ -47,7 +47,7 @@ async fn check_file_and_report(path: PathBuf) -> Result<()> { /// /// Doesn't do mod-descending, so it will still try to check unreachable files. TODO. async fn check_project_and_report() -> Result<()> { - let crate_config = crate::CrateConfig::new(None)?; + let crate_config = dioxus_cli_config::CrateConfig::new(None)?; let mut files_to_check = vec![]; collect_rs_files(&crate_config.crate_dir, &mut files_to_check); diff --git a/packages/cli/src/cli/clean.rs b/packages/cli/src/cli/clean.rs index 9a0f0d4fb..04e0cb45b 100644 --- a/packages/cli/src/cli/clean.rs +++ b/packages/cli/src/cli/clean.rs @@ -7,7 +7,7 @@ pub struct Clean {} impl Clean { pub fn clean(self, bin: Option) -> Result<()> { - let crate_config = crate::CrateConfig::new(bin)?; + let crate_config = dioxus_cli_config::CrateConfig::new(bin)?; let output = Command::new("cargo") .arg("clean") @@ -19,11 +19,7 @@ impl Clean { return custom_error!("Cargo clean failed."); } - let out_dir = crate_config - .dioxus_config - .application - .out_dir - .unwrap_or_else(|| PathBuf::from("dist")); + let out_dir = crate_config.dioxus_config.application.out_dir; if crate_config.crate_dir.join(&out_dir).is_dir() { remove_dir_all(crate_config.crate_dir.join(&out_dir))?; } diff --git a/packages/cli/src/cli/config.rs b/packages/cli/src/cli/config.rs index 45fcc2f23..1a1072c2f 100644 --- a/packages/cli/src/cli/config.rs +++ b/packages/cli/src/cli/config.rs @@ -1,3 +1,5 @@ +use dioxus_cli_config::crate_root; + use super::*; /// Dioxus config file controls @@ -26,7 +28,7 @@ pub enum Config { impl Config { pub fn config(self) -> Result<()> { - let crate_root = crate::cargo::crate_root()?; + let crate_root = crate_root()?; match self { Config::Init { name, @@ -48,7 +50,10 @@ impl Config { log::info!("🚩 Init config file completed."); } Config::FormatPrint {} => { - println!("{:#?}", crate::CrateConfig::new(None)?.dioxus_config); + println!( + "{:#?}", + dioxus_cli_config::CrateConfig::new(None)?.dioxus_config + ); } Config::CustomHtml {} => { let html_path = crate_root.join("index.html"); diff --git a/packages/cli/src/cli/mod.rs b/packages/cli/src/cli/mod.rs index 9b3be33bc..4295bfa9b 100644 --- a/packages/cli/src/cli/mod.rs +++ b/packages/cli/src/cli/mod.rs @@ -15,9 +15,10 @@ use crate::{ cfg::{ConfigOptsBuild, ConfigOptsServe}, custom_error, error::Result, - gen_page, server, CrateConfig, Error, + gen_page, server, Error, }; use clap::{Parser, Subcommand}; +use dioxus_cli_config::CrateConfig; use html_parser::Dom; use serde::Deserialize; use std::{ diff --git a/packages/cli/src/cli/serve.rs b/packages/cli/src/cli/serve.rs index 1bb24ecdb..4b0f3f457 100644 --- a/packages/cli/src/cli/serve.rs +++ b/packages/cli/src/cli/serve.rs @@ -1,3 +1,5 @@ +use dioxus_cli_config::Platform; + use super::*; use std::{fs::create_dir_all, io::Write, path::PathBuf}; @@ -11,7 +13,7 @@ pub struct Serve { impl Serve { pub async fn serve(self, bin: Option) -> Result<()> { - let mut crate_config = crate::CrateConfig::new(bin)?; + let mut crate_config = dioxus_cli_config::CrateConfig::new(bin)?; let serve_cfg = self.serve.clone(); // change the relase state. @@ -32,9 +34,6 @@ impl Serve { crate_config.set_features(self.serve.features.unwrap()); } - // Subdirectories don't work with the server - crate_config.dioxus_config.web.app.base_path = None; - if let Some(target) = self.serve.target { crate_config.set_target(target); } @@ -47,7 +46,7 @@ impl Serve { .unwrap_or(crate_config.dioxus_config.application.default_platform); match platform { - cfg::Platform::Web => { + Platform::Web => { // generate dev-index page Serve::regen_dev_page(&crate_config, self.serve.skip_assets)?; @@ -60,10 +59,10 @@ impl Serve { ) .await?; } - cfg::Platform::Desktop => { + Platform::Desktop => { server::desktop::startup(crate_config.clone(), &serve_cfg).await?; } - cfg::Platform::Fullstack => { + Platform::Fullstack => { server::fullstack::startup(crate_config.clone(), &serve_cfg).await?; } } @@ -73,14 +72,9 @@ impl Serve { pub fn regen_dev_page(crate_config: &CrateConfig, skip_assets: bool) -> Result<()> { let serve_html = gen_page(crate_config, true, skip_assets); - let dist_path = crate_config.crate_dir.join( - crate_config - .dioxus_config - .application - .out_dir - .clone() - .unwrap_or_else(|| PathBuf::from("dist")), - ); + let dist_path = crate_config + .crate_dir + .join(crate_config.dioxus_config.application.out_dir.clone()); if !dist_path.is_dir() { create_dir_all(&dist_path)?; } diff --git a/packages/cli/src/config.rs b/packages/cli/src/config.rs deleted file mode 100644 index b220fdf37..000000000 --- a/packages/cli/src/config.rs +++ /dev/null @@ -1,608 +0,0 @@ -use crate::{cfg::Platform, error::Result}; -use manganis_cli_support::AssetManifest; -use manganis_cli_support::AssetManifestExt; -use serde::{Deserialize, Serialize}; -use std::{ - collections::HashMap, - path::{Path, PathBuf}, -}; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct DioxusConfig { - pub application: ApplicationConfig, - - pub web: WebConfig, - - #[serde(default)] - pub bundle: BundleConfig, - - #[serde(default = "default_plugin")] - pub plugin: toml::Value, -} - -fn default_plugin() -> toml::Value { - toml::Value::Boolean(true) -} - -impl DioxusConfig { - pub fn load(bin: Option) -> crate::error::Result> { - let crate_dir = crate::cargo::crate_root(); - - let crate_dir = match crate_dir { - Ok(dir) => { - if let Some(bin) = bin { - dir.join(bin) - } else { - dir - } - } - Err(_) => return Ok(None), - }; - let crate_dir = crate_dir.as_path(); - - let Some(dioxus_conf_file) = acquire_dioxus_toml(crate_dir) else { - return Ok(None); - }; - - let dioxus_conf_file = dioxus_conf_file.as_path(); - let cfg = toml::from_str::(&std::fs::read_to_string(dioxus_conf_file)?) - .map_err(|err| { - let error_location = dioxus_conf_file - .strip_prefix(crate_dir) - .unwrap_or(dioxus_conf_file) - .display(); - crate::Error::Unique(format!("{error_location} {err}")) - }) - .map(Some); - match cfg { - Ok(Some(mut cfg)) => { - let name = cfg.application.name.clone(); - if cfg.bundle.identifier.is_none() { - cfg.bundle.identifier = Some(format!("io.github.{name}")); - } - if cfg.bundle.publisher.is_none() { - cfg.bundle.publisher = Some(name); - } - Ok(Some(cfg)) - } - cfg => cfg, - } - } -} - -fn acquire_dioxus_toml(dir: &Path) -> Option { - // prefer uppercase - let uppercase_conf = dir.join("Dioxus.toml"); - if uppercase_conf.is_file() { - return Some(uppercase_conf); - } - - // lowercase is fine too - let lowercase_conf = dir.join("dioxus.toml"); - if lowercase_conf.is_file() { - return Some(lowercase_conf); - } - - None -} - -impl Default for DioxusConfig { - fn default() -> Self { - let name = "name"; - Self { - application: ApplicationConfig { - name: name.into(), - default_platform: Platform::Web, - out_dir: Some(PathBuf::from("dist")), - asset_dir: Some(PathBuf::from("public")), - - tools: None, - - sub_package: None, - }, - web: WebConfig { - app: WebAppConfig { - title: Some("dioxus | ⛺".into()), - base_path: None, - }, - proxy: Some(vec![]), - watcher: WebWatcherConfig { - watch_path: Some(vec![PathBuf::from("src"), PathBuf::from("examples")]), - reload_html: Some(false), - index_on_404: Some(true), - }, - resource: WebResourceConfig { - dev: WebDevResourceConfig { - style: Some(vec![]), - script: Some(vec![]), - }, - style: Some(vec![]), - script: Some(vec![]), - }, - https: WebHttpsConfig { - enabled: None, - mkcert: None, - key_path: None, - cert_path: None, - }, - }, - bundle: BundleConfig { - identifier: Some(format!("io.github.{name}")), - publisher: Some(name.into()), - ..Default::default() - }, - plugin: toml::Value::Table(toml::map::Map::new()), - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ApplicationConfig { - pub name: String, - pub default_platform: Platform, - pub out_dir: Option, - pub asset_dir: Option, - - pub tools: Option>, - - pub sub_package: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct WebConfig { - pub app: WebAppConfig, - pub proxy: Option>, - pub watcher: WebWatcherConfig, - pub resource: WebResourceConfig, - #[serde(default)] - pub https: WebHttpsConfig, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct WebAppConfig { - pub title: Option, - pub base_path: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct WebProxyConfig { - pub backend: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct WebWatcherConfig { - pub watch_path: Option>, - pub reload_html: Option, - pub index_on_404: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct WebResourceConfig { - pub dev: WebDevResourceConfig, - pub style: Option>, - pub script: Option>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct WebDevResourceConfig { - pub style: Option>, - pub script: Option>, -} - -#[derive(Debug, Default, Clone, Serialize, Deserialize)] -pub struct WebHttpsConfig { - pub enabled: Option, - pub mkcert: Option, - pub key_path: Option, - pub cert_path: Option, -} - -#[derive(Debug, Clone)] -pub struct CrateConfig { - pub out_dir: PathBuf, - pub crate_dir: PathBuf, - pub workspace_dir: PathBuf, - pub target_dir: PathBuf, - pub asset_dir: PathBuf, - pub manifest: cargo_toml::Manifest, - pub executable: ExecutableType, - pub dioxus_config: DioxusConfig, - pub release: bool, - pub hot_reload: bool, - pub cross_origin_policy: bool, - pub verbose: bool, - pub custom_profile: Option, - pub features: Option>, - pub target: Option, - pub cargo_args: Vec, -} - -#[derive(Debug, Clone)] -pub enum ExecutableType { - Binary(String), - Lib(String), - Example(String), -} - -impl CrateConfig { - pub fn new(bin: Option) -> Result { - let dioxus_config = DioxusConfig::load(bin.clone())?.unwrap_or_default(); - - let crate_root = crate::cargo::crate_root()?; - - let crate_dir = if let Some(package) = &dioxus_config.application.sub_package { - crate_root.join(package) - } else if let Some(bin) = bin { - crate_root.join(bin) - } else { - crate_root - }; - - let meta = crate::cargo::Metadata::get()?; - let workspace_dir = meta.workspace_root; - let target_dir = meta.target_directory; - - let out_dir = match dioxus_config.application.out_dir { - Some(ref v) => crate_dir.join(v), - None => crate_dir.join("dist"), - }; - - let cargo_def = &crate_dir.join("Cargo.toml"); - - let asset_dir = match dioxus_config.application.asset_dir { - Some(ref v) => crate_dir.join(v), - None => crate_dir.join("public"), - }; - - let manifest = cargo_toml::Manifest::from_path(cargo_def).unwrap(); - - let mut output_filename = String::from("dioxus_app"); - if let Some(package) = &manifest.package.as_ref() { - output_filename = match &package.default_run { - Some(default_run_target) => default_run_target.to_owned(), - None => manifest - .bin - .iter() - .find(|b| b.name == manifest.package.as_ref().map(|pkg| pkg.name.clone())) - .or(manifest - .bin - .iter() - .find(|b| b.path == Some("src/main.rs".to_owned()))) - .or(manifest.bin.first()) - .or(manifest.lib.as_ref()) - .and_then(|prod| prod.name.clone()) - .unwrap_or(String::from("dioxus_app")), - }; - } - - let executable = ExecutableType::Binary(output_filename); - - let release = false; - let hot_reload = false; - let verbose = false; - let custom_profile = None; - let features = None; - let target = None; - let cargo_args = vec![]; - - Ok(Self { - out_dir, - crate_dir, - workspace_dir, - target_dir, - asset_dir, - manifest, - executable, - release, - dioxus_config, - hot_reload, - cross_origin_policy: false, - custom_profile, - features, - verbose, - target, - cargo_args, - }) - } - - pub fn asset_manifest(&self) -> AssetManifest { - AssetManifest::load_from_path( - self.crate_dir.join("Cargo.toml"), - self.workspace_dir.join("Cargo.lock"), - ) - } - - pub fn as_example(&mut self, example_name: String) -> &mut Self { - self.executable = ExecutableType::Example(example_name); - self - } - - pub fn with_release(&mut self, release: bool) -> &mut Self { - self.release = release; - self - } - - pub fn with_hot_reload(&mut self, hot_reload: bool) -> &mut Self { - self.hot_reload = hot_reload; - self - } - - pub fn with_cross_origin_policy(&mut self, cross_origin_policy: bool) -> &mut Self { - self.cross_origin_policy = cross_origin_policy; - self - } - - pub fn with_verbose(&mut self, verbose: bool) -> &mut Self { - self.verbose = verbose; - self - } - - pub fn set_profile(&mut self, profile: String) -> &mut Self { - self.custom_profile = Some(profile); - self - } - - pub fn set_features(&mut self, features: Vec) -> &mut Self { - self.features = Some(features); - self - } - - pub fn set_target(&mut self, target: String) -> &mut Self { - self.target = Some(target); - self - } - - pub fn set_cargo_args(&mut self, cargo_args: Vec) -> &mut Self { - self.cargo_args = cargo_args; - self - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, Default)] -pub struct BundleConfig { - pub identifier: Option, - pub publisher: Option, - pub icon: Option>, - pub resources: Option>, - pub copyright: Option, - pub category: Option, - pub short_description: Option, - pub long_description: Option, - pub external_bin: Option>, - pub deb: Option, - pub macos: Option, - pub windows: Option, -} - -impl From for tauri_bundler::BundleSettings { - fn from(val: BundleConfig) -> Self { - tauri_bundler::BundleSettings { - identifier: val.identifier, - publisher: val.publisher, - icon: val.icon, - resources: val.resources, - copyright: val.copyright, - category: val.category.and_then(|c| c.parse().ok()), - short_description: val.short_description, - long_description: val.long_description, - external_bin: val.external_bin, - deb: val.deb.map(Into::into).unwrap_or_default(), - macos: val.macos.map(Into::into).unwrap_or_default(), - windows: val.windows.map(Into::into).unwrap_or_default(), - ..Default::default() - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, Default)] -pub struct DebianSettings { - pub depends: Option>, - pub files: HashMap, - pub nsis: Option, -} - -impl From for tauri_bundler::DebianSettings { - fn from(val: DebianSettings) -> Self { - tauri_bundler::DebianSettings { - depends: val.depends, - files: val.files, - desktop_template: None, - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, Default)] -pub struct WixSettings { - pub language: Vec<(String, Option)>, - pub template: Option, - pub fragment_paths: Vec, - pub component_group_refs: Vec, - pub component_refs: Vec, - pub feature_group_refs: Vec, - pub feature_refs: Vec, - pub merge_refs: Vec, - pub skip_webview_install: bool, - pub license: Option, - pub enable_elevated_update_task: bool, - pub banner_path: Option, - pub dialog_image_path: Option, - pub fips_compliant: bool, -} - -impl From for tauri_bundler::WixSettings { - fn from(val: WixSettings) -> Self { - tauri_bundler::WixSettings { - language: tauri_bundler::bundle::WixLanguage({ - let mut languages: Vec<_> = val - .language - .iter() - .map(|l| { - ( - l.0.clone(), - tauri_bundler::bundle::WixLanguageConfig { - locale_path: l.1.clone(), - }, - ) - }) - .collect(); - if languages.is_empty() { - languages.push(("en-US".into(), Default::default())); - } - languages - }), - template: val.template, - fragment_paths: val.fragment_paths, - component_group_refs: val.component_group_refs, - component_refs: val.component_refs, - feature_group_refs: val.feature_group_refs, - feature_refs: val.feature_refs, - merge_refs: val.merge_refs, - skip_webview_install: val.skip_webview_install, - license: val.license, - enable_elevated_update_task: val.enable_elevated_update_task, - banner_path: val.banner_path, - dialog_image_path: val.dialog_image_path, - fips_compliant: val.fips_compliant, - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, Default)] -pub struct MacOsSettings { - pub frameworks: Option>, - pub minimum_system_version: Option, - pub license: Option, - pub exception_domain: Option, - pub signing_identity: Option, - pub provider_short_name: Option, - pub entitlements: Option, - pub info_plist_path: Option, -} - -impl From for tauri_bundler::MacOsSettings { - fn from(val: MacOsSettings) -> Self { - tauri_bundler::MacOsSettings { - frameworks: val.frameworks, - minimum_system_version: val.minimum_system_version, - license: val.license, - exception_domain: val.exception_domain, - signing_identity: val.signing_identity, - provider_short_name: val.provider_short_name, - entitlements: val.entitlements, - info_plist_path: val.info_plist_path, - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct WindowsSettings { - pub digest_algorithm: Option, - pub certificate_thumbprint: Option, - pub timestamp_url: Option, - pub tsp: bool, - pub wix: Option, - pub icon_path: Option, - pub webview_install_mode: WebviewInstallMode, - pub webview_fixed_runtime_path: Option, - pub allow_downgrades: bool, - pub nsis: Option, -} - -impl From for tauri_bundler::WindowsSettings { - fn from(val: WindowsSettings) -> Self { - tauri_bundler::WindowsSettings { - digest_algorithm: val.digest_algorithm, - certificate_thumbprint: val.certificate_thumbprint, - timestamp_url: val.timestamp_url, - tsp: val.tsp, - wix: val.wix.map(Into::into), - icon_path: val.icon_path.unwrap_or("icons/icon.ico".into()), - webview_install_mode: val.webview_install_mode.into(), - webview_fixed_runtime_path: val.webview_fixed_runtime_path, - allow_downgrades: val.allow_downgrades, - nsis: val.nsis.map(Into::into), - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct NsisSettings { - pub template: Option, - pub license: Option, - pub header_image: Option, - pub sidebar_image: Option, - pub installer_icon: Option, - pub install_mode: NSISInstallerMode, - pub languages: Option>, - pub custom_language_files: Option>, - pub display_language_selector: bool, -} - -impl From for tauri_bundler::NsisSettings { - fn from(val: NsisSettings) -> Self { - tauri_bundler::NsisSettings { - license: val.license, - header_image: val.header_image, - sidebar_image: val.sidebar_image, - installer_icon: val.installer_icon, - install_mode: val.install_mode.into(), - languages: val.languages, - display_language_selector: val.display_language_selector, - custom_language_files: None, - template: None, - compression: None, - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum NSISInstallerMode { - CurrentUser, - PerMachine, - Both, -} - -impl From for tauri_utils::config::NSISInstallerMode { - fn from(val: NSISInstallerMode) -> Self { - match val { - NSISInstallerMode::CurrentUser => tauri_utils::config::NSISInstallerMode::CurrentUser, - NSISInstallerMode::PerMachine => tauri_utils::config::NSISInstallerMode::PerMachine, - NSISInstallerMode::Both => tauri_utils::config::NSISInstallerMode::Both, - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum WebviewInstallMode { - Skip, - DownloadBootstrapper { silent: bool }, - EmbedBootstrapper { silent: bool }, - OfflineInstaller { silent: bool }, - FixedRuntime { path: PathBuf }, -} - -impl WebviewInstallMode { - fn into(self) -> tauri_utils::config::WebviewInstallMode { - match self { - Self::Skip => tauri_utils::config::WebviewInstallMode::Skip, - Self::DownloadBootstrapper { silent } => { - tauri_utils::config::WebviewInstallMode::DownloadBootstrapper { silent } - } - Self::EmbedBootstrapper { silent } => { - tauri_utils::config::WebviewInstallMode::EmbedBootstrapper { silent } - } - Self::OfflineInstaller { silent } => { - tauri_utils::config::WebviewInstallMode::OfflineInstaller { silent } - } - Self::FixedRuntime { path } => { - tauri_utils::config::WebviewInstallMode::FixedRuntime { path } - } - } - } -} - -impl Default for WebviewInstallMode { - fn default() -> Self { - Self::OfflineInstaller { silent: false } - } -} diff --git a/packages/cli/src/error.rs b/packages/cli/src/error.rs index d577b2b40..35eea7eab 100644 --- a/packages/cli/src/error.rs +++ b/packages/cli/src/error.rs @@ -72,6 +72,24 @@ impl From for Error { } } +impl From for Error { + fn from(e: dioxus_cli_config::LoadDioxusConfigError) -> Self { + Self::RuntimeError(e.to_string()) + } +} + +impl From for Error { + fn from(e: dioxus_cli_config::CargoError) -> Self { + Self::CargoError(e.to_string()) + } +} + +impl From for Error { + fn from(e: dioxus_cli_config::CrateConfigError) -> Self { + Self::RuntimeError(e.to_string()) + } +} + #[macro_export] macro_rules! custom_error { ($msg:literal $(,)?) => { diff --git a/packages/cli/src/lib.rs b/packages/cli/src/lib.rs index b27cf55a9..191c65008 100644 --- a/packages/cli/src/lib.rs +++ b/packages/cli/src/lib.rs @@ -4,21 +4,16 @@ pub const DIOXUS_CLI_VERSION: &str = "0.4.1"; +mod assets; pub mod builder; pub mod server; pub mod tools; pub use builder::*; -pub mod cargo; -pub use cargo::*; - pub mod cli; pub use cli::*; -pub mod config; -pub use config::*; - pub mod error; pub use error::*; diff --git a/packages/cli/src/main.rs b/packages/cli/src/main.rs index 0695d7ac8..54211a254 100644 --- a/packages/cli/src/main.rs +++ b/packages/cli/src/main.rs @@ -1,3 +1,4 @@ +use dioxus_cli_config::DioxusConfig; use std::path::PathBuf; use anyhow::anyhow; diff --git a/packages/cli/src/server/desktop/mod.rs b/packages/cli/src/server/desktop/mod.rs index 34f85a312..ad4b7bac8 100644 --- a/packages/cli/src/server/desktop/mod.rs +++ b/packages/cli/src/server/desktop/mod.rs @@ -5,8 +5,11 @@ use crate::{ output::{print_console_info, PrettierOptions}, setup_file_watcher, }, - BuildResult, CrateConfig, Result, + BuildResult, Result, }; +use dioxus_cli_config::CrateConfig; +use dioxus_cli_config::ExecutableType; + use dioxus_hot_reload::HotReloadMsg; use dioxus_html::HtmlCtx; use dioxus_rsx::hot_reload::*; @@ -215,9 +218,9 @@ fn start_desktop(config: &CrateConfig, skip_assets: bool) -> Result<(RAIIChild, let result = crate::builder::build_desktop(config, true, skip_assets)?; match &config.executable { - crate::ExecutableType::Binary(name) - | crate::ExecutableType::Lib(name) - | crate::ExecutableType::Example(name) => { + ExecutableType::Binary(name) + | ExecutableType::Lib(name) + | ExecutableType::Example(name) => { let mut file = config.out_dir.join(name); if cfg!(windows) { file.set_extension("exe"); diff --git a/packages/cli/src/server/fullstack/mod.rs b/packages/cli/src/server/fullstack/mod.rs index fa9c77459..60d685ced 100644 --- a/packages/cli/src/server/fullstack/mod.rs +++ b/packages/cli/src/server/fullstack/mod.rs @@ -1,6 +1,9 @@ +use dioxus_cli_config::CrateConfig; + use crate::{ + assets::WebAssetConfigDropGuard, cfg::{ConfigOptsBuild, ConfigOptsServe}, - CrateConfig, Result, WebAssetConfigDropGuard, + Result, }; use super::{desktop, Platform}; @@ -86,7 +89,7 @@ fn build_web(serve: ConfigOptsServe, target_directory: &std::path::Path) -> Resu } None => web_config.features = Some(vec![web_feature]), }; - web_config.platform = Some(crate::cfg::Platform::Web); + web_config.platform = Some(dioxus_cli_config::Platform::Web); let _gaurd = FullstackWebEnvGuard::new(&web_config); crate::cli::build::Build { build: web_config }.build(None, Some(target_directory)) diff --git a/packages/cli/src/server/mod.rs b/packages/cli/src/server/mod.rs index 895d9e51e..1fe5f2ea5 100644 --- a/packages/cli/src/server/mod.rs +++ b/packages/cli/src/server/mod.rs @@ -1,14 +1,12 @@ -use crate::{cfg::ConfigOptsServe, BuildResult, CrateConfig, Result}; +use crate::{cfg::ConfigOptsServe, BuildResult, Result}; +use dioxus_cli_config::CrateConfig; use cargo_metadata::diagnostic::Diagnostic; use dioxus_core::Template; use dioxus_html::HtmlCtx; use dioxus_rsx::hot_reload::*; use notify::{RecommendedWatcher, Watcher}; -use std::{ - path::PathBuf, - sync::{Arc, Mutex}, -}; +use std::sync::{Arc, Mutex}; use tokio::sync::broadcast::{self}; mod output; @@ -27,13 +25,7 @@ async fn setup_file_watcher Result + Send + 'static>( let mut last_update_time = chrono::Local::now().timestamp(); // file watcher: check file change - let allow_watch_path = config - .dioxus_config - .web - .watcher - .watch_path - .clone() - .unwrap_or_else(|| vec![PathBuf::from("src"), PathBuf::from("examples")]); + let allow_watch_path = config.dioxus_config.web.watcher.watch_path.clone(); let watcher_config = config.clone(); let mut watcher = notify::recommended_watcher(move |info: notify::Result| { diff --git a/packages/cli/src/server/output.rs b/packages/cli/src/server/output.rs index 4e148b0f1..a61dd1f59 100644 --- a/packages/cli/src/server/output.rs +++ b/packages/cli/src/server/output.rs @@ -1,6 +1,7 @@ use crate::server::Diagnostic; -use crate::CrateConfig; use colored::Colorize; +use dioxus_cli_config::crate_root; +use dioxus_cli_config::CrateConfig; use std::path::PathBuf; use std::process::Command; @@ -43,25 +44,19 @@ pub fn print_console_info( profile = config.custom_profile.as_ref().unwrap().to_string(); } let hot_reload = if config.hot_reload { "RSX" } else { "Normal" }; - let crate_root = crate::cargo::crate_root().unwrap(); + let crate_root = crate_root().unwrap(); let custom_html_file = if crate_root.join("index.html").is_file() { "Custom [index.html]" } else { "Default" }; - let url_rewrite = if config - .dioxus_config - .web - .watcher - .index_on_404 - .unwrap_or(false) - { + let url_rewrite = if config.dioxus_config.web.watcher.index_on_404 { "True" } else { "False" }; - let proxies = config.dioxus_config.web.proxy.as_ref(); + let proxies = &config.dioxus_config.web.proxy; if options.changed.is_empty() { println!( @@ -109,12 +104,10 @@ pub fn print_console_info( println!(); println!("\t> Profile : {}", profile.green()); println!("\t> Hot Reload : {}", hot_reload.cyan()); - if let Some(proxies) = proxies { - if !proxies.is_empty() { - println!("\t> Proxies :"); - for proxy in proxies { - println!("\t\t- {}", proxy.backend.blue()); - } + if !proxies.is_empty() { + println!("\t> Proxies :"); + for proxy in proxies { + println!("\t\t- {}", proxy.backend.blue()); } } println!("\t> Index Template : {}", custom_html_file.green()); diff --git a/packages/cli/src/server/web/mod.rs b/packages/cli/src/server/web/mod.rs index 896d08643..47661e3dd 100644 --- a/packages/cli/src/server/web/mod.rs +++ b/packages/cli/src/server/web/mod.rs @@ -5,7 +5,7 @@ use crate::{ output::{print_console_info, PrettierOptions, WebServerInfo}, setup_file_watcher, HotReloadState, }, - BuildResult, CrateConfig, Result, WebHttpsConfig, + BuildResult, Result, }; use axum::{ body::{Full, HttpBody}, @@ -20,6 +20,8 @@ use axum::{ Router, }; use axum_server::tls_rustls::RustlsConfig; +use dioxus_cli_config::CrateConfig; +use dioxus_cli_config::WebHttpsConfig; use dioxus_html::HtmlCtx; use dioxus_rsx::hot_reload::*; @@ -277,12 +279,7 @@ async fn setup_router( .override_response_header(HeaderName::from_static("cross-origin-opener-policy"), coop) .and_then( move |response: Response| async move { - let mut response = if file_service_config - .dioxus_config - .web - .watcher - .index_on_404 - .unwrap_or(false) + let mut response = if file_service_config.dioxus_config.web.watcher.index_on_404 && response.status() == StatusCode::NOT_FOUND { let body = Full::from( @@ -321,7 +318,7 @@ async fn setup_router( let mut router = Router::new().route("/_dioxus/ws", get(ws_handler)); // Setup proxy - for proxy_config in config.dioxus_config.web.proxy.unwrap_or_default() { + for proxy_config in config.dioxus_config.web.proxy { router = proxy::add_proxy(router, &proxy_config)?; } @@ -335,6 +332,18 @@ async fn setup_router( }, )); + router = if let Some(base_path) = config.dioxus_config.web.app.base_path.clone() { + let base_path = format!("/{}", base_path.trim_matches('/')); + Router::new() + .nest(&base_path, axum::routing::any_service(router)) + .fallback(get(move || { + let base_path = base_path.clone(); + async move { format!("Outside of the base path: {}", base_path) } + })) + } else { + router + }; + // Setup routes router = router .route("/_dioxus/hot_reload", get(hot_reload_handler)) @@ -438,13 +447,7 @@ fn build(config: &CrateConfig, reload_tx: &Sender<()>, skip_assets: bool) -> Res let result = builder::build(config, true, skip_assets)?; // change the websocket reload state to true; // the page will auto-reload. - if config - .dioxus_config - .web - .watcher - .reload_html - .unwrap_or(false) - { + if config.dioxus_config.web.watcher.reload_html { let _ = Serve::regen_dev_page(config, skip_assets); } let _ = reload_tx.send(()); diff --git a/packages/cli/src/server/web/proxy.rs b/packages/cli/src/server/web/proxy.rs index e35635b6b..4242a55eb 100644 --- a/packages/cli/src/server/web/proxy.rs +++ b/packages/cli/src/server/web/proxy.rs @@ -1,4 +1,5 @@ -use crate::{Result, WebProxyConfig}; +use crate::Result; +use dioxus_cli_config::WebProxyConfig; use anyhow::Context; use axum::{http::StatusCode, routing::any, Router}; diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index 9ecbcef47..d7c1bb403 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -19,6 +19,7 @@ dioxus-html = { workspace = true, features = [ ] } dioxus-interpreter-js = { workspace = true, features = ["binary-protocol"] } dioxus-hot-reload = { workspace = true, optional = true } +dioxus-cli-config = { workspace = true } serde = "1.0.136" serde_json = "1.0.79" diff --git a/packages/desktop/src/config.rs b/packages/desktop/src/config.rs index b7567030c..a69c8c6df 100644 --- a/packages/desktop/src/config.rs +++ b/packages/desktop/src/config.rs @@ -47,7 +47,12 @@ impl Config { /// Initializes a new `WindowBuilder` with default values. #[inline] pub fn new() -> Self { - let window = WindowBuilder::new().with_title("Dioxus app"); + let window = WindowBuilder::new().with_title( + dioxus_cli_config::CURRENT_CONFIG + .as_ref() + .map(|c| c.dioxus_config.application.name.clone()) + .unwrap_or("Dioxus App".to_string()), + ); Self { // event_handler: None, diff --git a/packages/router/Cargo.toml b/packages/router/Cargo.toml index 6307dacc9..0064b5300 100644 --- a/packages/router/Cargo.toml +++ b/packages/router/Cargo.toml @@ -27,6 +27,7 @@ gloo-utils = { version = "0.1.6", optional = true } dioxus-liveview = { workspace = true, optional = true } dioxus-ssr = { workspace = true, optional = true } tokio = { workspace = true, features = ["full"], optional = true } +dioxus-cli-config.workspace = true [features] default = ["web"] diff --git a/packages/router/src/history/web.rs b/packages/router/src/history/web.rs index 789eed76e..a0f677e24 100644 --- a/packages/router/src/history/web.rs +++ b/packages/router/src/history/web.rs @@ -13,6 +13,16 @@ use super::{ HistoryProvider, }; +#[allow(dead_code)] +fn base_path() -> Option<&'static str> { + let base_path = dioxus_cli_config::CURRENT_CONFIG + .as_ref() + .ok() + .and_then(|c| c.dioxus_config.web.app.base_path.as_deref()); + tracing::trace!("Using base_path from Dioxus.toml: {:?}", base_path); + base_path +} + #[cfg(not(feature = "serde"))] #[allow(clippy::extra_unused_type_parameters)] fn update_scroll(window: &Window, history: &History) { @@ -160,6 +170,10 @@ impl WebHistory { .expect("`history` can set scroll restoration"); } + let prefix = prefix + .or_else(|| base_path().map(|s| s.to_string())) + .map(|prefix| format!("/{}", prefix.trim_matches('/'))); + Self { do_scroll_restoration, history, @@ -198,6 +212,16 @@ where let location = self.window.location(); let path = location.pathname().unwrap_or_else(|_| "/".into()) + &location.search().unwrap_or("".into()); + let path = match self.prefix { + None => path, + Some(ref prefix) => { + if path.starts_with(prefix) { + path[prefix.len()..].to_string() + } else { + path + } + } + }; R::from_str(&path).unwrap_or_else(|err| panic!("{}", err)) }