fabro/lib/crates/fabro-cli/src/command_context.rs
Bryan Helmkamp 99ce9a66c4 refactor(cli): route global args through settings layer
Resolve CLI settings once from user config plus process-local overrides
and pass the resolved view through command dispatch and CommandContext.

This keeps config-driven cli.output, cli.updates, and cli.logging
behavior working while preserving commands that only reject explicit
--json overrides. It also removes the implicit auto-approve coupling
from JSON run output.
2026-04-14 12:27:59 -04:00

161 lines
4.6 KiB
Rust

use std::path::{Path, PathBuf};
use std::sync::Arc;
use anyhow::{Context as _, Result, bail};
use fabro_config::merge::combine_files;
use fabro_types::settings::cli::CliLayer;
use fabro_types::settings::{CliSettings, SettingsLayer};
use fabro_util::printer::Printer;
use tokio::sync::OnceCell;
use crate::args::{ServerConnectionArgs, ServerTargetArgs};
use crate::server_client::ServerStoreClient;
use crate::{server_client, user_config};
#[derive(Clone, Debug)]
pub(crate) enum ServerMode {
None,
ByTarget {
target_override: Option<String>,
},
ByStorageDir {
target_override: Option<String>,
storage_dir_override: Option<PathBuf>,
},
}
pub(crate) struct CommandContext {
#[allow(dead_code)]
printer: Printer,
cwd: PathBuf,
base_config_path: PathBuf,
machine_settings: SettingsLayer,
cli_settings: CliSettings,
server_mode: ServerMode,
server: OnceCell<Arc<ServerStoreClient>>,
}
impl CommandContext {
pub(crate) fn base(
printer: Printer,
cli_settings: CliSettings,
cli_layer: &CliLayer,
) -> Result<Self> {
Self::new(printer, ServerMode::None, cli_settings, cli_layer)
}
pub(crate) fn for_target(
args: &ServerTargetArgs,
printer: Printer,
cli_settings: CliSettings,
cli_layer: &CliLayer,
) -> Result<Self> {
Self::new(
printer,
ServerMode::ByTarget {
target_override: args.server.clone(),
},
cli_settings,
cli_layer,
)
}
pub(crate) fn for_connection(
args: &ServerConnectionArgs,
printer: Printer,
cli_settings: CliSettings,
cli_layer: &CliLayer,
) -> Result<Self> {
Self::new(
printer,
ServerMode::ByStorageDir {
target_override: args.target.server.clone(),
storage_dir_override: args.storage_dir.clone_path(),
},
cli_settings,
cli_layer,
)
}
fn new(
printer: Printer,
server_mode: ServerMode,
cli_settings: CliSettings,
cli_layer: &CliLayer,
) -> Result<Self> {
let cwd = std::env::current_dir().context("Failed to get current directory")?;
let base_config_path = user_config::active_settings_path(None);
let disk_settings = match &server_mode {
ServerMode::None | ServerMode::ByTarget { .. } => user_config::load_settings()?,
ServerMode::ByStorageDir {
storage_dir_override,
..
} => user_config::load_settings_with_storage_dir(storage_dir_override.as_deref())?,
};
let machine_settings = combine_files(disk_settings, SettingsLayer {
cli: Some(cli_layer.clone()),
..SettingsLayer::default()
});
Ok(Self {
printer,
cwd,
base_config_path,
machine_settings,
cli_settings,
server_mode,
server: OnceCell::new(),
})
}
#[allow(dead_code)]
pub(crate) fn printer(&self) -> Printer {
self.printer
}
pub(crate) fn cwd(&self) -> &Path {
&self.cwd
}
pub(crate) fn base_config_path(&self) -> &Path {
&self.base_config_path
}
pub(crate) fn machine_settings(&self) -> &SettingsLayer {
&self.machine_settings
}
pub(crate) fn cli_settings(&self) -> &CliSettings {
&self.cli_settings
}
pub(crate) async fn server(&self) -> Result<Arc<ServerStoreClient>> {
let server_mode = self.server_mode.clone();
let base_config_path = self.base_config_path.clone();
let machine_settings = self.machine_settings.clone();
let client = self
.server
.get_or_try_init(|| async move {
let target = match server_mode {
ServerMode::None => bail!("This command context does not have server access"),
ServerMode::ByTarget { target_override }
| ServerMode::ByStorageDir {
target_override, ..
} => ServerTargetArgs {
server: target_override,
},
};
server_client::connect_server_with_settings(
&target,
&machine_settings,
&base_config_path,
)
.await
.map(Arc::new)
})
.await?;
Ok(Arc::clone(client))
}
}