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 installed = detect::list_installed();
34 if installed.is_empty() {
35 anyhow::bail!("no supported browsers installed; run `browser-control list-installed`");
36 }
37
38 let resolved_kind: Kind = match browser.as_deref() {
39 None => first_chromium_or_first(&installed)
40 .ok_or_else(|| anyhow!("no chromium-based browser installed"))?,
41 Some(s) => Kind::parse(s).ok_or_else(|| {
42 anyhow!("unknown browser kind `{s}`; valid: chrome, edge, chromium, brave, firefox")
43 })?,
44 };
45
46 let installed_match = installed
47 .iter()
48 .find(|i| i.kind == resolved_kind)
49 .cloned()
50 .ok_or_else(|| {
51 anyhow!(
52 "browser `{}` is not installed on this machine",
53 resolved_kind.as_str()
54 )
55 })?;
56
57 let registry = Registry::open()?;
58 if let Some(row) = registry.first_alive_by_kind(resolved_kind)? {
59 if !no_wait {
60 crate::cli::wait::wait_until_ready(
61 &row.endpoint,
62 row.engine,
63 std::time::Duration::from_secs(wait_timeout),
64 )
65 .await?;
66 }
67 let res = to_result(&row, true);
68 emit(&res, json)?;
69 return Ok(());
70 }
71
72 let name = registry::naming::generate_default(resolved_kind, ®istry)?;
73 let profile_dir = paths::default_profile_dir(resolved_kind)?;
74 std::fs::create_dir_all(&profile_dir).context("creating profile directory")?;
75 let opts = LaunchOpts {
76 headless,
77 profile_dir: profile_dir.clone(),
78 };
79 let handle = launch::launch(&installed_match, opts)
80 .await
81 .with_context(|| format!("launching {}", installed_match.executable.display()))?;
82
83 let row = BrowserRow {
84 name: name.clone(),
85 kind: resolved_kind,
86 engine: handle.engine,
87 pid: handle.pid,
88 endpoint: handle.endpoint.clone(),
89 port: handle.port,
90 profile_dir: handle.profile_dir.clone(),
91 executable: installed_match.executable.clone(),
92 headless,
93 started_at: registry::now_iso8601(),
94 };
95 registry.insert(&row)?;
96 let _pid = handle.forget();
97
98 if !no_wait {
99 crate::cli::wait::wait_until_ready(
100 &row.endpoint,
101 row.engine,
102 std::time::Duration::from_secs(wait_timeout),
103 )
104 .await?;
105 }
106
107 let res = to_result(&row, false);
108 emit(&res, json)?;
109 Ok(())
110}
111
112fn first_chromium_or_first(installed: &[Installed]) -> Option<Kind> {
113 installed
114 .iter()
115 .find(|i| i.kind.is_chromium())
116 .map(|i| i.kind)
117 .or_else(|| installed.first().map(|i| i.kind))
118}
119
120fn to_result(row: &BrowserRow, reused: bool) -> StartResult {
121 StartResult {
122 name: row.name.clone(),
123 kind: row.kind,
124 pid: row.pid,
125 engine: row.engine,
126 endpoint: row.endpoint.clone(),
127 profile: row.profile_dir.clone(),
128 headless: row.headless,
129 started_at: row.started_at.clone(),
130 reused,
131 }
132}
133
134fn emit(res: &StartResult, json: bool) -> Result<()> {
135 if json {
136 print_json(&mut std::io::stdout(), res)?;
137 } else {
138 let reused = if res.reused { " (reused)" } else { "" };
139 println!("Started {}{}", res.name, reused);
140 println!(" kind: {}", res.kind.as_str());
141 println!(" pid: {}", res.pid);
142 println!(" engine: {:?}", res.engine);
143 println!(" endpoint: {}", res.endpoint);
144 println!(" profile: {}", res.profile.display());
145 }
146 Ok(())
147}