use std::path::PathBuf;
use clap::ValueEnum;
use dialoguer::{Confirm, theme::ColorfulTheme};
use crate::shell::Shell;
use crate::{note, warn};
use waterui_cli::{
platform::TargetBackend as LibTargetBackend,
toolchain::{Host, sccache::Sccache},
utils::sccache_install_hint,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum TargetBackend {
Apple,
Android,
Gtk4,
Hydrolysis,
#[value(name = "winui")]
WinUi,
Dew,
}
impl TargetBackend {
pub const fn is_experimental(self) -> bool {
matches!(self, Self::Gtk4 | Self::WinUi)
}
pub const fn lib_backend(self) -> LibTargetBackend {
match self {
Self::Apple => LibTargetBackend::Apple,
Self::Android => LibTargetBackend::Android,
Self::Gtk4 => LibTargetBackend::Gtk4,
Self::Hydrolysis => LibTargetBackend::Hydrolysis,
Self::WinUi => LibTargetBackend::WinUi,
Self::Dew => LibTargetBackend::Dew,
}
}
}
fn sccache_allowed(host: &Host) -> bool {
if let Some(value) = host.env("WATERUI_DISABLE_SCCACHE") {
let value = value.to_string_lossy().trim().to_ascii_lowercase();
if matches!(value.as_str(), "1" | "true" | "yes" | "on") {
return false;
}
}
host.env("RUSTC_WRAPPER").is_none()
}
async fn detect_sccache_path(shell: &Shell, host: &Host) -> Option<PathBuf> {
if !sccache_allowed(host) {
note!(
shell,
"Skipping sccache (explicit wrapper or WATERUI_DISABLE_SCCACHE is set)"
);
return None;
}
let sccache = Sccache;
sccache.path(host).await.map_or_else(
|_| {
warn!(
shell,
"sccache not found. Build efficiency may be reduced. Install with: {}",
sccache_install_hint()
);
None
},
Some,
)
}
fn confirm_experimental_backend(
shell: &Shell,
backend_name: &str,
yes: bool,
) -> eyre::Result<bool> {
warn!(
shell,
"The {backend_name} backend is experimental and is not fully tested ahead of milestone releases"
);
if yes {
return Ok(true);
}
if !shell.is_interactive() {
eyre::bail!(
"the {backend_name} backend is experimental; pass --yes to confirm it in non-interactive environments"
);
}
let confirmed = Confirm::with_theme(&ColorfulTheme::default())
.with_prompt(format!(
"Continue with the experimental {backend_name} backend?"
))
.default(false)
.interact()?;
if !confirmed {
warn!(shell, "Cancelled");
return Ok(false);
}
Ok(true)
}
pub mod backend;
pub mod bench;
pub mod build;
pub mod channel;
pub mod clean;
pub mod completions;
pub mod create;
pub mod device;
pub mod devices;
pub mod doctor;
pub mod gc;
pub mod init;
pub mod inspector;
pub mod mcp;
pub mod package;
pub mod preview;
pub mod run;
pub mod web;
fn parse_viewport(s: &str) -> eyre::Result<(u32, u32)> {
let parts: Vec<&str> = s.split('x').collect();
if parts.len() != 2 {
eyre::bail!("Invalid viewport format: expected WIDTHxHEIGHT (e.g., 390x844)");
}
let width: u32 = parts[0]
.parse()
.map_err(|_| eyre::eyre!("Invalid viewport width"))?;
let height: u32 = parts[1]
.parse()
.map_err(|_| eyre::eyre!("Invalid viewport height"))?;
if width == 0 {
eyre::bail!("Invalid viewport width: must be positive");
}
if height == 0 {
eyre::bail!("Invalid viewport height: must be positive");
}
Ok((width, height))
}
#[cfg(test)]
mod tests {
use super::confirm_experimental_backend;
use crate::shell::Shell;
#[test]
fn experimental_backend_yes_flag_confirms_without_prompt() {
let shell = Shell::new(false);
assert!(!shell.is_interactive());
assert!(
confirm_experimental_backend(&shell, "GTK4", true).expect("--yes confirms the gate")
);
}
#[test]
fn experimental_backend_non_interactive_without_yes_is_an_error() {
let shell = Shell::new(false);
let err = confirm_experimental_backend(&shell, "GTK4", false)
.expect_err("non-interactive use without --yes must fail");
assert!(err.to_string().contains("--yes"));
}
}