browser_control/cli/
start.rs1use anyhow::{anyhow, Context, Result};
4use serde::Serialize;
5use std::path::PathBuf;
6
7use crate::cli::output::print_json;
8use crate::detect::{self, Engine, Installed, Kind};
9use crate::launch::{self, LaunchOpts};
10use crate::paths;
11use crate::registry::{self, BrowserRow, Registry};
12
13#[derive(Debug, Serialize)]
14pub struct StartResult {
15 pub name: String,
16 pub kind: Kind,
17 pub pid: u32,
18 pub engine: Engine,
19 pub endpoint: String,
20 pub profile: PathBuf,
21 pub headless: bool,
22 pub started_at: String,
23 pub reused: bool,
24}
25
26pub async fn run(
27 browser: Option<String>,
28 headless: bool,
29 no_wait: bool,
30 wait_timeout: u64,
31 json: bool,
32) -> Result<()> {
33 let res = ensure_started(browser, headless, no_wait, wait_timeout).await?;
34 emit(&res, json)?;
35 Ok(())
36}
37
38pub async fn ensure_started(
44 browser: Option<String>,
45 headless: bool,
46 no_wait: bool,
47 wait_timeout: u64,
48) -> Result<StartResult> {
49 let installed = detect::list_installed();
50 if installed.is_empty() {
51 anyhow::bail!("no supported browsers installed; run `browser-control list-installed`");
52 }
53
54 let resolved_kind: Kind = match browser.as_deref() {
55 None => first_chromium_or_first(&installed)
56 .ok_or_else(|| anyhow!("no chromium-based browser installed"))?,
57 Some(s) => Kind::parse(s).ok_or_else(|| {
58 anyhow!("unknown browser kind `{s}`; valid: chrome, edge, chromium, brave, firefox")
59 })?,
60 };
61
62 let installed_match = installed
63 .iter()
64 .find(|i| i.kind == resolved_kind)
65 .cloned()
66 .ok_or_else(|| {
67 anyhow!(
68 "browser `{}` is not installed on this machine",
69 resolved_kind.as_str()
70 )
71 })?;
72
73 let registry = Registry::open()?;
74 if let Some(row) = registry.first_alive_by_kind(resolved_kind)? {
75 if !no_wait {
76 crate::cli::wait::wait_until_ready(
77 &row.endpoint,
78 row.engine,
79 std::time::Duration::from_secs(wait_timeout),
80 )
81 .await?;
82 }
83 return Ok(to_result(&row, true));
84 }
85
86 let name = registry::naming::generate_default(resolved_kind, ®istry)?;
87 let profile_dir = paths::default_profile_dir(resolved_kind)?;
88 std::fs::create_dir_all(&profile_dir).context("creating profile directory")?;
89 let opts = LaunchOpts {
90 headless,
91 profile_dir: profile_dir.clone(),
92 };
93 let handle = launch::launch(&installed_match, opts)
94 .await
95 .with_context(|| format!("launching {}", installed_match.executable.display()))?;
96
97 let row = BrowserRow {
98 name: name.clone(),
99 kind: resolved_kind,
100 engine: handle.engine,
101 pid: handle.pid,
102 endpoint: handle.endpoint.clone(),
103 port: handle.port,
104 profile_dir: handle.profile_dir.clone(),
105 executable: installed_match.executable.clone(),
106 headless,
107 started_at: registry::now_iso8601(),
108 };
109 registry.insert(&row)?;
110 let _pid = handle.forget();
111
112 if !no_wait {
113 crate::cli::wait::wait_until_ready(
114 &row.endpoint,
115 row.engine,
116 std::time::Duration::from_secs(wait_timeout),
117 )
118 .await?;
119 }
120
121 Ok(to_result(&row, false))
122}
123
124pub(crate) fn first_chromium_or_first(installed: &[Installed]) -> Option<Kind> {
125 installed
126 .iter()
127 .find(|i| i.kind.is_chromium())
128 .map(|i| i.kind)
129 .or_else(|| installed.first().map(|i| i.kind))
130}
131
132fn to_result(row: &BrowserRow, reused: bool) -> StartResult {
133 StartResult {
134 name: row.name.clone(),
135 kind: row.kind,
136 pid: row.pid,
137 engine: row.engine,
138 endpoint: row.endpoint.clone(),
139 profile: row.profile_dir.clone(),
140 headless: row.headless,
141 started_at: row.started_at.clone(),
142 reused,
143 }
144}
145
146fn emit(res: &StartResult, json: bool) -> Result<()> {
147 if json {
148 print_json(&mut std::io::stdout(), res)?;
149 } else {
150 let reused = if res.reused { " (reused)" } else { "" };
151 println!("Started {}{}", res.name, reused);
152 println!(" kind: {}", res.kind.as_str());
153 println!(" pid: {}", res.pid);
154 println!(" engine: {:?}", res.engine);
155 println!(" endpoint: {}", res.endpoint);
156 println!(" profile: {}", res.profile.display());
157 }
158 Ok(())
159}