diff --git a/Cargo.toml b/Cargo.toml index 9ba51bf5c..f9ad5ae22 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -227,8 +227,8 @@ thiserror = "2.0.12" prettyplease = { version = "0.2.30", features = ["verbatim"] } const_format = "0.2.34" cargo_toml = { version = "0.21.0" } -tauri-utils = { version = "2.2.0" } -tauri-bundler = { version = "2.2.4" } +tauri-utils = { version = "=2.4.0" } +tauri-bundler = { version = "=2.4.0" } lru = "0.13.0" async-trait = "0.1.87" axum = { version = "0.8.1", default-features = false } diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index e15ca20e3..17bdd2f60 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -390,6 +390,7 @@ pub(crate) struct BuildRequest { pub(crate) link_err_file: Arc, pub(crate) rustc_wrapper_args_file: Arc, pub(crate) base_path: Option, + pub(crate) using_dioxus_explicitly: bool, } /// dx can produce different "modes" of a build. A "regular" build is a "base" build. The Fat and Thin @@ -757,6 +758,7 @@ impl BuildRequest { package, main_target, rustflags, + using_dioxus_explicitly, skip_assets: args.skip_assets, base_path: args.base_path.clone(), wasm_split: args.wasm_split, @@ -1132,6 +1134,14 @@ impl BuildRequest { return Ok(()); } + // Run the tailwind build before bundling anything else + crate::TailwindCli::run_once( + self.package_manifest_dir(), + self.config.application.tailwind_input.clone(), + self.config.application.tailwind_output.clone(), + ) + .await?; + let asset_dir = self.asset_dir(); // First, clear the asset dir of any files that don't exist in the new manifest @@ -2147,6 +2157,18 @@ impl BuildRequest { }; cargo_args.push(self.executable_name().to_string()); + // Set offline/locked/frozen + let lock_opts = crate::VERBOSITY.get().cloned().unwrap_or_default(); + if lock_opts.frozen { + cargo_args.push("--frozen".to_string()); + } + if lock_opts.locked { + cargo_args.push("--locked".to_string()); + } + if lock_opts.offline { + cargo_args.push("--offline".to_string()); + } + // Merge in extra args. Order shouldn't really matter. cargo_args.extend(self.extra_cargo_args.clone()); cargo_args.push("--".to_string()); diff --git a/packages/cli/src/cli/create.rs b/packages/cli/src/cli/create.rs index 62175ec6b..2251e9a04 100644 --- a/packages/cli/src/cli/create.rs +++ b/packages/cli/src/cli/create.rs @@ -1,6 +1,6 @@ use super::*; use crate::TraceSrc; -use cargo_generate::{GenerateArgs, TemplatePath}; +use cargo_generate::{GenerateArgs, TemplatePath, Vcs}; use std::path::Path; pub(crate) static DEFAULT_TEMPLATE: &str = "gh:dioxuslabs/dioxus-template"; @@ -46,15 +46,25 @@ pub struct Create { /// Default values can be overridden with `--option` #[clap(short, long)] yes: bool, + + /// Specify the VCS used to initialize the generated template. + /// Options: `git`, `none`. + #[arg(long, value_parser)] + vcs: Option, } impl Create { - pub fn create(mut self) -> Result { + pub async fn create(mut self) -> Result { // Project name defaults to directory name. if self.name.is_none() { self.name = Some(create::name_from_path(&self.path)?); } + // Perform a connectivity check so we just don't it around doing nothing if there's a network error + if self.template.is_none() { + connectivity_check().await?; + } + // If no template is specified, use the default one and set the branch to the latest release. resolve_template_and_branch(&mut self.template, &mut self.branch); @@ -72,14 +82,13 @@ impl Create { init: true, name: self.name, silent: self.yes, - vcs: Some(cargo_generate::Vcs::Git), + vcs: self.vcs, template_path: TemplatePath { auto_path: self.template, branch: self.branch, revision: self.revision, subfolder: self.subtemplate, tag: self.tag, - ..Default::default() }, verbose: crate::logging::VERBOSITY @@ -93,7 +102,7 @@ impl Create { tracing::debug!(dx_src = ?TraceSrc::Dev, "Creating new project with args: {args:#?}"); let path = cargo_generate::generate(args)?; - _ = post_create(&path); + _ = post_create(&path, &self.vcs.unwrap_or(Vcs::Git)); Ok(StructuredOutput::Success) } @@ -145,7 +154,7 @@ pub(crate) fn name_from_path(path: &Path) -> Result { } /// Post-creation actions for newly setup crates. -pub(crate) fn post_create(path: &Path) -> Result<()> { +pub(crate) fn post_create(path: &Path, vcs: &Vcs) -> Result<()> { let parent_dir = path.parent(); let metadata = if parent_dir.is_none() { None @@ -168,6 +177,7 @@ pub(crate) fn post_create(path: &Path) -> Result<()> { // 1. Add the new project to the workspace, if it exists. // This must be executed first in order to run `cargo fmt` on the new project. + let is_workspace = metadata.is_some(); metadata.and_then(|metadata| { let cargo_toml_path = &metadata.workspace_root.join("Cargo.toml"); let cargo_toml_str = std::fs::read_to_string(cargo_toml_path).ok()?; @@ -223,6 +233,11 @@ pub(crate) fn post_create(path: &Path) -> Result<()> { let mut file = std::fs::File::create(readme_path)?; file.write_all(new_readme.as_bytes())?; + // 5. Run git init + if !is_workspace { + vcs.initialize(path, Some("main"), true)?; + } + tracing::info!(dx_src = ?TraceSrc::Dev, "Generated project at {}\n\n`cd` to your project and run `dx serve` to start developing.\nMore information is available in the generated `README.md`.\n\nBuild cool things! ✌️", path.display()); Ok(()) @@ -239,6 +254,43 @@ fn remove_triple_newlines(string: &str) -> String { new_string } +/// Perform a health check against github itself before we attempt to download any templates hosted +/// on github. +pub(crate) async fn connectivity_check() -> Result<()> { + if crate::VERBOSITY + .get() + .map(|f| f.offline) + .unwrap_or_default() + { + return Ok(()); + } + + use crate::styles::{GLOW_STYLE, LINK_STYLE}; + let client = reqwest::Client::new(); + for x in 0..=5 { + tokio::select! { + res = client.head("https://github.com/DioxusLabs/").header("User-Agent", "dioxus-cli").send() => { + if res.is_ok() { + return Ok(()); + } + tokio::time::sleep(std::time::Duration::from_millis(2000)).await; + }, + _ = tokio::time::sleep(std::time::Duration::from_millis(if x == 1 { 500 } else { 2000 })) => {} + } + if x == 0 { + println!("{GLOW_STYLE}warning{GLOW_STYLE:#}: Waiting for {LINK_STYLE}https://github.com/dioxuslabs{LINK_STYLE:#}...") + } else { + println!( + "{GLOW_STYLE}warning{GLOW_STYLE:#}: ({x}/5) Taking a while, maybe your internet is down?" + ); + } + } + + Err(Error::Network( + "Error connecting to template repository. Try cloning the template manually or add `dioxus` to a `cargo new` project.".to_string(), + )) +} + // todo: re-enable these tests with better parallelization // // #[cfg(test)] diff --git a/packages/cli/src/cli/init.rs b/packages/cli/src/cli/init.rs index c6a44eb44..ffcf7bf5c 100644 --- a/packages/cli/src/cli/init.rs +++ b/packages/cli/src/cli/init.rs @@ -1,5 +1,5 @@ use super::*; -use cargo_generate::{GenerateArgs, TemplatePath}; +use cargo_generate::{GenerateArgs, TemplatePath, Vcs}; #[derive(Clone, Debug, Default, Deserialize, Parser)] #[clap(name = "init")] @@ -43,15 +43,25 @@ pub struct Init { /// Default values can be overridden with `--option` #[clap(short, long)] yes: bool, + + /// Specify the VCS used to initialize the generated template. + /// Options: `git`, `none`. + #[arg(long, value_parser)] + vcs: Option, } impl Init { - pub fn init(mut self) -> Result { + pub async fn init(mut self) -> Result { // Project name defaults to directory name. if self.name.is_none() { self.name = Some(create::name_from_path(&self.path)?); } + // Perform a connectivity check so we just don't it around doing nothing if there's a network error + if self.template.is_none() { + create::connectivity_check().await?; + } + // If no template is specified, use the default one and set the branch to the latest release. create::resolve_template_and_branch(&mut self.template, &mut self.branch); @@ -64,7 +74,7 @@ impl Init { init: true, name: self.name, silent: self.yes, - vcs: Some(cargo_generate::Vcs::Git), + vcs: self.vcs, template_path: TemplatePath { auto_path: self.template, branch: self.branch, @@ -77,7 +87,7 @@ impl Init { }; create::restore_cursor_on_sigint(); let path = cargo_generate::generate(args)?; - _ = create::post_create(&path); + _ = create::post_create(&path, &self.vcs.unwrap_or(Vcs::Git)); Ok(StructuredOutput::Success) } } diff --git a/packages/cli/src/cli/mod.rs b/packages/cli/src/cli/mod.rs index edf6ed4d1..9a05342e0 100644 --- a/packages/cli/src/cli/mod.rs +++ b/packages/cli/src/cli/mod.rs @@ -70,10 +70,6 @@ pub(crate) enum Commands { #[clap(name = "run")] Run(run::RunArgs), - /// Build the assets for a specific target. - #[clap(name = "assets")] - BuildAssets(build_assets::BuildAssets), - /// Init a new project for Dioxus in the current directory (by default). /// Will attempt to keep your project in a good state. #[clap(name = "init")] @@ -103,6 +99,18 @@ pub(crate) enum Commands { /// Update the Dioxus CLI to the latest version. #[clap(name = "self-update")] SelfUpdate(update::SelfUpdate), + + /// Run a dioxus build tool. IE `build-assets`, etc + #[clap(name = "tools")] + #[clap(subcommand)] + Tools(BuildTools), +} + +#[derive(Subcommand)] +pub enum BuildTools { + /// Build the assets for a specific target. + #[clap(name = "assets")] + BuildAssets(build_assets::BuildAssets), } impl Display for Commands { @@ -119,8 +127,8 @@ impl Display for Commands { Commands::Check(_) => write!(f, "check"), Commands::Bundle(_) => write!(f, "bundle"), Commands::Run(_) => write!(f, "run"), - Commands::BuildAssets(_) => write!(f, "assets"), Commands::SelfUpdate(_) => write!(f, "self-update"), + Commands::Tools(_) => write!(f, "tools"), } } } @@ -156,8 +164,9 @@ pub mod styles { pub(crate) const INVALID: Style = AnsiColor::Yellow.on_default().effects(Effects::BOLD); // extra styles for styling logs - // we can style stuff using the ansi sequences like: "hotpatched in {GLOW_STYLE}{}{GLOW_STYLE:X}ms" + // we can style stuff using the ansi sequences like: "hotpatched in {GLOW_STYLE}{}{GLOW_STYLE:#}ms" pub(crate) const GLOW_STYLE: Style = AnsiColor::Yellow.on_default(); pub(crate) const NOTE_STYLE: Style = AnsiColor::Green.on_default(); pub(crate) const LINK_STYLE: Style = AnsiColor::Blue.on_default(); + pub(crate) const ERROR_STYLE: Style = AnsiColor::Red.on_default(); } diff --git a/packages/cli/src/cli/platform_override.rs b/packages/cli/src/cli/platform_override.rs index 525c79534..48017160b 100644 --- a/packages/cli/src/cli/platform_override.rs +++ b/packages/cli/src/cli/platform_override.rs @@ -86,12 +86,8 @@ where let mut subcommand = matches.subcommand(); while let Some((name, sub_matches)) = subcommand { match name { - "@client" => { - client = Some(sub_matches); - } - "@server" => { - server = Some(sub_matches); - } + "@client" => client = Some(sub_matches), + "@server" => server = Some(sub_matches), _ => {} } subcommand = sub_matches.subcommand(); @@ -117,7 +113,6 @@ where } } -/// Chain together multiple target commands #[derive(Debug, Subcommand, Clone)] #[command(subcommand_precedence_over_arg = true)] pub(crate) enum PlatformOverrides { diff --git a/packages/cli/src/cli/target.rs b/packages/cli/src/cli/target.rs index 3d33c2e62..806965ab0 100644 --- a/packages/cli/src/cli/target.rs +++ b/packages/cli/src/cli/target.rs @@ -2,48 +2,50 @@ use crate::cli::*; use crate::Platform; use target_lexicon::Triple; +const HELP_HEADING: &str = "Target Options"; + /// A single target to build for #[derive(Clone, Debug, Default, Deserialize, Parser)] pub(crate) struct TargetArgs { /// Build platform: support Web & Desktop [default: "default_platform"] - #[clap(long, value_enum)] + #[clap(long, value_enum, help_heading = HELP_HEADING)] pub(crate) platform: Option, /// Build in release mode [default: false] - #[clap(long, short)] + #[clap(long, short, help_heading = HELP_HEADING)] #[serde(default)] pub(crate) release: bool, /// The package to build - #[clap(short, long)] + #[clap(short, long, help_heading = HELP_HEADING)] pub(crate) package: Option, /// Build a specific binary [default: ""] - #[clap(long)] + #[clap(long, help_heading = HELP_HEADING)] pub(crate) bin: Option, /// Build a specific example [default: ""] - #[clap(long)] + #[clap(long, help_heading = HELP_HEADING)] pub(crate) example: Option, /// Build the app with custom a profile - #[clap(long)] + #[clap(long, help_heading = HELP_HEADING)] pub(crate) profile: Option, /// Space separated list of features to activate - #[clap(long)] + #[clap(long, help_heading = HELP_HEADING)] pub(crate) features: Vec, /// Don't include the default features in the build - #[clap(long)] + #[clap(long, help_heading = HELP_HEADING)] pub(crate) no_default_features: bool, /// Include all features in the build - #[clap(long)] + #[clap(long, help_heading = HELP_HEADING)] pub(crate) all_features: bool, /// Rustc platform triple - #[clap(long)] + #[clap(long, help_heading = HELP_HEADING)] pub(crate) target: Option, /// Extra arguments passed to `cargo` @@ -52,7 +54,7 @@ pub(crate) struct TargetArgs { /// /// This can include stuff like, "--locked", "--frozen", etc. Note that `dx` sets many of these /// args directly from other args in this command. - #[clap(long)] + #[clap(long, help_heading = HELP_HEADING)] pub(crate) cargo_args: Option, /// Extra arguments passed to `rustc`. This can be used to customize the linker, or other flags. @@ -62,36 +64,36 @@ pub(crate) struct TargetArgs { /// /// cargo rustc -- -Clink-arg=-Wl,-blah /// - #[clap(long)] + #[clap(long, help_heading = HELP_HEADING)] pub(crate) rustc_args: Option, /// Skip collecting assets from dependencies [default: false] - #[clap(long)] + #[clap(long, help_heading = HELP_HEADING)] #[serde(default)] pub(crate) skip_assets: bool, /// Inject scripts to load the wasm and js files for your dioxus app if they are not already present [default: true] - #[clap(long, default_value_t = true)] + #[clap(long, default_value_t = true, help_heading = HELP_HEADING)] pub(crate) inject_loading_scripts: bool, /// Experimental: Bundle split the wasm binary into multiple chunks based on `#[wasm_split]` annotations [default: false] - #[clap(long, default_value_t = false)] + #[clap(long, default_value_t = false, help_heading = HELP_HEADING)] pub(crate) wasm_split: bool, /// Generate debug symbols for the wasm binary [default: true] /// /// This will make the binary larger and take longer to compile, but will allow you to debug the /// wasm binary - #[clap(long, default_value_t = true)] + #[clap(long, default_value_t = true, help_heading = HELP_HEADING)] pub(crate) debug_symbols: bool, /// Are we building for a device or just the simulator. /// If device is false, then we'll build for the simulator - #[clap(long)] + #[clap(long, help_heading = HELP_HEADING)] pub(crate) device: Option, /// The base path the build will fetch assets relative to. This will override the /// base path set in the `dioxus` config. - #[clap(long)] + #[clap(long, help_heading = HELP_HEADING)] pub(crate) base_path: Option, } diff --git a/packages/cli/src/cli/verbosity.rs b/packages/cli/src/cli/verbosity.rs index 527096878..1cc4d65ee 100644 --- a/packages/cli/src/cli/verbosity.rs +++ b/packages/cli/src/cli/verbosity.rs @@ -1,6 +1,6 @@ use clap::Parser; -#[derive(Parser, Clone, Copy, Debug)] +#[derive(Parser, Clone, Copy, Debug, Default)] pub struct Verbosity { /// Use verbose output [default: false] #[clap(long, global = true)] @@ -13,4 +13,16 @@ pub struct Verbosity { /// Output logs in JSON format #[clap(long, global = true)] pub(crate) json_output: bool, + + /// Assert that `Cargo.lock` will remain unchanged + #[clap(long, global = true, help_heading = "Manifest Options")] + pub(crate) locked: bool, + + /// Run without accessing the network + #[clap(long, global = true, help_heading = "Manifest Options")] + pub(crate) offline: bool, + + /// Equivalent to specifying both --locked and --offline + #[clap(long, global = true, help_heading = "Manifest Options")] + pub(crate) frozen: bool, } diff --git a/packages/cli/src/error.rs b/packages/cli/src/error.rs index a1f57ab0b..f70330471 100644 --- a/packages/cli/src/error.rs +++ b/packages/cli/src/error.rs @@ -27,24 +27,27 @@ pub(crate) enum Error { #[error("Invalid proxy URL: {0}")] InvalidProxy(#[from] hyper::http::uri::InvalidUri), - #[error("Failed to establish proxy: {0}")] + #[error("Establishing proxy: {0}")] ProxySetup(String), - #[error("Failed to bundle project: {0}")] + #[error("Bundling project: {0}")] BundleFailed(#[from] tauri_bundler::Error), - #[error("Failed to perform hotpatch: {0}")] + #[error("Performing hotpatch: {0}")] PatchingFailed(#[from] crate::build::PatchError), - #[error("Failed to read object file: {0}")] + #[error("Reading object file: {0}")] ObjectReadFailed(#[from] object::Error), #[error("{0}")] CapturedPanic(String), - #[error("Failed to render template: {0}")] + #[error("Rendering template error: {0}")] TemplateParse(#[from] handlebars::RenderError), + #[error("Network connectivity error: {0}")] + Network(String), + #[error(transparent)] Other(#[from] anyhow::Error), } diff --git a/packages/cli/src/main.rs b/packages/cli/src/main.rs index 105a66fe1..4ba8f2669 100644 --- a/packages/cli/src/main.rs +++ b/packages/cli/src/main.rs @@ -51,8 +51,8 @@ async fn main() { let args = TraceController::initialize(); let result = match args.action { Commands::Translate(opts) => opts.translate(), - Commands::New(opts) => opts.create(), - Commands::Init(opts) => opts.init(), + Commands::New(opts) => opts.create().await, + Commands::Init(opts) => opts.init().await, Commands::Config(opts) => opts.config().await, Commands::Autoformat(opts) => opts.autoformat().await, Commands::Check(opts) => opts.check().await, @@ -61,8 +61,8 @@ async fn main() { Commands::Serve(opts) => opts.serve().await, Commands::Bundle(opts) => opts.bundle().await, Commands::Run(opts) => opts.run().await, - Commands::BuildAssets(opts) => opts.run().await, Commands::SelfUpdate(opts) => opts.self_update().await, + Commands::Tools(BuildTools::BuildAssets(opts)) => opts.run().await, }; // Provide a structured output for third party tools that can consume the output of the CLI @@ -71,7 +71,10 @@ async fn main() { tracing::debug!(json = ?output); } Err(err) => { - eprintln!("{err}"); + eprintln!( + "{ERROR_STYLE}failed:{ERROR_STYLE:#} {err}", + ERROR_STYLE = styles::ERROR_STYLE + ); tracing::error!( json = ?StructuredOutput::Error { diff --git a/packages/cli/src/serve/mod.rs b/packages/cli/src/serve/mod.rs index 31e594362..7755410ff 100644 --- a/packages/cli/src/serve/mod.rs +++ b/packages/cli/src/serve/mod.rs @@ -37,25 +37,32 @@ pub(crate) use update::*; /// - I want us to be able to detect a `server_fn` in the project and then upgrade from a static server /// to a dynamic one on the fly. pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) -> Result<()> { - // This is our default splash screen. We might want to make this a fancier splash screen in the future - // Also, these commands might not be the most important, but it's all we've got enabled right now - tracing::info!( - r#"----------------------------------------------------------------- - Serving your Dioxus app! 🚀 - • Press {GLOW_STYLE}`ctrl+c`{GLOW_STYLE:#} to exit the server - • Press {GLOW_STYLE}`r`{GLOW_STYLE:#} to rebuild the app - • Press {GLOW_STYLE}`p`{GLOW_STYLE:#} to toggle automatic rebuilds - • Press {GLOW_STYLE}`v`{GLOW_STYLE:#} to toggle verbose logging - • Press {GLOW_STYLE}`/`{GLOW_STYLE:#} for more commands and shortcuts - Learn more at {LINK_STYLE}https://dioxuslabs.com/learn/0.7/getting_started{LINK_STYLE:#} - ----------------------------------------------------------------"#, - ); - // Load the args into a plan, resolving all tooling, build dirs, arguments, decoding the multi-target, etc let mut builder = AppServer::start(args).await?; let mut devserver = WebServer::start(&builder)?; let mut screen = Output::start(builder.interactive).await?; + // This is our default splash screen. We might want to make this a fancier splash screen in the future + // Also, these commands might not be the most important, but it's all we've got enabled right now + tracing::info!( + r#"----------------------------------------------------------------- + Serving your app: {binname}! 🚀 + • Press {GLOW_STYLE}`ctrl+c`{GLOW_STYLE:#} to exit the server + • Press {GLOW_STYLE}`r`{GLOW_STYLE:#} to rebuild the app + • Press {GLOW_STYLE}`p`{GLOW_STYLE:#} to toggle automatic rebuilds + • Press {GLOW_STYLE}`v`{GLOW_STYLE:#} to toggle verbose logging + • Press {GLOW_STYLE}`/`{GLOW_STYLE:#} for more commands and shortcuts{extra} + ----------------------------------------------------------------"#, + binname = builder.client.build.executable_name(), + extra = if builder.client.build.using_dioxus_explicitly { + format!( + "\n Learn more at {LINK_STYLE}https://dioxuslabs.com/learn/0.7/getting_started{LINK_STYLE:#}" + ) + } else { + String::new() + } + ); + loop { // Draw the state of the server to the screen screen.render(&builder, &devserver); diff --git a/packages/cli/src/serve/output.rs b/packages/cli/src/serve/output.rs index 7835db922..f52002628 100644 --- a/packages/cli/src/serve/output.rs +++ b/packages/cli/src/serve/output.rs @@ -165,7 +165,7 @@ impl Output { } pub(crate) fn remote_shutdown(interactive: bool) -> io::Result<()> { - if interactive { + if interactive && crossterm::terminal::is_raw_mode_enabled().unwrap_or(true) { stdout() .execute(Show)? .execute(DisableFocusChange)? @@ -766,21 +766,23 @@ impl Output { let links_list: [_; 2] = Layout::vertical([Constraint::Length(1), Constraint::Length(1)]).areas(bottom); - frame.render_widget( - Paragraph::new(Line::from(vec![ - "Read the docs: ".gray(), - "https://dioxuslabs.com/0.6/docs".blue(), - ])), - links_list[0], - ); + if state.runner.client.build.using_dioxus_explicitly { + frame.render_widget( + Paragraph::new(Line::from(vec![ + "Read the docs: ".gray(), + "https://dioxuslabs.com/0.6/docs".blue(), + ])), + links_list[0], + ); - frame.render_widget( - Paragraph::new(Line::from(vec![ - "Video tutorials: ".gray(), - "https://youtube.com/@DioxusLabs".blue(), - ])), - links_list[1], - ); + frame.render_widget( + Paragraph::new(Line::from(vec![ + "Video tutorials: ".gray(), + "https://youtube.com/@DioxusLabs".blue(), + ])), + links_list[1], + ); + } let cmds = [ "", diff --git a/packages/cli/src/tailwind.rs b/packages/cli/src/tailwind.rs index 4cd111136..649815c43 100644 --- a/packages/cli/src/tailwind.rs +++ b/packages/cli/src/tailwind.rs @@ -19,6 +19,35 @@ impl TailwindCli { Self { version } } + pub(crate) async fn run_once( + manifest_dir: PathBuf, + input_path: Option, + output_path: Option, + ) -> Result<()> { + let Some(tailwind) = Self::autodetect(&manifest_dir) else { + return Ok(()); + }; + + if !tailwind.get_binary_path()?.exists() { + tracing::info!("Installing tailwindcss@{}", tailwind.version); + tailwind.install_github().await?; + } + + let output = tailwind + .run(&manifest_dir, input_path, output_path, false)? + .wait_with_output() + .await?; + + if !output.stderr.is_empty() { + tracing::warn!( + "Warnings while running tailwind: {}", + String::from_utf8_lossy(&output.stdout) + ); + } + + Ok(()) + } + pub(crate) fn serve( manifest_dir: PathBuf, input_path: Option, @@ -36,7 +65,7 @@ impl TailwindCli { // the tw watcher blocks on stdin, and `.wait()` will drop stdin // unfortunately the tw watcher just deadlocks in this case, so we take the stdin manually - let mut proc = tailwind.watch(&manifest_dir, input_path, output_path)?; + let mut proc = tailwind.run(&manifest_dir, input_path, output_path, true)?; let stdin = proc.stdin.take(); proc.wait().await?; drop(stdin); @@ -76,11 +105,12 @@ impl TailwindCli { Self::new(Self::V3_TAG.to_string()) } - pub(crate) fn watch( + pub(crate) fn run( &self, manifest_dir: &Path, input_path: Option, output_path: Option, + watch: bool, ) -> Result { let binary_path = self.get_binary_path()?; @@ -110,7 +140,7 @@ impl TailwindCli { .arg(input_path) .arg("--output") .arg(output_path) - .arg("--watch") + .args(watch.then_some("--watch")) .kill_on_drop(true) .stdin(Stdio::piped()) .stdout(Stdio::null()) diff --git a/packages/cli/src/workspace.rs b/packages/cli/src/workspace.rs index d72303b75..ba0900dce 100644 --- a/packages/cli/src/workspace.rs +++ b/packages/cli/src/workspace.rs @@ -1,13 +1,14 @@ +use crate::styles::GLOW_STYLE; use crate::CliSettings; use crate::Result; use crate::{config::DioxusConfig, AndroidTools}; use anyhow::Context; use ignore::gitignore::Gitignore; -use krates::{semver::Version, KrateDetails}; +use krates::{semver::Version, KrateDetails, LockOptions}; use krates::{Cmd, Krates, NodeId}; -use std::path::PathBuf; use std::sync::Arc; use std::{collections::HashSet, path::Path}; +use std::{path::PathBuf, time::Duration}; use target_lexicon::Triple; use tokio::process::Command; @@ -34,12 +35,50 @@ impl Workspace { return Ok(ws.clone()); } - let cmd = Cmd::new(); - let mut builder = krates::Builder::new(); - builder.workspace(true); - let krates = builder - .build(cmd, |_| {}) - .context("Failed to run cargo metadata")?; + let krates_future = tokio::task::spawn_blocking(|| { + let manifest_options = crate::logging::VERBOSITY.get().unwrap(); + let lock_options = LockOptions { + frozen: manifest_options.frozen, + locked: manifest_options.locked, + offline: manifest_options.offline, + }; + + let mut cmd = Cmd::new(); + cmd.lock_opts(lock_options); + + let mut builder = krates::Builder::new(); + builder.workspace(true); + let res = builder + .build(cmd, |_| {}) + .context("Failed to run cargo metadata"); + + if !lock_options.offline { + if let Ok(res) = std::env::var("SIMULATE_SLOW_NETWORK") { + std::thread::sleep(Duration::from_secs(res.parse().unwrap_or(5))); + } + } + + res + }); + + let spin_future = async move { + tokio::time::sleep(Duration::from_millis(500)).await; + println!("{GLOW_STYLE}warning{GLOW_STYLE:#}: Waiting for cargo-metadata..."); + tokio::time::sleep(Duration::from_millis(2000)).await; + for x in 1..=5 { + tokio::time::sleep(Duration::from_millis(2000)).await; + println!( + "{GLOW_STYLE}warning{GLOW_STYLE:#}: ({x}/5) Taking a while, maybe your internet is down?" + ); + } + }; + + let krates = tokio::select! { + f = krates_future => f.context("failed to run cargo metadata")??, + _ = spin_future => return Err(crate::Error::Network( + "cargo metadata took too long to respond, try again with --offline".to_string(), + )), + }; let settings = CliSettings::global_or_default(); let sysroot = Command::new("rustc")