Skip to main content

browser_control/cli/
start.rs

1//! `start` subcommand: launch a browser and register it.
2
3use 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, &registry)?;
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}