use std::net::SocketAddr;
use std::path::PathBuf;
use clap::{Args as ClapArgs, ValueEnum};
use color_eyre::eyre::Result;
use crate::shell::Shell;
use crate::{header, note, success};
use waterui_cli::inspector::{InspectorLaunchOptions, InspectorPlatform, launch_inspector_session};
#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum CliInspectorPlatform {
Ios,
Macos,
Android,
}
impl From<CliInspectorPlatform> for InspectorPlatform {
fn from(platform: CliInspectorPlatform) -> Self {
match platform {
CliInspectorPlatform::Ios => Self::IosSimulator,
CliInspectorPlatform::Macos => Self::Macos,
CliInspectorPlatform::Android => Self::Android,
}
}
}
#[derive(ClapArgs, Debug)]
pub struct Args {
#[arg(long)]
target: Option<String>,
#[arg(long)]
token: Option<String>,
#[arg(short, long, value_enum, default_value = "macos")]
platform: CliInspectorPlatform,
#[arg(long, default_value = ".")]
path: PathBuf,
}
pub async fn run(shell: &Shell, args: Args) -> Result<()> {
let project_path = crate::project_path::canonicalize(&args.path)?;
header!(shell, "Inspector");
let (target, token) = if let Some(target) = args.target {
let addr = target.parse::<SocketAddr>()?;
let token = match args.token {
Some(token) => token,
None => token_for(addr)?,
};
(addr, token)
} else {
let found = discover(shell)?;
note!(shell, "Attaching to {} (pid {})", found.app_name, found.pid);
(found.addr, args.token.unwrap_or(found.token))
};
note!(shell, "Target runtime endpoint: {}", target);
note!(shell, "Session token: {}", token);
let mut session = launch_inspector_session(
&project_path,
args.platform.into(),
InspectorLaunchOptions {
target_addr: target.to_string(),
token: token.clone(),
},
)
.await?;
session.detach();
success!(shell, "Inspector app launched");
note!(
shell,
"Ensure the target app runs with WATERUI_INSPECTOR_TOKEN={token} and matching inspector endpoint."
);
Ok(())
}
fn discover(shell: &Shell) -> Result<waterui_inspector_protocol::discovery::Advertisement> {
use color_eyre::eyre::eyre;
let mut found = waterui_inspector_protocol::discovery::list()
.map_err(|error| eyre!("could not read advertised inspector endpoints: {error}"))?;
match found.len() {
0 => Err(eyre!(
"no running WaterUI debug build was found.\n\
Start one, or pass --target host:port for an application on another \
machine or device."
)),
1 => Ok(found.remove(0)),
_ => {
for advertisement in &found {
note!(
shell,
" {} (pid {}) at {}",
advertisement.app_name,
advertisement.pid,
advertisement.addr
);
}
Err(eyre!(
"several debug builds are running; pass --target host:port to choose one"
))
}
}
}
fn token_for(addr: SocketAddr) -> Result<String> {
use color_eyre::eyre::eyre;
let found = waterui_inspector_protocol::discovery::list()
.map_err(|error| eyre!("could not read advertised inspector endpoints: {error}"))?;
found
.into_iter()
.find(|advertisement| advertisement.addr.port() == addr.port())
.map(|advertisement| advertisement.token)
.map_or_else(
|| {
Err(eyre!(
"no application on this machine advertises {addr}.\n\
Pass --token with the token it printed at startup, which is how \
to attach to an application on another machine or device."
))
},
Ok,
)
}