Skip to main content

sharepoint_cli/commands/
init.rs

1//! `sharepoint init` — interactive first-run setup.
2//!
3//! Prompts for tenant + (optional) default site, writes the config file,
4//! then runs the same device-code login as `sharepoint auth login`. Only
5//! device-code authentication is supported; client-credential flows are
6//! not yet implemented.
7
8use std::io::{BufRead, Write};
9
10use crate::cli::{AuthCmd, Runtime};
11use crate::commands::auth;
12use crate::config;
13use crate::error::{CliError, Result};
14
15pub async fn run(rt: &Runtime) -> Result<()> {
16    if rt.out.quiet {
17        return Err(CliError::Input(
18            "init is interactive and cannot run with --quiet".into(),
19        ));
20    }
21    // `read_only` does not gate init: init bootstraps the config file from
22    // scratch, and would have nothing to protect if the file does not exist.
23    let stdin = std::io::stdin();
24    let mut lines = stdin.lock().lines();
25
26    let tenant = prompt(
27        &mut lines,
28        "Tenant (domain or GUID, e.g. contoso.onmicrosoft.com): ",
29    )?;
30    if tenant.is_empty() {
31        return Err(CliError::Input("tenant is required".into()));
32    }
33    let default_site = prompt(
34        &mut lines,
35        "Default site name or URL (optional, press enter to skip): ",
36    )?;
37
38    let profile_name = rt.cfg.profile_name.clone();
39    let mut file = rt.config_file.clone();
40    let entry = file.profile.entry(profile_name.clone()).or_default();
41    entry.tenant_id = Some(tenant.clone());
42    if !default_site.is_empty() {
43        entry.default_site = Some(default_site.clone());
44    }
45    config::save_file(&rt.config_path, &file)?;
46    rt.out
47        .print_message(&format!("Wrote {}", rt.config_path.display()));
48
49    // Re-build runtime so the new config is loaded for the auth-login call.
50    let mut updated = rt.cfg.clone();
51    updated.tenant_id = Some(tenant);
52    updated.default_site = if default_site.is_empty() {
53        file.profile
54            .get(&profile_name)
55            .and_then(|p| p.default_site.clone())
56    } else {
57        Some(default_site)
58    };
59    let new_rt = Runtime {
60        out: rt.out,
61        cfg: updated,
62        config_file: file,
63        config_path: rt.config_path.clone(),
64        cache_path: rt.cache_path.clone(),
65    };
66
67    auth::run(&new_rt, AuthCmd::Login).await
68}
69
70fn prompt(lines: &mut std::io::Lines<std::io::StdinLock<'_>>, label: &str) -> Result<String> {
71    eprint!("{label}");
72    std::io::stderr().flush().ok();
73    match lines.next() {
74        Some(Ok(line)) => Ok(line.trim().to_string()),
75        Some(Err(e)) => Err(CliError::Other(format!("read stdin: {e}"))),
76        None => Ok(String::new()),
77    }
78}