mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-08-28 05:27:41 +00:00
refactor(cli): route command output through Printer
Plumb the CLI printer through command dispatch, replace direct stdout and stderr writes with printer helpers, preserve important stdout in quiet mode, and update the Claude Rust formatting hook to use cargo +nightly fmt.
This commit is contained in:
parent
6a87f0a071
commit
4b30a5f16e
69 changed files with 1057 additions and 455 deletions
|
|
@ -6,7 +6,7 @@
|
||||||
"hooks": [
|
"hooks": [
|
||||||
{
|
{
|
||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "FILE=$(jq -r '.tool_input.file_path') && case \"$FILE\" in *.rs) cargo fmt -- \"$FILE\" ;; esac"
|
"command": "FILE=$(jq -r '.tool_input.file_path') && case \"$FILE\" in *.rs) cargo +nightly fmt -- \"$FILE\" ;; esac"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::{Context as _, Result, bail};
|
use anyhow::{Context as _, Result, bail};
|
||||||
use fabro_types::settings::{CliSettings, SettingsLayer};
|
use fabro_types::settings::{CliSettings, SettingsLayer};
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use tokio::sync::OnceCell;
|
use tokio::sync::OnceCell;
|
||||||
|
|
||||||
use crate::args::{ServerConnectionArgs, ServerTargetArgs};
|
use crate::args::{ServerConnectionArgs, ServerTargetArgs};
|
||||||
|
|
@ -22,6 +23,8 @@ pub(crate) enum ServerMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct CommandContext {
|
pub(crate) struct CommandContext {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
printer: Printer,
|
||||||
cwd: PathBuf,
|
cwd: PathBuf,
|
||||||
base_config_path: PathBuf,
|
base_config_path: PathBuf,
|
||||||
machine_settings: SettingsLayer,
|
machine_settings: SettingsLayer,
|
||||||
|
|
@ -31,24 +34,24 @@ pub(crate) struct CommandContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CommandContext {
|
impl CommandContext {
|
||||||
pub(crate) fn base() -> Result<Self> {
|
pub(crate) fn base(printer: Printer) -> Result<Self> {
|
||||||
Self::new(ServerMode::None)
|
Self::new(printer, ServerMode::None)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn for_target(args: &ServerTargetArgs) -> Result<Self> {
|
pub(crate) fn for_target(args: &ServerTargetArgs, printer: Printer) -> Result<Self> {
|
||||||
Self::new(ServerMode::ByTarget {
|
Self::new(printer, ServerMode::ByTarget {
|
||||||
target_override: args.server.clone(),
|
target_override: args.server.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn for_connection(args: &ServerConnectionArgs) -> Result<Self> {
|
pub(crate) fn for_connection(args: &ServerConnectionArgs, printer: Printer) -> Result<Self> {
|
||||||
Self::new(ServerMode::ByStorageDir {
|
Self::new(printer, ServerMode::ByStorageDir {
|
||||||
target_override: args.target.server.clone(),
|
target_override: args.target.server.clone(),
|
||||||
storage_dir_override: args.storage_dir.clone_path(),
|
storage_dir_override: args.storage_dir.clone_path(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new(server_mode: ServerMode) -> Result<Self> {
|
fn new(printer: Printer, server_mode: ServerMode) -> Result<Self> {
|
||||||
let cwd = std::env::current_dir().context("Failed to get current directory")?;
|
let cwd = std::env::current_dir().context("Failed to get current directory")?;
|
||||||
let base_config_path = user_config::active_settings_path(None);
|
let base_config_path = user_config::active_settings_path(None);
|
||||||
let machine_settings = match &server_mode {
|
let machine_settings = match &server_mode {
|
||||||
|
|
@ -61,6 +64,7 @@ impl CommandContext {
|
||||||
let cli_settings = user_config::resolve_cli_settings(&machine_settings)?;
|
let cli_settings = user_config::resolve_cli_settings(&machine_settings)?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
printer,
|
||||||
cwd,
|
cwd,
|
||||||
base_config_path,
|
base_config_path,
|
||||||
machine_settings,
|
machine_settings,
|
||||||
|
|
@ -70,6 +74,11 @@ impl CommandContext {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn printer(&self) -> Printer {
|
||||||
|
self.printer
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn cwd(&self) -> &Path {
|
pub(crate) fn cwd(&self) -> &Path {
|
||||||
&self.cwd
|
&self.cwd
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,24 @@
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use anyhow::{Context, Result, bail};
|
use anyhow::{Context, Result, bail};
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
|
|
||||||
use crate::args::{ArtifactCpArgs, GlobalArgs};
|
use crate::args::{ArtifactCpArgs, GlobalArgs};
|
||||||
use crate::server_client::ServerStoreClient;
|
use crate::server_client::ServerStoreClient;
|
||||||
use crate::shared::{print_json_pretty, split_run_path};
|
use crate::shared::{print_json_pretty, split_run_path};
|
||||||
|
|
||||||
pub(super) async fn cp_command(args: &ArtifactCpArgs, globals: &GlobalArgs) -> Result<()> {
|
pub(super) async fn cp_command(
|
||||||
|
args: &ArtifactCpArgs,
|
||||||
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
|
) -> Result<()> {
|
||||||
let (run_id_selector, asset_path) = parse_source(&args.source);
|
let (run_id_selector, asset_path) = parse_source(&args.source);
|
||||||
let (run_id, client, entries) = super::resolve_artifacts(
|
let (run_id, client, entries) = super::resolve_artifacts(
|
||||||
&args.server,
|
&args.server,
|
||||||
run_id_selector,
|
run_id_selector,
|
||||||
args.node.as_deref(),
|
args.node.as_deref(),
|
||||||
args.retry,
|
args.retry,
|
||||||
|
printer,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
|
@ -57,7 +63,12 @@ pub(super) async fn cp_command(args: &ArtifactCpArgs, globals: &GlobalArgs) -> R
|
||||||
}],
|
}],
|
||||||
}))?;
|
}))?;
|
||||||
} else {
|
} else {
|
||||||
println!("Copied {} to {}", entry.relative_path, dest_file.display());
|
fabro_util::printout!(
|
||||||
|
printer,
|
||||||
|
"Copied {} to {}",
|
||||||
|
entry.relative_path,
|
||||||
|
dest_file.display()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
@ -108,7 +119,8 @@ pub(super) async fn cp_command(args: &ArtifactCpArgs, globals: &GlobalArgs) -> R
|
||||||
if globals.json {
|
if globals.json {
|
||||||
print_json_pretty(&serde_json::json!({ "copied": copied }))?;
|
print_json_pretty(&serde_json::json!({ "copied": copied }))?;
|
||||||
} else {
|
} else {
|
||||||
println!(
|
fabro_util::printout!(
|
||||||
|
printer,
|
||||||
"Copied {} artifact(s) to {}",
|
"Copied {} artifact(s) to {}",
|
||||||
entries.len(),
|
entries.len(),
|
||||||
args.dest.display()
|
args.dest.display()
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,29 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
|
|
||||||
use crate::args::{ArtifactListArgs, GlobalArgs};
|
use crate::args::{ArtifactListArgs, GlobalArgs};
|
||||||
|
|
||||||
pub(super) async fn list_command(args: &ArtifactListArgs, globals: &GlobalArgs) -> Result<()> {
|
pub(super) async fn list_command(
|
||||||
let (_run_id, _client, entries) =
|
args: &ArtifactListArgs,
|
||||||
super::resolve_artifacts(&args.server, &args.run_id, args.node.as_deref(), args.retry)
|
globals: &GlobalArgs,
|
||||||
.await?;
|
printer: Printer,
|
||||||
|
) -> Result<()> {
|
||||||
|
let (_run_id, _client, entries) = super::resolve_artifacts(
|
||||||
|
&args.server,
|
||||||
|
&args.run_id,
|
||||||
|
args.node.as_deref(),
|
||||||
|
args.retry,
|
||||||
|
printer,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
if globals.json {
|
if globals.json {
|
||||||
println!("{}", serde_json::to_string_pretty(&entries)?);
|
fabro_util::printout!(printer, "{}", serde_json::to_string_pretty(&entries)?);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
if entries.is_empty() {
|
if entries.is_empty() {
|
||||||
println!("No artifacts found for this run.");
|
fabro_util::printout!(printer, "No artifacts found for this run.");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -30,15 +40,23 @@ pub(super) async fn list_command(args: &ArtifactListArgs, globals: &GlobalArgs)
|
||||||
.unwrap_or(5)
|
.unwrap_or(5)
|
||||||
.max(5);
|
.max(5);
|
||||||
|
|
||||||
println!("{:<node_width$} {:>retry_width$} PATH", "NODE", "RETRY");
|
fabro_util::printout!(
|
||||||
|
printer,
|
||||||
|
"{:<node_width$} {:>retry_width$} PATH",
|
||||||
|
"NODE",
|
||||||
|
"RETRY"
|
||||||
|
);
|
||||||
for entry in &entries {
|
for entry in &entries {
|
||||||
println!(
|
fabro_util::printout!(
|
||||||
|
printer,
|
||||||
"{:<node_width$} {:>retry_width$} {}",
|
"{:<node_width$} {:>retry_width$} {}",
|
||||||
entry.node_slug, entry.retry, entry.relative_path
|
entry.node_slug,
|
||||||
|
entry.retry,
|
||||||
|
entry.relative_path
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
println!();
|
fabro_util::printout!(printer, "");
|
||||||
println!("{} artifact(s)", entries.len());
|
fabro_util::printout!(printer, "{} artifact(s)", entries.len());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ mod list;
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use fabro_types::{RunId, StageId};
|
use fabro_types::{RunId, StageId};
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
|
|
||||||
use crate::args::{ArtifactCommand, ArtifactNamespace, GlobalArgs, ServerTargetArgs};
|
use crate::args::{ArtifactCommand, ArtifactNamespace, GlobalArgs, ServerTargetArgs};
|
||||||
use crate::command_context::CommandContext;
|
use crate::command_context::CommandContext;
|
||||||
|
|
@ -24,8 +25,9 @@ pub(super) async fn resolve_artifacts(
|
||||||
run_selector: &str,
|
run_selector: &str,
|
||||||
node: Option<&str>,
|
node: Option<&str>,
|
||||||
retry: Option<u32>,
|
retry: Option<u32>,
|
||||||
|
printer: Printer,
|
||||||
) -> Result<(RunId, ServerStoreClient, Vec<ArtifactEntry>)> {
|
) -> Result<(RunId, ServerStoreClient, Vec<ArtifactEntry>)> {
|
||||||
let ctx = CommandContext::for_target(server)?;
|
let ctx = CommandContext::for_target(server, printer)?;
|
||||||
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
||||||
let run = lookup.resolve(run_selector)?;
|
let run = lookup.resolve(run_selector)?;
|
||||||
let run_id = run.run_id();
|
let run_id = run.run_id();
|
||||||
|
|
@ -60,9 +62,13 @@ pub(super) async fn resolve_artifacts(
|
||||||
Ok((run_id, client, entries))
|
Ok((run_id, client, entries))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn dispatch(ns: ArtifactNamespace, globals: &GlobalArgs) -> Result<()> {
|
pub(crate) async fn dispatch(
|
||||||
|
ns: ArtifactNamespace,
|
||||||
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
|
) -> Result<()> {
|
||||||
match ns.command {
|
match ns.command {
|
||||||
ArtifactCommand::List(args) => list::list_command(&args, globals).await,
|
ArtifactCommand::List(args) => list::list_command(&args, globals, printer).await,
|
||||||
ArtifactCommand::Cp(args) => cp::cp_command(&args, globals).await,
|
ArtifactCommand::Cp(args) => cp::cp_command(&args, globals, printer).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ use std::path::Path;
|
||||||
use fabro_config::effective_settings::{EffectiveSettingsLayers, EffectiveSettingsMode};
|
use fabro_config::effective_settings::{EffectiveSettingsLayers, EffectiveSettingsMode};
|
||||||
use fabro_config::{effective_settings, load_settings_project, project};
|
use fabro_config::{effective_settings, load_settings_project, project};
|
||||||
use fabro_types::settings::SettingsLayer;
|
use fabro_types::settings::SettingsLayer;
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
|
|
||||||
use crate::args::{GlobalArgs, SettingsArgs};
|
use crate::args::{GlobalArgs, SettingsArgs};
|
||||||
use crate::command_context::CommandContext;
|
use crate::command_context::CommandContext;
|
||||||
|
|
@ -56,8 +57,8 @@ fn workflow_and_project_layers(
|
||||||
Ok((workflow_layer, project_layer))
|
Ok((workflow_layer, project_layer))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn merged_config(args: &SettingsArgs) -> anyhow::Result<SettingsLayer> {
|
async fn merged_config(args: &SettingsArgs, printer: Printer) -> anyhow::Result<SettingsLayer> {
|
||||||
let base_ctx = CommandContext::base()?;
|
let base_ctx = CommandContext::base(printer)?;
|
||||||
let layers = config_layers(&base_ctx, args.workflow.as_deref())?;
|
let layers = config_layers(&base_ctx, args.workflow.as_deref())?;
|
||||||
if args.local {
|
if args.local {
|
||||||
return Ok(effective_settings::resolve_settings(
|
return Ok(effective_settings::resolve_settings(
|
||||||
|
|
@ -67,7 +68,7 @@ async fn merged_config(args: &SettingsArgs) -> anyhow::Result<SettingsLayer> {
|
||||||
)?);
|
)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
let ctx = CommandContext::for_target(&args.target)?;
|
let ctx = CommandContext::for_target(&args.target, printer)?;
|
||||||
let target = user_config::resolve_server_target(&args.target, ctx.machine_settings())?;
|
let target = user_config::resolve_server_target(&args.target, ctx.machine_settings())?;
|
||||||
let server_settings = ctx.server().await?.retrieve_server_settings().await?;
|
let server_settings = ctx.server().await?.retrieve_server_settings().await?;
|
||||||
let mode = match target {
|
let mode = match target {
|
||||||
|
|
@ -82,8 +83,12 @@ async fn merged_config(args: &SettingsArgs) -> anyhow::Result<SettingsLayer> {
|
||||||
)?)
|
)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn execute(args: &SettingsArgs, globals: &GlobalArgs) -> anyhow::Result<()> {
|
pub(crate) async fn execute(
|
||||||
let config = Box::pin(merged_config(args)).await?;
|
args: &SettingsArgs,
|
||||||
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let config = Box::pin(merged_config(args, printer)).await?;
|
||||||
if globals.json {
|
if globals.json {
|
||||||
print_json_pretty(&config)?;
|
print_json_pretty(&config)?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ use fabro_config::user::{
|
||||||
pub(crate) use fabro_util::check_report::{
|
pub(crate) use fabro_util::check_report::{
|
||||||
CheckDetail, CheckReport, CheckResult, CheckSection, CheckStatus,
|
CheckDetail, CheckReport, CheckResult, CheckSection, CheckStatus,
|
||||||
};
|
};
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use fabro_util::terminal::Styles;
|
use fabro_util::terminal::Styles;
|
||||||
use fabro_util::version::FABRO_VERSION;
|
use fabro_util::version::FABRO_VERSION;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
@ -301,18 +302,23 @@ fn render_report_text(
|
||||||
report.render(styles, verbose, None, max_width)
|
report.render(styles, verbose, None, max_width)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_report(report: &CheckReport, styles: &Styles, verbose: bool) {
|
fn render_report(report: &CheckReport, styles: &Styles, verbose: bool, printer: Printer) {
|
||||||
let term_width = console::Term::stderr().size().1;
|
let term_width = console::Term::stderr().size().1;
|
||||||
print!(
|
{
|
||||||
"{}",
|
use std::fmt::Write as _;
|
||||||
render_report_text(report, styles, verbose, Some(term_width))
|
let _ = write!(
|
||||||
);
|
printer.stdout(),
|
||||||
|
"{}",
|
||||||
|
render_report_text(report, styles, verbose, Some(term_width))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn run_doctor(
|
pub(crate) async fn run_doctor(
|
||||||
args: &DoctorArgs,
|
args: &DoctorArgs,
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
globals: &GlobalArgs,
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
) -> Result<i32, anyhow::Error> {
|
) -> Result<i32, anyhow::Error> {
|
||||||
let styles = Styles::detect_stdout();
|
let styles = Styles::detect_stdout();
|
||||||
let spinner = if globals.json {
|
let spinner = if globals.json {
|
||||||
|
|
@ -360,7 +366,7 @@ pub(crate) async fn run_doctor(
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
|
||||||
let ctx = match CommandContext::for_target(&args.target) {
|
let ctx = match CommandContext::for_target(&args.target, printer) {
|
||||||
Ok(ctx) => ctx,
|
Ok(ctx) => ctx,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
report.sections.push(CheckSection {
|
report.sections.push(CheckSection {
|
||||||
|
|
@ -384,7 +390,7 @@ pub(crate) async fn run_doctor(
|
||||||
if globals.json {
|
if globals.json {
|
||||||
print_json_pretty(&report)?;
|
print_json_pretty(&report)?;
|
||||||
} else {
|
} else {
|
||||||
render_report(&report, &styles, verbose);
|
render_report(&report, &styles, verbose, printer);
|
||||||
}
|
}
|
||||||
return Ok(1);
|
return Ok(1);
|
||||||
}
|
}
|
||||||
|
|
@ -414,7 +420,7 @@ pub(crate) async fn run_doctor(
|
||||||
if globals.json {
|
if globals.json {
|
||||||
print_json_pretty(&report)?;
|
print_json_pretty(&report)?;
|
||||||
} else {
|
} else {
|
||||||
render_report(&report, &styles, verbose);
|
render_report(&report, &styles, verbose, printer);
|
||||||
}
|
}
|
||||||
return Ok(1);
|
return Ok(1);
|
||||||
}
|
}
|
||||||
|
|
@ -443,7 +449,7 @@ pub(crate) async fn run_doctor(
|
||||||
if globals.json {
|
if globals.json {
|
||||||
print_json_pretty(&report)?;
|
print_json_pretty(&report)?;
|
||||||
} else {
|
} else {
|
||||||
render_report(&report, &styles, verbose);
|
render_report(&report, &styles, verbose, printer);
|
||||||
}
|
}
|
||||||
return Ok(1);
|
return Ok(1);
|
||||||
}
|
}
|
||||||
|
|
@ -484,7 +490,7 @@ pub(crate) async fn run_doctor(
|
||||||
if globals.json {
|
if globals.json {
|
||||||
print_json_pretty(&report)?;
|
print_json_pretty(&report)?;
|
||||||
} else {
|
} else {
|
||||||
render_report(&report, &styles, verbose);
|
render_report(&report, &styles, verbose, printer);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(i32::from(report.has_errors()))
|
Ok(i32::from(report.has_errors()))
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ use fabro_mcp::config::{McpServerSettings, McpTransport};
|
||||||
use fabro_types::settings::InterpString;
|
use fabro_types::settings::InterpString;
|
||||||
use fabro_types::settings::cli::OutputFormat as SettingsOutputFormat;
|
use fabro_types::settings::cli::OutputFormat as SettingsOutputFormat;
|
||||||
use fabro_types::settings::run::McpEntryLayer;
|
use fabro_types::settings::run::McpEntryLayer;
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
|
|
||||||
use crate::args::{ExecArgs, GlobalArgs};
|
use crate::args::{ExecArgs, GlobalArgs};
|
||||||
use crate::user_config;
|
use crate::user_config;
|
||||||
|
|
@ -97,7 +98,11 @@ fn runtime_mcp_server(name: &str, entry: &McpEntryLayer) -> McpServerSettings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn execute(mut args: ExecArgs, globals: &GlobalArgs) -> Result<()> {
|
pub(crate) async fn execute(
|
||||||
|
mut args: ExecArgs,
|
||||||
|
globals: &GlobalArgs,
|
||||||
|
_printer: Printer,
|
||||||
|
) -> Result<()> {
|
||||||
use fabro_agent::cli::PermissionLevel as AgentPermissionLevel;
|
use fabro_agent::cli::PermissionLevel as AgentPermissionLevel;
|
||||||
use fabro_types::settings::run::AgentPermissions;
|
use fabro_types::settings::run::AgentPermissions;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ use fabro_api::types;
|
||||||
use fabro_config::load::load_settings_user;
|
use fabro_config::load::load_settings_user;
|
||||||
use fabro_config::user::active_settings_path;
|
use fabro_config::user::active_settings_path;
|
||||||
use fabro_types::settings::SettingsLayer;
|
use fabro_types::settings::SettingsLayer;
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use fabro_util::terminal::Styles;
|
use fabro_util::terminal::Styles;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
|
|
@ -18,12 +19,13 @@ pub(crate) async fn run(
|
||||||
args: &GraphArgs,
|
args: &GraphArgs,
|
||||||
styles: &Styles,
|
styles: &Styles,
|
||||||
globals: &GlobalArgs,
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
if globals.json && args.output.is_none() {
|
if globals.json && args.output.is_none() {
|
||||||
globals.require_no_json()?;
|
globals.require_no_json()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let ctx = CommandContext::for_target(&args.target)?;
|
let ctx = CommandContext::for_target(&args.target, printer)?;
|
||||||
let built = build_run_manifest(ManifestBuildInput {
|
let built = build_run_manifest(ManifestBuildInput {
|
||||||
workflow: args.workflow.clone(),
|
workflow: args.workflow.clone(),
|
||||||
cwd: ctx.cwd().to_path_buf(),
|
cwd: ctx.cwd().to_path_buf(),
|
||||||
|
|
@ -37,7 +39,7 @@ pub(crate) async fn run(
|
||||||
let preflight = client.run_preflight(built.manifest.clone()).await?;
|
let preflight = client.run_preflight(built.manifest.clone()).await?;
|
||||||
let diagnostics = api_diagnostics_to_local(&preflight.workflow.diagnostics);
|
let diagnostics = api_diagnostics_to_local(&preflight.workflow.diagnostics);
|
||||||
|
|
||||||
print_diagnostics(&diagnostics, styles);
|
print_diagnostics(&diagnostics, styles, printer);
|
||||||
if diagnostics
|
if diagnostics
|
||||||
.iter()
|
.iter()
|
||||||
.any(|diagnostic| diagnostic.severity == fabro_validate::Severity::Error)
|
.any(|diagnostic| diagnostic.severity == fabro_validate::Severity::Error)
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ use fabro_config::user::SETTINGS_CONFIG_FILENAME;
|
||||||
use fabro_config::{Storage, legacy_env};
|
use fabro_config::{Storage, legacy_env};
|
||||||
use fabro_model::Provider;
|
use fabro_model::Provider;
|
||||||
use fabro_server::secret_store::SecretStore;
|
use fabro_server::secret_store::SecretStore;
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use fabro_util::terminal::Styles;
|
use fabro_util::terminal::Styles;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
|
|
@ -430,6 +431,7 @@ async fn setup_github_app(
|
||||||
web_url: &str,
|
web_url: &str,
|
||||||
owner: &GitHubAppOwner,
|
owner: &GitHubAppOwner,
|
||||||
username: Option<&str>,
|
username: Option<&str>,
|
||||||
|
printer: Printer,
|
||||||
) -> Result<Vec<(String, String)>> {
|
) -> Result<Vec<(String, String)>> {
|
||||||
let app_name = owner.app_name(username);
|
let app_name = owner.app_name(username);
|
||||||
|
|
||||||
|
|
@ -521,13 +523,14 @@ async fn setup_github_app(
|
||||||
|
|
||||||
// Open browser
|
// Open browser
|
||||||
let url = format!("http://127.0.0.1:{port}/");
|
let url = format!("http://127.0.0.1:{port}/");
|
||||||
eprintln!(" {}", s.dim.apply_to("Opening browser..."));
|
fabro_util::printerr!(printer, " {}", s.dim.apply_to("Opening browser..."));
|
||||||
if let Err(e) = open::that(&url) {
|
if let Err(e) = open::that(&url) {
|
||||||
eprintln!(" Could not open browser automatically: {e}");
|
fabro_util::printerr!(printer, " Could not open browser automatically: {e}");
|
||||||
eprintln!(" Please open this URL manually: {url}");
|
fabro_util::printerr!(printer, " Please open this URL manually: {url}");
|
||||||
}
|
}
|
||||||
|
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" {}",
|
" {}",
|
||||||
s.dim.apply_to("Waiting for GitHub... (Ctrl+C to cancel)")
|
s.dim.apply_to("Waiting for GitHub... (Ctrl+C to cancel)")
|
||||||
);
|
);
|
||||||
|
|
@ -538,7 +541,11 @@ async fn setup_github_app(
|
||||||
.context("did not receive callback from GitHub (was the browser flow completed?)")?;
|
.context("did not receive callback from GitHub (was the browser flow completed?)")?;
|
||||||
|
|
||||||
// Exchange code for app credentials
|
// Exchange code for app credentials
|
||||||
eprintln!(" {}", s.dim.apply_to("Exchanging code with GitHub..."));
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
|
" {}",
|
||||||
|
s.dim.apply_to("Exchanging code with GitHub...")
|
||||||
|
);
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
let resp = client
|
let resp = client
|
||||||
.post(format!(
|
.post(format!(
|
||||||
|
|
@ -601,12 +608,14 @@ async fn setup_github_app(
|
||||||
git_table.insert("slug".into(), toml::Value::String(slug.clone()));
|
git_table.insert("slug".into(), toml::Value::String(slug.clone()));
|
||||||
git_table.insert("client_id".into(), toml::Value::String(client_id));
|
git_table.insert("client_id".into(), toml::Value::String(client_id));
|
||||||
std::fs::write(&user_toml_path, toml::to_string_pretty(&doc)?)?;
|
std::fs::write(&user_toml_path, toml::to_string_pretty(&doc)?)?;
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" {}",
|
" {}",
|
||||||
s.dim
|
s.dim
|
||||||
.apply_to(format!("Wrote {}", user_toml_path.display()))
|
.apply_to(format!("Wrote {}", user_toml_path.display()))
|
||||||
);
|
);
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" {}",
|
" {}",
|
||||||
s.dim
|
s.dim
|
||||||
.apply_to(format!("App: https://github.com/apps/{slug}"))
|
.apply_to(format!("App: https://github.com/apps/{slug}"))
|
||||||
|
|
@ -657,7 +666,11 @@ async fn persist_install_secrets(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn run_install(args: &InstallArgs, globals: &GlobalArgs) -> Result<()> {
|
pub(crate) async fn run_install(
|
||||||
|
args: &InstallArgs,
|
||||||
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
|
) -> Result<()> {
|
||||||
globals.require_no_json()?;
|
globals.require_no_json()?;
|
||||||
let web_url = &args.web_url;
|
let web_url = &args.web_url;
|
||||||
let s = Styles::detect_stderr();
|
let s = Styles::detect_stderr();
|
||||||
|
|
@ -666,16 +679,21 @@ pub(crate) async fn run_install(args: &InstallArgs, globals: &GlobalArgs) -> Res
|
||||||
let storage_dir = user_config::storage_dir(&cli_settings)?;
|
let storage_dir = user_config::storage_dir(&cli_settings)?;
|
||||||
let server_was_running = record::active_server_record(&storage_dir).is_some();
|
let server_was_running = record::active_server_record(&storage_dir).is_some();
|
||||||
|
|
||||||
eprintln!();
|
fabro_util::printerr!(printer, "");
|
||||||
eprintln!(" {}{}", emoji, s.bold.apply_to("Fabro Install"));
|
fabro_util::printerr!(printer, " {}{}", emoji, s.bold.apply_to("Fabro Install"));
|
||||||
eprintln!();
|
fabro_util::printerr!(printer, "");
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" {}",
|
" {}",
|
||||||
s.dim
|
s.dim
|
||||||
.apply_to("Let's get Fabro set up. This will configure your")
|
.apply_to("Let's get Fabro set up. This will configure your")
|
||||||
);
|
);
|
||||||
eprintln!(" {}", s.dim.apply_to("LLM providers and GitHub App."));
|
fabro_util::printerr!(
|
||||||
eprintln!();
|
printer,
|
||||||
|
" {}",
|
||||||
|
s.dim.apply_to("LLM providers and GitHub App.")
|
||||||
|
);
|
||||||
|
fabro_util::printerr!(printer, "");
|
||||||
|
|
||||||
let fabro_dir = fabro_util::Home::from_env().root().to_path_buf();
|
let fabro_dir = fabro_util::Home::from_env().root().to_path_buf();
|
||||||
std::fs::create_dir_all(&fabro_dir)?;
|
std::fs::create_dir_all(&fabro_dir)?;
|
||||||
|
|
@ -683,17 +701,19 @@ pub(crate) async fn run_install(args: &InstallArgs, globals: &GlobalArgs) -> Res
|
||||||
{
|
{
|
||||||
let env_path = legacy_env::legacy_env_file_path();
|
let env_path = legacy_env::legacy_env_file_path();
|
||||||
if env_path.exists() {
|
if env_path.exists() {
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" Warning: {} is no longer read by fabro server. This install will persist credentials in the server secret store instead.",
|
" Warning: {} is no longer read by fabro server. This install will persist credentials in the server secret store instead.",
|
||||||
env_path.display()
|
env_path.display()
|
||||||
);
|
);
|
||||||
eprintln!();
|
fabro_util::printerr!(printer, "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pre-flight checks
|
// Pre-flight checks
|
||||||
{
|
{
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" {}",
|
" {}",
|
||||||
s.dim.apply_to("[Pre-flight] System dependency checks")
|
s.dim.apply_to("[Pre-flight] System dependency checks")
|
||||||
);
|
);
|
||||||
|
|
@ -701,9 +721,9 @@ pub(crate) async fn run_install(args: &InstallArgs, globals: &GlobalArgs) -> Res
|
||||||
let dep_check = doctor::check_system_deps(doctor::DEP_SPECS, &dep_outcomes);
|
let dep_check = doctor::check_system_deps(doctor::DEP_SPECS, &dep_outcomes);
|
||||||
|
|
||||||
if dep_check.status == doctor::CheckStatus::Error {
|
if dep_check.status == doctor::CheckStatus::Error {
|
||||||
eprintln!(" Missing required system dependencies:");
|
fabro_util::printerr!(printer, " Missing required system dependencies:");
|
||||||
for detail in &dep_check.details {
|
for detail in &dep_check.details {
|
||||||
eprintln!(" {}", detail.text);
|
fabro_util::printerr!(printer, " {}", detail.text);
|
||||||
}
|
}
|
||||||
bail!("Install missing required tools before running setup");
|
bail!("Install missing required tools before running setup");
|
||||||
}
|
}
|
||||||
|
|
@ -723,22 +743,22 @@ pub(crate) async fn run_install(args: &InstallArgs, globals: &GlobalArgs) -> Res
|
||||||
.status()
|
.status()
|
||||||
.context("failed to run brew install graphviz")?;
|
.context("failed to run brew install graphviz")?;
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
eprintln!(" Warning: brew install graphviz failed");
|
fabro_util::printerr!(printer, " Warning: brew install graphviz failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for detail in &dep_check.details {
|
for detail in &dep_check.details {
|
||||||
eprintln!(" {}", detail.text);
|
fabro_util::printerr!(printer, " {}", detail.text);
|
||||||
}
|
}
|
||||||
eprintln!();
|
fabro_util::printerr!(printer, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 1: LLM Providers
|
// Step 1: LLM Providers
|
||||||
eprintln!(" {}", s.bold.apply_to("Step 1 · LLM Providers"));
|
fabro_util::printerr!(printer, " {}", s.bold.apply_to("Step 1 · LLM Providers"));
|
||||||
eprintln!(" {}", s.dim.apply_to("──────────────────────"));
|
fabro_util::printerr!(printer, " {}", s.dim.apply_to("──────────────────────"));
|
||||||
eprintln!();
|
fabro_util::printerr!(printer, "");
|
||||||
|
|
||||||
let mut secret_pairs: Vec<(String, String)> = Vec::new();
|
let mut secret_pairs: Vec<(String, String)> = Vec::new();
|
||||||
let mut configured_providers: Vec<Provider> = Vec::new();
|
let mut configured_providers: Vec<Provider> = Vec::new();
|
||||||
|
|
@ -757,7 +777,7 @@ pub(crate) async fn run_install(args: &InstallArgs, globals: &GlobalArgs) -> Res
|
||||||
.await??;
|
.await??;
|
||||||
|
|
||||||
if use_oauth {
|
if use_oauth {
|
||||||
let pairs = run_openai_oauth_or_api_key(&s).await?;
|
let pairs = run_openai_oauth_or_api_key(&s, printer).await?;
|
||||||
secret_pairs.extend(pairs);
|
secret_pairs.extend(pairs);
|
||||||
configured_providers.push(Provider::OpenAi);
|
configured_providers.push(Provider::OpenAi);
|
||||||
openai_via_oauth = true;
|
openai_via_oauth = true;
|
||||||
|
|
@ -780,14 +800,14 @@ pub(crate) async fn run_install(args: &InstallArgs, globals: &GlobalArgs) -> Res
|
||||||
|
|
||||||
let first_provider = primary_providers[primary_idx];
|
let first_provider = primary_providers[primary_idx];
|
||||||
{
|
{
|
||||||
let (env_var, key) = prompt_and_validate_key(first_provider, &s).await?;
|
let (env_var, key) = prompt_and_validate_key(first_provider, &s, printer).await?;
|
||||||
secret_pairs.push((env_var, key));
|
secret_pairs.push((env_var, key));
|
||||||
configured_providers.push(first_provider);
|
configured_providers.push(first_provider);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Additional providers
|
// Additional providers
|
||||||
eprintln!();
|
fabro_util::printerr!(printer, "");
|
||||||
let add_more =
|
let add_more =
|
||||||
spawn_blocking(|| prompt_confirm("Set up additional LLM providers?", false)).await??;
|
spawn_blocking(|| prompt_confirm("Set up additional LLM providers?", false)).await??;
|
||||||
|
|
||||||
|
|
@ -814,16 +834,16 @@ pub(crate) async fn run_install(args: &InstallArgs, globals: &GlobalArgs) -> Res
|
||||||
|
|
||||||
for idx in selected_indices {
|
for idx in selected_indices {
|
||||||
let provider = remaining_providers[idx];
|
let provider = remaining_providers[idx];
|
||||||
let (env_var, key) = prompt_and_validate_key(provider, &s).await?;
|
let (env_var, key) = prompt_and_validate_key(provider, &s, printer).await?;
|
||||||
secret_pairs.push((env_var, key));
|
secret_pairs.push((env_var, key));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
eprintln!();
|
fabro_util::printerr!(printer, "");
|
||||||
|
|
||||||
// Step 2: GitHub App
|
// Step 2: GitHub App
|
||||||
eprintln!(" {}", s.bold.apply_to("Step 2 · GitHub App"));
|
fabro_util::printerr!(printer, " {}", s.bold.apply_to("Step 2 · GitHub App"));
|
||||||
eprintln!(" {}", s.dim.apply_to("───────────────────"));
|
fabro_util::printerr!(printer, " {}", s.dim.apply_to("───────────────────"));
|
||||||
eprintln!();
|
fabro_util::printerr!(printer, "");
|
||||||
|
|
||||||
{
|
{
|
||||||
let setup_github =
|
let setup_github =
|
||||||
|
|
@ -831,8 +851,15 @@ pub(crate) async fn run_install(args: &InstallArgs, globals: &GlobalArgs) -> Res
|
||||||
|
|
||||||
if setup_github {
|
if setup_github {
|
||||||
let (owner, username) = prompt_github_app_owner(&s).await?;
|
let (owner, username) = prompt_github_app_owner(&s).await?;
|
||||||
let github_env_pairs =
|
let github_env_pairs = setup_github_app(
|
||||||
setup_github_app(&fabro_dir, &s, web_url, &owner, username.as_deref()).await?;
|
&fabro_dir,
|
||||||
|
&s,
|
||||||
|
web_url,
|
||||||
|
&owner,
|
||||||
|
username.as_deref(),
|
||||||
|
printer,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
let slug = {
|
let slug = {
|
||||||
let user_toml_path = fabro_dir.join(SETTINGS_CONFIG_FILENAME);
|
let user_toml_path = fabro_dir.join(SETTINGS_CONFIG_FILENAME);
|
||||||
let toml_content = std::fs::read_to_string(&user_toml_path).unwrap_or_default();
|
let toml_content = std::fs::read_to_string(&user_toml_path).unwrap_or_default();
|
||||||
|
|
@ -844,23 +871,24 @@ pub(crate) async fn run_install(args: &InstallArgs, globals: &GlobalArgs) -> Res
|
||||||
.unwrap_or("unknown")
|
.unwrap_or("unknown")
|
||||||
.to_string()
|
.to_string()
|
||||||
};
|
};
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" {} GitHub App registered ({})",
|
" {} GitHub App registered ({})",
|
||||||
s.green.apply_to("✔"),
|
s.green.apply_to("✔"),
|
||||||
slug
|
slug
|
||||||
);
|
);
|
||||||
secret_pairs.extend(github_env_pairs);
|
secret_pairs.extend(github_env_pairs);
|
||||||
} else {
|
} else {
|
||||||
eprintln!(" Skipped");
|
fabro_util::printerr!(printer, " Skipped");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
eprintln!();
|
fabro_util::printerr!(printer, "");
|
||||||
|
|
||||||
// Server configuration
|
// Server configuration
|
||||||
{
|
{
|
||||||
eprintln!(" {}", s.bold.apply_to("Server · Configuration"));
|
fabro_util::printerr!(printer, " {}", s.bold.apply_to("Server · Configuration"));
|
||||||
eprintln!(" {}", s.dim.apply_to("─────────────────────"));
|
fabro_util::printerr!(printer, " {}", s.dim.apply_to("─────────────────────"));
|
||||||
eprintln!();
|
fabro_util::printerr!(printer, "");
|
||||||
|
|
||||||
let config_path = fabro_dir.join(SETTINGS_CONFIG_FILENAME);
|
let config_path = fabro_dir.join(SETTINGS_CONFIG_FILENAME);
|
||||||
let write_config = if config_path.exists() {
|
let write_config = if config_path.exists() {
|
||||||
|
|
@ -884,32 +912,47 @@ pub(crate) async fn run_install(args: &InstallArgs, globals: &GlobalArgs) -> Res
|
||||||
};
|
};
|
||||||
merge_server_settings(&mut doc, &username)?;
|
merge_server_settings(&mut doc, &username)?;
|
||||||
std::fs::write(&config_path, toml::to_string_pretty(&doc)?)?;
|
std::fs::write(&config_path, toml::to_string_pretty(&doc)?)?;
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" {}",
|
" {}",
|
||||||
s.dim.apply_to(format!("Wrote {}", config_path.display()))
|
s.dim.apply_to(format!("Wrote {}", config_path.display()))
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
eprintln!(" {}", s.dim.apply_to("Keeping existing settings.toml"));
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
|
" {}",
|
||||||
|
s.dim.apply_to("Keeping existing settings.toml")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
eprintln!();
|
fabro_util::printerr!(printer, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Secrets and certificates
|
// Secrets and certificates
|
||||||
{
|
{
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" {}",
|
" {}",
|
||||||
s.dim.apply_to("Generating secrets and certificates...")
|
s.dim.apply_to("Generating secrets and certificates...")
|
||||||
);
|
);
|
||||||
|
|
||||||
let session_secret = generate_session_secret();
|
let session_secret = generate_session_secret();
|
||||||
eprintln!(" {} Session secret generated", s.green.apply_to("✔"));
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
|
" {} Session secret generated",
|
||||||
|
s.green.apply_to("✔")
|
||||||
|
);
|
||||||
|
|
||||||
let (jwt_private_pem, jwt_public_pem) = generate_jwt_keypair()?;
|
let (jwt_private_pem, jwt_public_pem) = generate_jwt_keypair()?;
|
||||||
eprintln!(" {} Ed25519 JWT keypair generated", s.green.apply_to("✔"));
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
|
" {} Ed25519 JWT keypair generated",
|
||||||
|
s.green.apply_to("✔")
|
||||||
|
);
|
||||||
|
|
||||||
let certs_dir = fabro_dir.join("certs");
|
let certs_dir = fabro_dir.join("certs");
|
||||||
generate_mtls_certs(&certs_dir)?;
|
generate_mtls_certs(&certs_dir)?;
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" {} mTLS CA + server certificates generated",
|
" {} mTLS CA + server certificates generated",
|
||||||
s.green.apply_to("✔")
|
s.green.apply_to("✔")
|
||||||
);
|
);
|
||||||
|
|
@ -923,43 +966,46 @@ pub(crate) async fn run_install(args: &InstallArgs, globals: &GlobalArgs) -> Res
|
||||||
("SESSION_SECRET".to_string(), session_secret),
|
("SESSION_SECRET".to_string(), session_secret),
|
||||||
];
|
];
|
||||||
secret_pairs.extend(server_env_pairs);
|
secret_pairs.extend(server_env_pairs);
|
||||||
eprintln!();
|
fabro_util::printerr!(printer, "");
|
||||||
|
|
||||||
eprintln!(" To start Fabro, run these commands:");
|
fabro_util::printerr!(printer, " To start Fabro, run these commands:");
|
||||||
eprintln!();
|
fabro_util::printerr!(printer, "");
|
||||||
eprintln!(" fabro server start");
|
fabro_util::printerr!(printer, " fabro server start");
|
||||||
eprintln!();
|
fabro_util::printerr!(printer, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
persist_install_secrets(&storage_dir, &secret_pairs, server_was_running).await?;
|
persist_install_secrets(&storage_dir, &secret_pairs, server_was_running).await?;
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" {} Saved {} secrets to {}",
|
" {} Saved {} secrets to {}",
|
||||||
s.green.apply_to("✔"),
|
s.green.apply_to("✔"),
|
||||||
secret_pairs.len(),
|
secret_pairs.len(),
|
||||||
Storage::new(&storage_dir).secrets_path().display()
|
Storage::new(&storage_dir).secrets_path().display()
|
||||||
);
|
);
|
||||||
if server_was_running {
|
if server_was_running {
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" Warning: the local fabro server was already running. Restart it to pick up startup-time features that only initialize at boot."
|
" Warning: the local fabro server was already running. Restart it to pick up startup-time features that only initialize at boot."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
eprintln!();
|
fabro_util::printerr!(printer, "");
|
||||||
|
|
||||||
// Verify setup
|
// Verify setup
|
||||||
let run_doctor =
|
let run_doctor =
|
||||||
spawn_blocking(|| prompt_confirm("Run fabro doctor to verify?", true)).await??;
|
spawn_blocking(|| prompt_confirm("Run fabro doctor to verify?", true)).await??;
|
||||||
|
|
||||||
if run_doctor {
|
if run_doctor {
|
||||||
eprintln!();
|
fabro_util::printerr!(printer, "");
|
||||||
let doctor_args = DoctorArgs {
|
let doctor_args = DoctorArgs {
|
||||||
target: ServerTargetArgs::default(),
|
target: ServerTargetArgs::default(),
|
||||||
verbose: true,
|
verbose: true,
|
||||||
};
|
};
|
||||||
let _ = doctor::run_doctor(&doctor_args, true, globals).await?;
|
let _ = doctor::run_doctor(&doctor_args, true, globals, printer).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
eprintln!();
|
fabro_util::printerr!(printer, "");
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" Setup complete! Go to your project and run {} to get started.",
|
" Setup complete! Go to your project and run {} to get started.",
|
||||||
s.bold_cyan.apply_to("fabro repo init")
|
s.bold_cyan.apply_to("fabro repo init")
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ use cli_table::format::{Border, Justify, Separator};
|
||||||
use cli_table::{Cell, CellStruct, Color, Style, Table};
|
use cli_table::{Cell, CellStruct, Color, Style, Table};
|
||||||
use fabro_api::{self, types as api_types};
|
use fabro_api::{self, types as api_types};
|
||||||
use fabro_model::{Catalog, Model, Provider};
|
use fabro_model::{Catalog, Model, Provider};
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use fabro_util::terminal::Styles;
|
use fabro_util::terminal::Styles;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
|
|
@ -37,13 +38,17 @@ struct ModelTestOutput {
|
||||||
failures: u32,
|
failures: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn execute(command: Option<ModelsCommand>, globals: &GlobalArgs) -> Result<()> {
|
pub(crate) async fn execute(
|
||||||
|
command: Option<ModelsCommand>,
|
||||||
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
|
) -> Result<()> {
|
||||||
let command = command.unwrap_or_default();
|
let command = command.unwrap_or_default();
|
||||||
let target_args = match &command {
|
let target_args = match &command {
|
||||||
ModelsCommand::List(args) => &args.target,
|
ModelsCommand::List(args) => &args.target,
|
||||||
ModelsCommand::Test(args) => &args.target,
|
ModelsCommand::Test(args) => &args.target,
|
||||||
};
|
};
|
||||||
let ctx = CommandContext::for_target(target_args)?;
|
let ctx = CommandContext::for_target(target_args, printer)?;
|
||||||
let server = ctx.server().await?;
|
let server = ctx.server().await?;
|
||||||
|
|
||||||
run_models(command, server.api(), globals.json).await
|
run_models(command, server.api(), globals.json).await
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,12 @@ use std::io::Write;
|
||||||
|
|
||||||
use fabro_config::project::resolve_workflow;
|
use fabro_config::project::resolve_workflow;
|
||||||
use fabro_graphviz::parser::parse_ast;
|
use fabro_graphviz::parser::parse_ast;
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
|
|
||||||
use crate::args::{GlobalArgs, ParseArgs};
|
use crate::args::{GlobalArgs, ParseArgs};
|
||||||
use crate::shared::read_workflow_file;
|
use crate::shared::read_workflow_file;
|
||||||
|
|
||||||
pub(crate) fn run(args: &ParseArgs, globals: &GlobalArgs) -> anyhow::Result<()> {
|
pub(crate) fn run(args: &ParseArgs, globals: &GlobalArgs, _printer: Printer) -> anyhow::Result<()> {
|
||||||
let _ = globals;
|
let _ = globals;
|
||||||
let stdout = std::io::stdout();
|
let stdout = std::io::stdout();
|
||||||
run_to(args, stdout.lock())
|
run_to(args, stdout.lock())
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
use crate::args::{GlobalArgs, PrCloseArgs};
|
use crate::args::{GlobalArgs, PrCloseArgs};
|
||||||
|
|
@ -8,8 +9,9 @@ pub(super) async fn close_command(
|
||||||
args: PrCloseArgs,
|
args: PrCloseArgs,
|
||||||
github_app: Option<fabro_github::GitHubAppCredentials>,
|
github_app: Option<fabro_github::GitHubAppCredentials>,
|
||||||
globals: &GlobalArgs,
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let (record, _run_id) = super::load_pr_record(&args.server, &args.run_id).await?;
|
let (record, _run_id) = super::load_pr_record(&args.server, &args.run_id, printer).await?;
|
||||||
|
|
||||||
let creds = github_app.context(
|
let creds = github_app.context(
|
||||||
"GitHub App credentials required — set GITHUB_APP_PRIVATE_KEY and configure app_id",
|
"GitHub App credentials required — set GITHUB_APP_PRIVATE_KEY and configure app_id",
|
||||||
|
|
@ -32,7 +34,7 @@ pub(super) async fn close_command(
|
||||||
"html_url": record.html_url,
|
"html_url": record.html_url,
|
||||||
}))?;
|
}))?;
|
||||||
} else {
|
} else {
|
||||||
println!("Closed #{} ({})", record.number, record.html_url);
|
fabro_util::printout!(printer, "Closed #{} ({})", record.number, record.html_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use anyhow::{Context, Result, bail};
|
use anyhow::{Context, Result, bail};
|
||||||
use fabro_model::Catalog;
|
use fabro_model::Catalog;
|
||||||
use fabro_sandbox::daytona::detect_repo_info;
|
use fabro_sandbox::daytona::detect_repo_info;
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use fabro_workflow::outcome::StageStatus;
|
use fabro_workflow::outcome::StageStatus;
|
||||||
use fabro_workflow::pull_request::maybe_open_pull_request;
|
use fabro_workflow::pull_request::maybe_open_pull_request;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
@ -16,8 +17,9 @@ pub(super) async fn create_command(
|
||||||
args: PrCreateArgs,
|
args: PrCreateArgs,
|
||||||
github_app: Option<fabro_github::GitHubAppCredentials>,
|
github_app: Option<fabro_github::GitHubAppCredentials>,
|
||||||
globals: &GlobalArgs,
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let ctx = CommandContext::for_target(&args.server)?;
|
let ctx = CommandContext::for_target(&args.server, printer)?;
|
||||||
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
||||||
let run = lookup.resolve(&args.run_id)?;
|
let run = lookup.resolve(&args.run_id)?;
|
||||||
let run_id = run.run_id();
|
let run_id = run.run_id();
|
||||||
|
|
@ -117,14 +119,14 @@ pub(super) async fn create_command(
|
||||||
if globals.json {
|
if globals.json {
|
||||||
print_json_pretty(&record)?;
|
print_json_pretty(&record)?;
|
||||||
} else {
|
} else {
|
||||||
println!("{}", record.html_url);
|
fabro_util::printout!(printer, "{}", record.html_url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
if globals.json {
|
if globals.json {
|
||||||
print_json_pretty(&serde_json::Value::Null)?;
|
print_json_pretty(&serde_json::Value::Null)?;
|
||||||
} else {
|
} else {
|
||||||
println!("No pull request created (empty diff).");
|
fabro_util::printout!(printer, "No pull request created (empty diff).");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use futures::future::join_all;
|
use futures::future::join_all;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
@ -21,11 +22,12 @@ pub(super) async fn list_command(
|
||||||
args: PrListArgs,
|
args: PrListArgs,
|
||||||
github_app: Option<fabro_github::GitHubAppCredentials>,
|
github_app: Option<fabro_github::GitHubAppCredentials>,
|
||||||
globals: &GlobalArgs,
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let creds = github_app.context(
|
let creds = github_app.context(
|
||||||
"GitHub App credentials required — set GITHUB_APP_PRIVATE_KEY and configure app_id",
|
"GitHub App credentials required — set GITHUB_APP_PRIVATE_KEY and configure app_id",
|
||||||
)?;
|
)?;
|
||||||
let ctx = CommandContext::for_target(&args.server)?;
|
let ctx = CommandContext::for_target(&args.server, printer)?;
|
||||||
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
||||||
|
|
||||||
let mut entries = Vec::new();
|
let mut entries = Vec::new();
|
||||||
|
|
@ -42,7 +44,7 @@ pub(super) async fn list_command(
|
||||||
print_json_pretty(&Vec::<PrRow>::new())?;
|
print_json_pretty(&Vec::<PrRow>::new())?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
println!("No pull requests found.");
|
fabro_util::printout!(printer, "No pull requests found.");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,13 +106,20 @@ pub(super) async fn list_command(
|
||||||
}
|
}
|
||||||
|
|
||||||
if rows.is_empty() {
|
if rows.is_empty() {
|
||||||
println!("No open pull requests found. Use --all to include closed/merged.");
|
fabro_util::printout!(
|
||||||
|
printer,
|
||||||
|
"No open pull requests found. Use --all to include closed/merged."
|
||||||
|
);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
println!(
|
fabro_util::printout!(
|
||||||
|
printer,
|
||||||
"{:<12} {:<6} {:<8} {:<50} URL",
|
"{:<12} {:<6} {:<8} {:<50} URL",
|
||||||
"RUN", "#", "STATE", "TITLE"
|
"RUN",
|
||||||
|
"#",
|
||||||
|
"STATE",
|
||||||
|
"TITLE"
|
||||||
);
|
);
|
||||||
for row in &rows {
|
for row in &rows {
|
||||||
let short_id = if row.run_id.len() > 12 {
|
let short_id = if row.run_id.len() > 12 {
|
||||||
|
|
@ -123,9 +132,14 @@ pub(super) async fn list_command(
|
||||||
} else {
|
} else {
|
||||||
row.title.clone()
|
row.title.clone()
|
||||||
};
|
};
|
||||||
println!(
|
fabro_util::printout!(
|
||||||
|
printer,
|
||||||
"{:<12} {:<6} {:<8} {:<50} {}",
|
"{:<12} {:<6} {:<8} {:<50} {}",
|
||||||
short_id, row.number, row.state, short_title, row.url
|
short_id,
|
||||||
|
row.number,
|
||||||
|
row.state,
|
||||||
|
short_title,
|
||||||
|
row.url
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
use crate::args::{GlobalArgs, PrMergeArgs};
|
use crate::args::{GlobalArgs, PrMergeArgs};
|
||||||
|
|
@ -8,8 +9,9 @@ pub(super) async fn merge_command(
|
||||||
args: PrMergeArgs,
|
args: PrMergeArgs,
|
||||||
github_app: Option<fabro_github::GitHubAppCredentials>,
|
github_app: Option<fabro_github::GitHubAppCredentials>,
|
||||||
globals: &GlobalArgs,
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let (record, _run_id) = super::load_pr_record(&args.server, &args.run_id).await?;
|
let (record, _run_id) = super::load_pr_record(&args.server, &args.run_id, printer).await?;
|
||||||
|
|
||||||
let creds = github_app.context(
|
let creds = github_app.context(
|
||||||
"GitHub App credentials required — set GITHUB_APP_PRIVATE_KEY and configure app_id",
|
"GitHub App credentials required — set GITHUB_APP_PRIVATE_KEY and configure app_id",
|
||||||
|
|
@ -34,7 +36,7 @@ pub(super) async fn merge_command(
|
||||||
"method": args.method,
|
"method": args.method,
|
||||||
}))?;
|
}))?;
|
||||||
} else {
|
} else {
|
||||||
println!("Merged #{} ({})", record.number, record.html_url);
|
fabro_util::printout!(printer, "Merged #{} ({})", record.number, record.html_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,19 @@ mod view;
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use fabro_types::PullRequestRecord;
|
use fabro_types::PullRequestRecord;
|
||||||
use fabro_types::settings::InterpString;
|
use fabro_types::settings::InterpString;
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
|
|
||||||
use crate::args::{GlobalArgs, PrCommand, PrNamespace, ServerTargetArgs};
|
use crate::args::{GlobalArgs, PrCommand, PrNamespace, ServerTargetArgs};
|
||||||
use crate::command_context::CommandContext;
|
use crate::command_context::CommandContext;
|
||||||
use crate::server_runs::ServerSummaryLookup;
|
use crate::server_runs::ServerSummaryLookup;
|
||||||
use crate::shared::github::build_github_app_credentials;
|
use crate::shared::github::build_github_app_credentials;
|
||||||
|
|
||||||
pub(crate) async fn dispatch(ns: PrNamespace, globals: &GlobalArgs) -> Result<()> {
|
pub(crate) async fn dispatch(
|
||||||
let ctx = CommandContext::base()?;
|
ns: PrNamespace,
|
||||||
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
|
) -> Result<()> {
|
||||||
|
let ctx = CommandContext::base(printer)?;
|
||||||
let server_settings =
|
let server_settings =
|
||||||
fabro_config::resolve_server_from_file(ctx.machine_settings()).map_err(|errors| {
|
fabro_config::resolve_server_from_file(ctx.machine_settings()).map_err(|errors| {
|
||||||
anyhow::anyhow!(
|
anyhow::anyhow!(
|
||||||
|
|
@ -37,20 +42,21 @@ pub(crate) async fn dispatch(ns: PrNamespace, globals: &GlobalArgs) -> Result<()
|
||||||
)?;
|
)?;
|
||||||
match ns.command {
|
match ns.command {
|
||||||
PrCommand::Create(args) => {
|
PrCommand::Create(args) => {
|
||||||
Box::pin(create::create_command(args, github_app, globals)).await
|
Box::pin(create::create_command(args, github_app, globals, printer)).await
|
||||||
}
|
}
|
||||||
PrCommand::List(args) => list::list_command(args, github_app, globals).await,
|
PrCommand::List(args) => list::list_command(args, github_app, globals, printer).await,
|
||||||
PrCommand::View(args) => view::view_command(args, github_app, globals).await,
|
PrCommand::View(args) => view::view_command(args, github_app, globals, printer).await,
|
||||||
PrCommand::Merge(args) => merge::merge_command(args, github_app, globals).await,
|
PrCommand::Merge(args) => merge::merge_command(args, github_app, globals, printer).await,
|
||||||
PrCommand::Close(args) => close::close_command(args, github_app, globals).await,
|
PrCommand::Close(args) => close::close_command(args, github_app, globals, printer).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn load_pr_record(
|
pub(crate) async fn load_pr_record(
|
||||||
server: &ServerTargetArgs,
|
server: &ServerTargetArgs,
|
||||||
run_id: &str,
|
run_id: &str,
|
||||||
|
printer: Printer,
|
||||||
) -> Result<(PullRequestRecord, fabro_types::RunId)> {
|
) -> Result<(PullRequestRecord, fabro_types::RunId)> {
|
||||||
let ctx = CommandContext::for_target(server)?;
|
let ctx = CommandContext::for_target(server, printer)?;
|
||||||
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
||||||
let run = lookup.resolve(run_id)?;
|
let run = lookup.resolve(run_id)?;
|
||||||
let run_id = run.run_id();
|
let run_id = run.run_id();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
use crate::args::{GlobalArgs, PrViewArgs};
|
use crate::args::{GlobalArgs, PrViewArgs};
|
||||||
|
|
@ -8,8 +9,9 @@ pub(super) async fn view_command(
|
||||||
args: PrViewArgs,
|
args: PrViewArgs,
|
||||||
github_app: Option<fabro_github::GitHubAppCredentials>,
|
github_app: Option<fabro_github::GitHubAppCredentials>,
|
||||||
globals: &GlobalArgs,
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let (record, _run_id) = super::load_pr_record(&args.server, &args.run_id).await?;
|
let (record, _run_id) = super::load_pr_record(&args.server, &args.run_id, printer).await?;
|
||||||
|
|
||||||
let creds = github_app.context(
|
let creds = github_app.context(
|
||||||
"GitHub App credentials required — set GITHUB_APP_PRIVATE_KEY and configure app_id",
|
"GitHub App credentials required — set GITHUB_APP_PRIVATE_KEY and configure app_id",
|
||||||
|
|
@ -32,23 +34,28 @@ pub(super) async fn view_command(
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("#{} {}", detail.number, detail.title);
|
fabro_util::printout!(printer, "#{} {}", detail.number, detail.title);
|
||||||
let state_display = if detail.draft { "draft" } else { &detail.state };
|
let state_display = if detail.draft { "draft" } else { &detail.state };
|
||||||
println!("State: {state_display}");
|
fabro_util::printout!(printer, "State: {state_display}");
|
||||||
println!("URL: {}", detail.html_url);
|
fabro_util::printout!(printer, "URL: {}", detail.html_url);
|
||||||
println!(
|
fabro_util::printout!(
|
||||||
|
printer,
|
||||||
"Branch: {} -> {}",
|
"Branch: {} -> {}",
|
||||||
detail.head.ref_name, detail.base.ref_name
|
detail.head.ref_name,
|
||||||
|
detail.base.ref_name
|
||||||
);
|
);
|
||||||
println!("Author: {}", detail.user.login);
|
fabro_util::printout!(printer, "Author: {}", detail.user.login);
|
||||||
println!(
|
fabro_util::printout!(
|
||||||
|
printer,
|
||||||
"Changes: +{} -{} ({} files)",
|
"Changes: +{} -{} ({} files)",
|
||||||
detail.additions, detail.deletions, detail.changed_files
|
detail.additions,
|
||||||
|
detail.deletions,
|
||||||
|
detail.changed_files
|
||||||
);
|
);
|
||||||
if let Some(body) = &detail.body {
|
if let Some(body) = &detail.body {
|
||||||
if !body.is_empty() {
|
if !body.is_empty() {
|
||||||
println!();
|
fabro_util::printout!(printer, "");
|
||||||
println!("{body}");
|
fabro_util::printout!(printer, "{body}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ use anyhow::bail;
|
||||||
use fabro_config::load::load_settings_user;
|
use fabro_config::load::load_settings_user;
|
||||||
use fabro_config::user::active_settings_path;
|
use fabro_config::user::active_settings_path;
|
||||||
use fabro_types::settings::cli::OutputVerbosity;
|
use fabro_types::settings::cli::OutputVerbosity;
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use fabro_util::terminal::Styles;
|
use fabro_util::terminal::Styles;
|
||||||
|
|
||||||
use crate::args::{GlobalArgs, PreflightArgs};
|
use crate::args::{GlobalArgs, PreflightArgs};
|
||||||
|
|
@ -13,9 +14,13 @@ use crate::commands::run::overrides::preflight_args_layer;
|
||||||
use crate::manifest_builder::{ManifestBuildInput, build_run_manifest, preflight_manifest_args};
|
use crate::manifest_builder::{ManifestBuildInput, build_run_manifest, preflight_manifest_args};
|
||||||
use crate::shared::print_json_pretty;
|
use crate::shared::print_json_pretty;
|
||||||
|
|
||||||
pub(crate) async fn execute(mut args: PreflightArgs, globals: &GlobalArgs) -> anyhow::Result<()> {
|
pub(crate) async fn execute(
|
||||||
|
mut args: PreflightArgs,
|
||||||
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr()));
|
let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr()));
|
||||||
let ctx = CommandContext::for_target(&args.target)?;
|
let ctx = CommandContext::for_target(&args.target, printer)?;
|
||||||
args.verbose = args.verbose || ctx.cli_settings().output.verbosity == OutputVerbosity::Verbose;
|
args.verbose = args.verbose || ctx.cli_settings().output.verbosity == OutputVerbosity::Verbose;
|
||||||
|
|
||||||
let manifest = build_run_manifest(ManifestBuildInput {
|
let manifest = build_run_manifest(ManifestBuildInput {
|
||||||
|
|
@ -34,7 +39,12 @@ pub(crate) async fn execute(mut args: PreflightArgs, globals: &GlobalArgs) -> an
|
||||||
if globals.json {
|
if globals.json {
|
||||||
print_json_pretty(&response)?;
|
print_json_pretty(&response)?;
|
||||||
} else {
|
} else {
|
||||||
print_preflight_workflow_summary(&response.workflow, Some(&manifest.target_path), styles);
|
print_preflight_workflow_summary(
|
||||||
|
&response.workflow,
|
||||||
|
Some(&manifest.target_path),
|
||||||
|
styles,
|
||||||
|
printer,
|
||||||
|
);
|
||||||
if diagnostics
|
if diagnostics
|
||||||
.iter()
|
.iter()
|
||||||
.any(|diagnostic| diagnostic.severity == fabro_validate::Severity::Error)
|
.any(|diagnostic| diagnostic.severity == fabro_validate::Severity::Error)
|
||||||
|
|
@ -43,7 +53,14 @@ pub(crate) async fn execute(mut args: PreflightArgs, globals: &GlobalArgs) -> an
|
||||||
}
|
}
|
||||||
let report = api_check_report_to_local(&response.checks);
|
let report = api_check_report_to_local(&response.checks);
|
||||||
let term_width = console::Term::stderr().size().1;
|
let term_width = console::Term::stderr().size().1;
|
||||||
print!("{}", report.render(styles, true, None, Some(term_width)));
|
{
|
||||||
|
use std::fmt::Write as _;
|
||||||
|
let _ = write!(
|
||||||
|
printer.stdout(),
|
||||||
|
"{}",
|
||||||
|
report.render(styles, true, None, Some(term_width))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if diagnostics
|
if diagnostics
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ use anyhow::Result;
|
||||||
use fabro_api::types;
|
use fabro_api::types;
|
||||||
use fabro_config::legacy_env;
|
use fabro_config::legacy_env;
|
||||||
use fabro_model::Provider;
|
use fabro_model::Provider;
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use fabro_util::terminal::Styles;
|
use fabro_util::terminal::Styles;
|
||||||
use tokio::task::spawn_blocking;
|
use tokio::task::spawn_blocking;
|
||||||
|
|
||||||
|
|
@ -9,10 +10,14 @@ use crate::args::{GlobalArgs, ProviderLoginArgs};
|
||||||
use crate::command_context::CommandContext;
|
use crate::command_context::CommandContext;
|
||||||
use crate::shared::provider_auth;
|
use crate::shared::provider_auth;
|
||||||
|
|
||||||
pub(super) async fn login_command(args: ProviderLoginArgs, globals: &GlobalArgs) -> Result<()> {
|
pub(super) async fn login_command(
|
||||||
|
args: ProviderLoginArgs,
|
||||||
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
|
) -> Result<()> {
|
||||||
globals.require_no_json()?;
|
globals.require_no_json()?;
|
||||||
let s = Styles::detect_stderr();
|
let s = Styles::detect_stderr();
|
||||||
let ctx = CommandContext::for_target(&args.target)?;
|
let ctx = CommandContext::for_target(&args.target, printer)?;
|
||||||
let server = ctx.server().await?;
|
let server = ctx.server().await?;
|
||||||
|
|
||||||
let use_oauth = args.provider == Provider::OpenAi
|
let use_oauth = args.provider == Provider::OpenAi
|
||||||
|
|
@ -20,16 +25,18 @@ pub(super) async fn login_command(args: ProviderLoginArgs, globals: &GlobalArgs)
|
||||||
.await??;
|
.await??;
|
||||||
|
|
||||||
let env_pairs = if use_oauth {
|
let env_pairs = if use_oauth {
|
||||||
provider_auth::run_openai_oauth_or_api_key(&s).await?
|
provider_auth::run_openai_oauth_or_api_key(&s, printer).await?
|
||||||
} else {
|
} else {
|
||||||
let (env_var, key) = provider_auth::prompt_and_validate_key(args.provider, &s).await?;
|
let (env_var, key) =
|
||||||
|
provider_auth::prompt_and_validate_key(args.provider, &s, printer).await?;
|
||||||
vec![(env_var, key)]
|
vec![(env_var, key)]
|
||||||
};
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
let path = legacy_env::legacy_env_file_path();
|
let path = legacy_env::legacy_env_file_path();
|
||||||
if path.exists() {
|
if path.exists() {
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" Warning: {} is no longer read by fabro server. Re-enter credentials with `fabro provider login` or `fabro secret set`.",
|
" Warning: {} is no longer read by fabro server. Re-enter credentials with `fabro provider login` or `fabro secret set`.",
|
||||||
path.display()
|
path.display()
|
||||||
);
|
);
|
||||||
|
|
@ -44,7 +51,7 @@ pub(super) async fn login_command(args: ProviderLoginArgs, globals: &GlobalArgs)
|
||||||
.body(types::SetSecretRequest { value })
|
.body(types::SetSecretRequest { value })
|
||||||
.send()
|
.send()
|
||||||
.await?;
|
.await?;
|
||||||
eprintln!(" {} Saved {}", s.green.apply_to("✔"), name);
|
fabro_util::printerr!(printer, " {} Saved {}", s.green.apply_to("✔"), name);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,16 @@
|
||||||
mod login;
|
mod login;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
|
|
||||||
use crate::args::{GlobalArgs, ProviderCommand, ProviderNamespace};
|
use crate::args::{GlobalArgs, ProviderCommand, ProviderNamespace};
|
||||||
|
|
||||||
pub(crate) async fn dispatch(ns: ProviderNamespace, globals: &GlobalArgs) -> Result<()> {
|
pub(crate) async fn dispatch(
|
||||||
|
ns: ProviderNamespace,
|
||||||
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
|
) -> Result<()> {
|
||||||
match ns.command {
|
match ns.command {
|
||||||
ProviderCommand::Login(args) => login::login_command(args, globals).await,
|
ProviderCommand::Login(args) => login::login_command(args, globals, printer).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
use anyhow::{Context, Result, bail};
|
use anyhow::{Context, Result, bail};
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
|
|
||||||
use crate::args::GlobalArgs;
|
use crate::args::GlobalArgs;
|
||||||
|
|
||||||
pub(crate) fn run_deinit(globals: &GlobalArgs) -> Result<Vec<String>> {
|
pub(crate) fn run_deinit(globals: &GlobalArgs, printer: Printer) -> Result<Vec<String>> {
|
||||||
let repo_root = super::init::git_repo_root()?;
|
let repo_root = super::init::git_repo_root()?;
|
||||||
let mut removed = Vec::new();
|
let mut removed = Vec::new();
|
||||||
|
|
||||||
|
|
@ -20,7 +21,8 @@ pub(crate) fn run_deinit(globals: &GlobalArgs) -> Result<Vec<String>> {
|
||||||
.with_context(|| format!("failed to remove {}", fabro_dir.display()))?;
|
.with_context(|| format!("failed to remove {}", fabro_dir.display()))?;
|
||||||
removed.push(".fabro/".to_string());
|
removed.push(".fabro/".to_string());
|
||||||
if !globals.json {
|
if !globals.json {
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" {} {}",
|
" {} {}",
|
||||||
green.apply_to("✔"),
|
green.apply_to("✔"),
|
||||||
dim.apply_to("removed .fabro/")
|
dim.apply_to("removed .fabro/")
|
||||||
|
|
@ -28,7 +30,8 @@ pub(crate) fn run_deinit(globals: &GlobalArgs) -> Result<Vec<String>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !globals.json {
|
if !globals.json {
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
"\n{}",
|
"\n{}",
|
||||||
console::Style::new()
|
console::Style::new()
|
||||||
.bold()
|
.bold()
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use anyhow::{Context, Result, bail};
|
use anyhow::{Context, Result, bail};
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use tokio::task::spawn_blocking;
|
use tokio::task::spawn_blocking;
|
||||||
|
|
||||||
use crate::args::{GlobalArgs, RepoInitArgs, ServerTargetArgs};
|
use crate::args::{GlobalArgs, RepoInitArgs, ServerTargetArgs};
|
||||||
|
|
@ -21,7 +22,11 @@ pub(super) fn git_repo_root() -> Result<PathBuf> {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn run_init(args: &RepoInitArgs, globals: &GlobalArgs) -> Result<Vec<String>> {
|
pub(crate) async fn run_init(
|
||||||
|
args: &RepoInitArgs,
|
||||||
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
|
) -> Result<Vec<String>> {
|
||||||
let repo_root = git_repo_root()?;
|
let repo_root = git_repo_root()?;
|
||||||
let mut created = Vec::new();
|
let mut created = Vec::new();
|
||||||
|
|
||||||
|
|
@ -60,7 +65,8 @@ draft = true
|
||||||
let bold = console::Style::new().bold();
|
let bold = console::Style::new().bold();
|
||||||
let dim = console::Style::new().dim();
|
let dim = console::Style::new().dim();
|
||||||
if !globals.json {
|
if !globals.json {
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" {} {}",
|
" {} {}",
|
||||||
green.apply_to("✔"),
|
green.apply_to("✔"),
|
||||||
dim.apply_to(".fabro/project.toml")
|
dim.apply_to(".fabro/project.toml")
|
||||||
|
|
@ -92,7 +98,8 @@ draft = true
|
||||||
.with_context(|| format!("failed to write {}", dot_path.display()))?;
|
.with_context(|| format!("failed to write {}", dot_path.display()))?;
|
||||||
created.push(".fabro/workflows/hello/workflow.fabro".to_string());
|
created.push(".fabro/workflows/hello/workflow.fabro".to_string());
|
||||||
if !globals.json {
|
if !globals.json {
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" {} {}",
|
" {} {}",
|
||||||
green.apply_to("✔"),
|
green.apply_to("✔"),
|
||||||
dim.apply_to(".fabro/workflows/hello/workflow.fabro")
|
dim.apply_to(".fabro/workflows/hello/workflow.fabro")
|
||||||
|
|
@ -108,7 +115,8 @@ draft = true
|
||||||
.with_context(|| format!("failed to write {}", toml_path.display()))?;
|
.with_context(|| format!("failed to write {}", toml_path.display()))?;
|
||||||
created.push(".fabro/workflows/hello/workflow.toml".to_string());
|
created.push(".fabro/workflows/hello/workflow.toml".to_string());
|
||||||
if !globals.json {
|
if !globals.json {
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" {} {}",
|
" {} {}",
|
||||||
green.apply_to("✔"),
|
green.apply_to("✔"),
|
||||||
dim.apply_to(".fabro/workflows/hello/workflow.toml")
|
dim.apply_to(".fabro/workflows/hello/workflow.toml")
|
||||||
|
|
@ -116,7 +124,8 @@ draft = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if !globals.json {
|
if !globals.json {
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
"\n{} Run a workflow with:\n\n {}",
|
"\n{} Run a workflow with:\n\n {}",
|
||||||
bold.apply_to("Project initialized!"),
|
bold.apply_to("Project initialized!"),
|
||||||
console::Style::new()
|
console::Style::new()
|
||||||
|
|
@ -127,13 +136,13 @@ draft = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if !globals.json {
|
if !globals.json {
|
||||||
check_github_app_installation(&args.target).await;
|
check_github_app_installation(&args.target, printer).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(created)
|
Ok(created)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn check_github_app_installation(target: &ServerTargetArgs) {
|
async fn check_github_app_installation(target: &ServerTargetArgs, printer: Printer) {
|
||||||
// Get the git remote origin URL
|
// Get the git remote origin URL
|
||||||
let output = match std::process::Command::new("git")
|
let output = match std::process::Command::new("git")
|
||||||
.args(["remote", "get-url", "origin"])
|
.args(["remote", "get-url", "origin"])
|
||||||
|
|
@ -143,11 +152,13 @@ async fn check_github_app_installation(target: &ServerTargetArgs) {
|
||||||
_ => {
|
_ => {
|
||||||
let yellow = console::Style::new().yellow();
|
let yellow = console::Style::new().yellow();
|
||||||
let dim = console::Style::new().dim();
|
let dim = console::Style::new().dim();
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
"\n {} No git remote found — skipping GitHub App check",
|
"\n {} No git remote found — skipping GitHub App check",
|
||||||
yellow.apply_to("!")
|
yellow.apply_to("!")
|
||||||
);
|
);
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" {}",
|
" {}",
|
||||||
dim.apply_to(
|
dim.apply_to(
|
||||||
"Run `git remote add origin <url>` then `fabro install` to set up the GitHub App"
|
"Run `git remote add origin <url>` then `fabro install` to set up the GitHub App"
|
||||||
|
|
@ -168,10 +179,13 @@ async fn check_github_app_installation(target: &ServerTargetArgs) {
|
||||||
return; // Not a GitHub repo — skip silently
|
return; // Not a GitHub repo — skip silently
|
||||||
};
|
};
|
||||||
|
|
||||||
let ctx = match CommandContext::for_target(target) {
|
let ctx = match CommandContext::for_target(target, printer) {
|
||||||
Ok(ctx) => ctx,
|
Ok(ctx) => ctx,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("\n Warning: could not resolve fabro server settings: {err}");
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
|
"\n Warning: could not resolve fabro server settings: {err}"
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -179,7 +193,10 @@ async fn check_github_app_installation(target: &ServerTargetArgs) {
|
||||||
let server = match ctx.server().await {
|
let server = match ctx.server().await {
|
||||||
Ok(server) => server,
|
Ok(server) => server,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("\n Warning: could not connect to fabro server: {err}");
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
|
"\n Warning: could not connect to fabro server: {err}"
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -194,14 +211,18 @@ async fn check_github_app_installation(target: &ServerTargetArgs) {
|
||||||
{
|
{
|
||||||
Ok(response) => response.into_inner(),
|
Ok(response) => response.into_inner(),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("\n Warning: could not check GitHub App installation: {err}");
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
|
"\n Warning: could not check GitHub App installation: {err}"
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if check.accessible {
|
if check.accessible {
|
||||||
let green = console::Style::new().green();
|
let green = console::Style::new().green();
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
"\n {} GitHub App is installed for {owner}/{repo}",
|
"\n {} GitHub App is installed for {owner}/{repo}",
|
||||||
green.apply_to("✔")
|
green.apply_to("✔")
|
||||||
);
|
);
|
||||||
|
|
@ -209,16 +230,17 @@ async fn check_github_app_installation(target: &ServerTargetArgs) {
|
||||||
}
|
}
|
||||||
|
|
||||||
let yellow = console::Style::new().yellow();
|
let yellow = console::Style::new().yellow();
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
"\n {} GitHub App is not installed for {owner}/{repo}",
|
"\n {} GitHub App is not installed for {owner}/{repo}",
|
||||||
yellow.apply_to("!")
|
yellow.apply_to("!")
|
||||||
);
|
);
|
||||||
if let Some(url) = &check.install_url {
|
if let Some(url) = &check.install_url {
|
||||||
eprintln!(" Install at: {url}");
|
fabro_util::printerr!(printer, " Install at: {url}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if std::io::IsTerminal::is_terminal(&std::io::stdin()) {
|
if std::io::IsTerminal::is_terminal(&std::io::stdin()) {
|
||||||
eprintln!(" Press Enter to continue after installing...");
|
fabro_util::printerr!(printer, " Press Enter to continue after installing...");
|
||||||
let _ = spawn_blocking(|| {
|
let _ = spawn_blocking(|| {
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
let _ = std::io::stdin().read_line(&mut buf);
|
let _ = std::io::stdin().read_line(&mut buf);
|
||||||
|
|
@ -237,19 +259,23 @@ async fn check_github_app_installation(target: &ServerTargetArgs) {
|
||||||
let response = response.into_inner();
|
let response = response.into_inner();
|
||||||
if response.accessible {
|
if response.accessible {
|
||||||
let green = console::Style::new().green();
|
let green = console::Style::new().green();
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" {} GitHub App is installed for {owner}/{repo}",
|
" {} GitHub App is installed for {owner}/{repo}",
|
||||||
green.apply_to("✔")
|
green.apply_to("✔")
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
eprintln!(" GitHub App is still not installed.");
|
fabro_util::printerr!(printer, " GitHub App is still not installed.");
|
||||||
if let Some(url) = &check.install_url {
|
if let Some(url) = &check.install_url {
|
||||||
eprintln!(" Install at: {url}");
|
fabro_util::printerr!(printer, " Install at: {url}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!(" Warning: could not re-check GitHub App installation: {err}");
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
|
" Warning: could not re-check GitHub App installation: {err}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,26 @@ pub(crate) mod deinit;
|
||||||
pub(crate) mod init;
|
pub(crate) mod init;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
|
|
||||||
use crate::args::{GlobalArgs, RepoCommand, RepoNamespace};
|
use crate::args::{GlobalArgs, RepoCommand, RepoNamespace};
|
||||||
use crate::shared::print_json_pretty;
|
use crate::shared::print_json_pretty;
|
||||||
|
|
||||||
pub(crate) async fn dispatch(ns: RepoNamespace, globals: &GlobalArgs) -> Result<()> {
|
pub(crate) async fn dispatch(
|
||||||
|
ns: RepoNamespace,
|
||||||
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
|
) -> Result<()> {
|
||||||
match ns.command {
|
match ns.command {
|
||||||
RepoCommand::Init(args) => {
|
RepoCommand::Init(args) => {
|
||||||
let created = init::run_init(&args, globals).await?;
|
let created = init::run_init(&args, globals, printer).await?;
|
||||||
if globals.json {
|
if globals.json {
|
||||||
print_json_pretty(&serde_json::json!({ "created": created }))?;
|
print_json_pretty(&serde_json::json!({ "created": created }))?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
RepoCommand::Deinit => {
|
RepoCommand::Deinit => {
|
||||||
let removed = deinit::run_deinit(globals)?;
|
let removed = deinit::run_deinit(globals, printer)?;
|
||||||
if globals.json {
|
if globals.json {
|
||||||
print_json_pretty(&serde_json::json!({ "removed": removed }))?;
|
print_json_pretty(&serde_json::json!({ "removed": removed }))?;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,15 @@ pub(crate) async fn attach_run(
|
||||||
|
|
||||||
if let (Some(storage_dir), Some(run_id)) = (storage_dir.as_deref(), run_id.as_ref()) {
|
if let (Some(storage_dir), Some(run_id)) = (storage_dir.as_deref(), run_id.as_ref()) {
|
||||||
let client = server_client::connect_server(storage_dir).await?;
|
let client = server_client::connect_server(storage_dir).await?;
|
||||||
return attach_run_with_client(&client, run_id, kill_on_detach, styles, json_output).await;
|
return attach_run_with_client(
|
||||||
|
&client,
|
||||||
|
run_id,
|
||||||
|
kill_on_detach,
|
||||||
|
styles,
|
||||||
|
json_output,
|
||||||
|
fabro_util::printer::Printer::Default,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(anyhow::anyhow!(
|
Err(anyhow::anyhow!(
|
||||||
|
|
@ -61,6 +69,7 @@ pub(crate) async fn attach_run_with_client(
|
||||||
kill_on_detach: bool,
|
kill_on_detach: bool,
|
||||||
styles: &'static Styles,
|
styles: &'static Styles,
|
||||||
json_output: bool,
|
json_output: bool,
|
||||||
|
printer: fabro_util::printer::Printer,
|
||||||
) -> Result<ExitCode> {
|
) -> Result<ExitCode> {
|
||||||
let state = client.get_run_state(run_id).await?;
|
let state = client.get_run_state(run_id).await?;
|
||||||
let auto_approve = state.run.as_ref().is_some_and(|record| {
|
let auto_approve = state.run.as_ref().is_some_and(|record| {
|
||||||
|
|
@ -103,6 +112,7 @@ pub(crate) async fn attach_run_with_client(
|
||||||
kill_on_detach,
|
kill_on_detach,
|
||||||
json_output,
|
json_output,
|
||||||
},
|
},
|
||||||
|
printer,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
@ -140,6 +150,7 @@ async fn attach_live_run_with_client(
|
||||||
mut stream: server_client::RunAttachEventStream,
|
mut stream: server_client::RunAttachEventStream,
|
||||||
styles: &'static Styles,
|
styles: &'static Styles,
|
||||||
opts: AttachOptions,
|
opts: AttachOptions,
|
||||||
|
printer: fabro_util::printer::Printer,
|
||||||
) -> Result<ExitCode> {
|
) -> Result<ExitCode> {
|
||||||
let is_tty = std::io::stderr().is_terminal();
|
let is_tty = std::io::stderr().is_terminal();
|
||||||
let mut progress_ui = run_progress::ProgressUI::new(is_tty, opts.verbose);
|
let mut progress_ui = run_progress::ProgressUI::new(is_tty, opts.verbose);
|
||||||
|
|
@ -158,6 +169,7 @@ async fn attach_live_run_with_client(
|
||||||
&mut progress_ui,
|
&mut progress_ui,
|
||||||
styles,
|
styles,
|
||||||
opts.json_output,
|
opts.json_output,
|
||||||
|
printer,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
|
|
@ -167,7 +179,7 @@ async fn attach_live_run_with_client(
|
||||||
loop {
|
loop {
|
||||||
let next_event = tokio::select! {
|
let next_event = tokio::select! {
|
||||||
_ = &mut ctrl_c_signal => {
|
_ = &mut ctrl_c_signal => {
|
||||||
handle_detach_signal(client, run_id, opts.kill_on_detach).await;
|
handle_detach_signal(client, run_id, opts.kill_on_detach, printer).await;
|
||||||
finish_progress(&mut progress_ui, opts.json_output);
|
finish_progress(&mut progress_ui, opts.json_output);
|
||||||
return Ok(ExitCode::from(1));
|
return Ok(ExitCode::from(1));
|
||||||
}
|
}
|
||||||
|
|
@ -195,6 +207,7 @@ async fn attach_live_run_with_client(
|
||||||
&mut progress_ui,
|
&mut progress_ui,
|
||||||
styles,
|
styles,
|
||||||
opts.json_output,
|
opts.json_output,
|
||||||
|
printer,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
|
|
@ -211,13 +224,14 @@ async fn handle_pending_server_interview(
|
||||||
progress_ui: &mut run_progress::ProgressUI,
|
progress_ui: &mut run_progress::ProgressUI,
|
||||||
styles: &'static Styles,
|
styles: &'static Styles,
|
||||||
json_output: bool,
|
json_output: bool,
|
||||||
|
printer: fabro_util::printer::Printer,
|
||||||
) -> Result<Option<ExitCode>> {
|
) -> Result<Option<ExitCode>> {
|
||||||
let Some(question) = client.list_run_questions(run_id).await?.into_iter().next() else {
|
let Some(question) = client.list_run_questions(run_id).await?.into_iter().next() else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
|
||||||
if json_pending_interview_requires_manual_input(json_output, auto_approve) {
|
if json_pending_interview_requires_manual_input(json_output, auto_approve) {
|
||||||
eprintln!("{JSON_INTERVIEW_MESSAGE}");
|
fabro_util::printerr!(printer, "{JSON_INTERVIEW_MESSAGE}");
|
||||||
return Ok(Some(ExitCode::from(1)));
|
return Ok(Some(ExitCode::from(1)));
|
||||||
}
|
}
|
||||||
if json_output {
|
if json_output {
|
||||||
|
|
@ -231,7 +245,7 @@ async fn handle_pending_server_interview(
|
||||||
show_progress(progress_ui, json_output);
|
show_progress(progress_ui, json_output);
|
||||||
|
|
||||||
if answer_requires_reattach(&answer) {
|
if answer_requires_reattach(&answer) {
|
||||||
eprintln!("{INTERVIEW_UNANSWERED_MESSAGE}");
|
fabro_util::printerr!(printer, "{INTERVIEW_UNANSWERED_MESSAGE}");
|
||||||
return Ok(Some(ExitCode::from(1)));
|
return Ok(Some(ExitCode::from(1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -243,6 +257,7 @@ async fn handle_detach_signal(
|
||||||
client: &server_client::ServerStoreClient,
|
client: &server_client::ServerStoreClient,
|
||||||
run_id: &RunId,
|
run_id: &RunId,
|
||||||
kill_on_detach: bool,
|
kill_on_detach: bool,
|
||||||
|
printer: fabro_util::printer::Printer,
|
||||||
) {
|
) {
|
||||||
if kill_on_detach {
|
if kill_on_detach {
|
||||||
let _ = client.cancel_run(run_id).await;
|
let _ = client.cancel_run(run_id).await;
|
||||||
|
|
@ -258,7 +273,10 @@ async fn handle_detach_signal(
|
||||||
sleep(Duration::from_millis(100)).await;
|
sleep(Duration::from_millis(100)).await;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
eprintln!("Detached from run (engine continues in background)");
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
|
"Detached from run (engine continues in background)"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -598,7 +616,13 @@ mod tests {
|
||||||
});
|
});
|
||||||
let client = server_client::ServerStoreClient::new_no_proxy(&server.base_url()).unwrap();
|
let client = server_client::ServerStoreClient::new_no_proxy(&server.base_url()).unwrap();
|
||||||
|
|
||||||
handle_detach_signal(&client, &run_id, true).await;
|
handle_detach_signal(
|
||||||
|
&client,
|
||||||
|
&run_id,
|
||||||
|
true,
|
||||||
|
fabro_util::printer::Printer::Default,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
cancel_mock.assert();
|
cancel_mock.assert();
|
||||||
state_mock.assert();
|
state_mock.assert();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use fabro_types::settings::cli::OutputVerbosity;
|
use fabro_types::settings::cli::OutputVerbosity;
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use fabro_util::terminal::Styles;
|
use fabro_util::terminal::Styles;
|
||||||
|
|
||||||
use crate::args::{GlobalArgs, RunArgs};
|
use crate::args::{GlobalArgs, RunArgs};
|
||||||
|
|
@ -7,15 +8,22 @@ use crate::command_context::CommandContext;
|
||||||
use crate::shared::print_json_pretty;
|
use crate::shared::print_json_pretty;
|
||||||
use crate::user_config::settings_layer_with_storage_dir;
|
use crate::user_config::settings_layer_with_storage_dir;
|
||||||
|
|
||||||
pub(crate) async fn execute(mut args: RunArgs, globals: &GlobalArgs) -> Result<()> {
|
pub(crate) async fn execute(
|
||||||
|
mut args: RunArgs,
|
||||||
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
|
) -> Result<()> {
|
||||||
let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr()));
|
let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr()));
|
||||||
let ctx = CommandContext::for_target(&args.target)?;
|
let ctx = CommandContext::for_target(&args.target, printer)?;
|
||||||
let cli = settings_layer_with_storage_dir(None)?;
|
let cli = settings_layer_with_storage_dir(None)?;
|
||||||
args.verbose = args.verbose || ctx.cli_settings().output.verbosity == OutputVerbosity::Verbose;
|
args.verbose = args.verbose || ctx.cli_settings().output.verbosity == OutputVerbosity::Verbose;
|
||||||
|
|
||||||
let quiet = args.detach;
|
let quiet = args.detach;
|
||||||
let prevent_idle_sleep = ctx.cli_settings().exec.prevent_idle_sleep;
|
let prevent_idle_sleep = ctx.cli_settings().exec.prevent_idle_sleep;
|
||||||
let created_run = Box::pin(super::create::create_run(&ctx, &args, cli, styles, quiet)).await?;
|
let created_run = Box::pin(super::create::create_run(
|
||||||
|
&ctx, &args, cli, styles, quiet, printer,
|
||||||
|
))
|
||||||
|
.await?;
|
||||||
|
|
||||||
#[cfg(feature = "sleep_inhibitor")]
|
#[cfg(feature = "sleep_inhibitor")]
|
||||||
let _sleep_guard = crate::sleep_inhibitor::guard(prevent_idle_sleep);
|
let _sleep_guard = crate::sleep_inhibitor::guard(prevent_idle_sleep);
|
||||||
|
|
@ -30,7 +38,7 @@ pub(crate) async fn execute(mut args: RunArgs, globals: &GlobalArgs) -> Result<(
|
||||||
if globals.json {
|
if globals.json {
|
||||||
print_json_pretty(&serde_json::json!({ "run_id": created_run.run_id }))?;
|
print_json_pretty(&serde_json::json!({ "run_id": created_run.run_id }))?;
|
||||||
} else {
|
} else {
|
||||||
println!("{}", created_run.run_id);
|
fabro_util::printout!(printer, "{}", created_run.run_id);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let exit_code = super::attach::attach_run_with_client(
|
let exit_code = super::attach::attach_run_with_client(
|
||||||
|
|
@ -39,6 +47,7 @@ pub(crate) async fn execute(mut args: RunArgs, globals: &GlobalArgs) -> Result<(
|
||||||
true,
|
true,
|
||||||
styles,
|
styles,
|
||||||
globals.json,
|
globals.json,
|
||||||
|
printer,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
if !globals.json {
|
if !globals.json {
|
||||||
|
|
@ -47,6 +56,7 @@ pub(crate) async fn execute(mut args: RunArgs, globals: &GlobalArgs) -> Result<(
|
||||||
&created_run.run_id,
|
&created_run.run_id,
|
||||||
created_run.local_run_dir.as_deref(),
|
created_run.local_run_dir.as_deref(),
|
||||||
styles,
|
styles,
|
||||||
|
printer,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use anyhow::{Context, Result, bail};
|
use anyhow::{Context, Result, bail};
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
use tracing::{debug, info};
|
use tracing::{debug, info};
|
||||||
|
|
||||||
|
|
@ -24,7 +25,7 @@ enum CopyDirection {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn cp_command(args: CpArgs, globals: &GlobalArgs) -> Result<()> {
|
pub(crate) async fn cp_command(args: CpArgs, globals: &GlobalArgs, printer: Printer) -> Result<()> {
|
||||||
let direction = parse_direction(&args.src, &args.dst)?;
|
let direction = parse_direction(&args.src, &args.dst)?;
|
||||||
|
|
||||||
match direction {
|
match direction {
|
||||||
|
|
@ -33,7 +34,8 @@ pub(crate) async fn cp_command(args: CpArgs, globals: &GlobalArgs) -> Result<()>
|
||||||
remote_path,
|
remote_path,
|
||||||
local_path,
|
local_path,
|
||||||
} => {
|
} => {
|
||||||
let (client, run_id) = resolve_client_and_run_id(&args.server, &run_prefix).await?;
|
let (client, run_id) =
|
||||||
|
resolve_client_and_run_id(&args.server, &run_prefix, printer).await?;
|
||||||
|
|
||||||
let file_count = if args.recursive {
|
let file_count = if args.recursive {
|
||||||
Some(download_recursive(&client, &run_id, &remote_path, &local_path).await?)
|
Some(download_recursive(&client, &run_id, &remote_path, &local_path).await?)
|
||||||
|
|
@ -63,7 +65,8 @@ pub(crate) async fn cp_command(args: CpArgs, globals: &GlobalArgs) -> Result<()>
|
||||||
run_prefix,
|
run_prefix,
|
||||||
remote_path,
|
remote_path,
|
||||||
} => {
|
} => {
|
||||||
let (client, run_id) = resolve_client_and_run_id(&args.server, &run_prefix).await?;
|
let (client, run_id) =
|
||||||
|
resolve_client_and_run_id(&args.server, &run_prefix, printer).await?;
|
||||||
|
|
||||||
let file_count = if args.recursive {
|
let file_count = if args.recursive {
|
||||||
Some(upload_recursive(&client, &run_id, &local_path, &remote_path).await?)
|
Some(upload_recursive(&client, &run_id, &local_path, &remote_path).await?)
|
||||||
|
|
@ -117,8 +120,9 @@ fn parse_direction(src: &str, dst: &str) -> Result<CopyDirection> {
|
||||||
async fn resolve_client_and_run_id(
|
async fn resolve_client_and_run_id(
|
||||||
server: &ServerTargetArgs,
|
server: &ServerTargetArgs,
|
||||||
run_prefix: &str,
|
run_prefix: &str,
|
||||||
|
printer: Printer,
|
||||||
) -> Result<(ServerStoreClient, fabro_types::RunId)> {
|
) -> Result<(ServerStoreClient, fabro_types::RunId)> {
|
||||||
let ctx = CommandContext::for_target(server)?;
|
let ctx = CommandContext::for_target(server, printer)?;
|
||||||
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
||||||
let run = lookup.resolve(run_prefix)?;
|
let run = lookup.resolve(run_prefix)?;
|
||||||
Ok((lookup.client().clone_for_reuse(), run.run_id()))
|
Ok((lookup.client().clone_for_reuse(), run.run_id()))
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ use fabro_config::load::load_settings_user;
|
||||||
use fabro_config::user::active_settings_path;
|
use fabro_config::user::active_settings_path;
|
||||||
use fabro_types::RunId;
|
use fabro_types::RunId;
|
||||||
use fabro_types::settings::SettingsLayer;
|
use fabro_types::settings::SettingsLayer;
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use fabro_util::terminal::Styles;
|
use fabro_util::terminal::Styles;
|
||||||
|
|
||||||
use super::output::{api_diagnostics_to_local, print_preflight_workflow_summary};
|
use super::output::{api_diagnostics_to_local, print_preflight_workflow_summary};
|
||||||
|
|
@ -29,6 +30,7 @@ pub(crate) async fn create_run(
|
||||||
_cli_defaults: SettingsLayer,
|
_cli_defaults: SettingsLayer,
|
||||||
styles: &Styles,
|
styles: &Styles,
|
||||||
quiet: bool,
|
quiet: bool,
|
||||||
|
printer: Printer,
|
||||||
) -> anyhow::Result<CreatedRun> {
|
) -> anyhow::Result<CreatedRun> {
|
||||||
let workflow_path = args
|
let workflow_path = args
|
||||||
.workflow
|
.workflow
|
||||||
|
|
@ -61,7 +63,12 @@ pub(crate) async fn create_run(
|
||||||
.iter()
|
.iter()
|
||||||
.any(|diagnostic| diagnostic.severity == fabro_validate::Severity::Error)
|
.any(|diagnostic| diagnostic.severity == fabro_validate::Severity::Error)
|
||||||
{
|
{
|
||||||
print_preflight_workflow_summary(&preflight.workflow, Some(&built.target_path), styles);
|
print_preflight_workflow_summary(
|
||||||
|
&preflight.workflow,
|
||||||
|
Some(&built.target_path),
|
||||||
|
styles,
|
||||||
|
printer,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use std::io::{self, IsTerminal, Write};
|
use std::io::{self, IsTerminal, Write};
|
||||||
|
|
||||||
use anyhow::{Context, Result, bail};
|
use anyhow::{Context, Result, bail};
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use tracing::{debug, info};
|
use tracing::{debug, info};
|
||||||
|
|
||||||
use crate::args::{DiffArgs, GlobalArgs};
|
use crate::args::{DiffArgs, GlobalArgs};
|
||||||
|
|
@ -9,9 +10,9 @@ use crate::server_client::RunProjection;
|
||||||
use crate::server_runs::ServerSummaryLookup;
|
use crate::server_runs::ServerSummaryLookup;
|
||||||
use crate::shared::print_json_pretty;
|
use crate::shared::print_json_pretty;
|
||||||
|
|
||||||
pub(crate) async fn run(args: DiffArgs, globals: &GlobalArgs) -> Result<()> {
|
pub(crate) async fn run(args: DiffArgs, globals: &GlobalArgs, printer: Printer) -> Result<()> {
|
||||||
info!(run_id = %args.run, "Showing diff");
|
info!(run_id = %args.run, "Showing diff");
|
||||||
let ctx = CommandContext::for_target(&args.server)?;
|
let ctx = CommandContext::for_target(&args.server, printer)?;
|
||||||
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
||||||
let run = lookup.resolve(&args.run)?;
|
let run = lookup.resolve(&args.run)?;
|
||||||
let run_id = run.run_id();
|
let run_id = run.run_id();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use fabro_checkpoint::git::Store;
|
use fabro_checkpoint::git::Store;
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use fabro_util::terminal::Styles;
|
use fabro_util::terminal::Styles;
|
||||||
use fabro_workflow::operations::{ForkRunInput, RewindTarget, build_timeline_or_rebuild, fork};
|
use fabro_workflow::operations::{ForkRunInput, RewindTarget, build_timeline_or_rebuild, fork};
|
||||||
use git2::Repository;
|
use git2::Repository;
|
||||||
|
|
@ -11,9 +12,14 @@ use crate::server_runs::ServerSummaryLookup;
|
||||||
use crate::shared::print_json_pretty;
|
use crate::shared::print_json_pretty;
|
||||||
use crate::shared::repo::ensure_matching_repo_origin;
|
use crate::shared::repo::ensure_matching_repo_origin;
|
||||||
|
|
||||||
pub(crate) async fn run(args: &ForkArgs, styles: &Styles, globals: &GlobalArgs) -> Result<()> {
|
pub(crate) async fn run(
|
||||||
|
args: &ForkArgs,
|
||||||
|
styles: &Styles,
|
||||||
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
|
) -> Result<()> {
|
||||||
let repo = Repository::discover(".").context("not in a git repository")?;
|
let repo = Repository::discover(".").context("not in a git repository")?;
|
||||||
let ctx = CommandContext::for_target(&args.server)?;
|
let ctx = CommandContext::for_target(&args.server, printer)?;
|
||||||
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
||||||
let run = lookup.resolve(&args.run_id)?;
|
let run = lookup.resolve(&args.run_id)?;
|
||||||
let run_id = run.run_id();
|
let run_id = run.run_id();
|
||||||
|
|
@ -31,7 +37,7 @@ pub(crate) async fn run(args: &ForkArgs, styles: &Styles, globals: &GlobalArgs)
|
||||||
print_json_pretty(&super::rewind::timeline_entries_json(&timeline))?;
|
print_json_pretty(&super::rewind::timeline_entries_json(&timeline))?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
super::rewind::print_timeline(&timeline, styles);
|
super::rewind::print_timeline(&timeline, styles, printer);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,12 +63,14 @@ pub(crate) async fn run(args: &ForkArgs, styles: &Styles, globals: &GlobalArgs)
|
||||||
"target": target,
|
"target": target,
|
||||||
}))?;
|
}))?;
|
||||||
} else {
|
} else {
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
"\nForked run {} -> {}",
|
"\nForked run {} -> {}",
|
||||||
&run_id_string[..8.min(run_id_string.len())],
|
&run_id_string[..8.min(run_id_string.len())],
|
||||||
&new_run_id_string[..8.min(new_run_id_string.len())]
|
&new_run_id_string[..8.min(new_run_id_string.len())]
|
||||||
);
|
);
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
"To resume: fabro resume {}",
|
"To resume: fabro resume {}",
|
||||||
&new_run_id_string[..8.min(new_run_id_string.len())]
|
&new_run_id_string[..8.min(new_run_id_string.len())]
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ use std::time::Duration;
|
||||||
use anyhow::{Context, Result, bail};
|
use anyhow::{Context, Result, bail};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use fabro_util::json::normalize_json_value;
|
use fabro_util::json::normalize_json_value;
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use fabro_util::redact::redact_jsonl_line;
|
use fabro_util::redact::redact_jsonl_line;
|
||||||
use fabro_util::terminal::Styles;
|
use fabro_util::terminal::Styles;
|
||||||
use tokio::time;
|
use tokio::time;
|
||||||
|
|
@ -18,8 +19,13 @@ use crate::shared::format_usd_micros;
|
||||||
|
|
||||||
const FOLLOW_TERMINAL_GRACE: Duration = Duration::from_millis(500);
|
const FOLLOW_TERMINAL_GRACE: Duration = Duration::from_millis(500);
|
||||||
|
|
||||||
pub(crate) async fn run(args: &LogsArgs, styles: &Styles, globals: &GlobalArgs) -> Result<()> {
|
pub(crate) async fn run(
|
||||||
let ctx = CommandContext::for_target(&args.server)?;
|
args: &LogsArgs,
|
||||||
|
styles: &Styles,
|
||||||
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
|
) -> Result<()> {
|
||||||
|
let ctx = CommandContext::for_target(&args.server, printer)?;
|
||||||
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
||||||
let run = lookup.resolve(&args.run)?;
|
let run = lookup.resolve(&args.run)?;
|
||||||
let client = lookup.client();
|
let client = lookup.client();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use fabro_util::terminal::Styles;
|
use fabro_util::terminal::Styles;
|
||||||
|
|
||||||
use crate::args::{AttachArgs, GlobalArgs, RunArgs, RunCommands, RunWorkerArgs, StartArgs};
|
use crate::args::{AttachArgs, GlobalArgs, RunArgs, RunCommands, RunWorkerArgs, StartArgs};
|
||||||
|
|
@ -31,27 +32,32 @@ fn apply_json_defaults(args: &mut RunArgs, globals: &GlobalArgs) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn dispatch(cmd: RunCommands, globals: &GlobalArgs) -> Result<()> {
|
pub(crate) async fn dispatch(
|
||||||
|
cmd: RunCommands,
|
||||||
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
|
) -> Result<()> {
|
||||||
match cmd {
|
match cmd {
|
||||||
RunCommands::Run(mut args) => {
|
RunCommands::Run(mut args) => {
|
||||||
apply_json_defaults(&mut args, globals);
|
apply_json_defaults(&mut args, globals);
|
||||||
Box::pin(command::execute(args, globals)).await
|
Box::pin(command::execute(args, globals, printer)).await
|
||||||
}
|
}
|
||||||
RunCommands::Create(mut args) => {
|
RunCommands::Create(mut args) => {
|
||||||
apply_json_defaults(&mut args, globals);
|
apply_json_defaults(&mut args, globals);
|
||||||
let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr()));
|
let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr()));
|
||||||
let cli = settings_layer_with_storage_dir(None)?;
|
let cli = settings_layer_with_storage_dir(None)?;
|
||||||
let ctx = CommandContext::for_target(&args.target)?;
|
let ctx = CommandContext::for_target(&args.target, printer)?;
|
||||||
let created_run = Box::pin(create::create_run(&ctx, &args, cli, styles, true)).await?;
|
let created_run =
|
||||||
|
Box::pin(create::create_run(&ctx, &args, cli, styles, true, printer)).await?;
|
||||||
if globals.json {
|
if globals.json {
|
||||||
print_json_pretty(&serde_json::json!({ "run_id": created_run.run_id }))?;
|
print_json_pretty(&serde_json::json!({ "run_id": created_run.run_id }))?;
|
||||||
} else {
|
} else {
|
||||||
println!("{}", created_run.run_id);
|
fabro_util::printout!(printer, "{}", created_run.run_id);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
RunCommands::Start(StartArgs { server, run }) => {
|
RunCommands::Start(StartArgs { server, run }) => {
|
||||||
let ctx = CommandContext::for_target(&server)?;
|
let ctx = CommandContext::for_target(&server, printer)?;
|
||||||
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
||||||
let run_info = lookup.resolve(&run)?;
|
let run_info = lookup.resolve(&run)?;
|
||||||
let run_id = run_info.run_id();
|
let run_id = run_info.run_id();
|
||||||
|
|
@ -63,7 +69,7 @@ pub(crate) async fn dispatch(cmd: RunCommands, globals: &GlobalArgs) -> Result<(
|
||||||
}
|
}
|
||||||
RunCommands::Attach(AttachArgs { server, run }) => {
|
RunCommands::Attach(AttachArgs { server, run }) => {
|
||||||
let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr()));
|
let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr()));
|
||||||
let ctx = CommandContext::for_target(&server)?;
|
let ctx = CommandContext::for_target(&server, printer)?;
|
||||||
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
||||||
let run_info = lookup.resolve(&run)?;
|
let run_info = lookup.resolve(&run)?;
|
||||||
let run_id = run_info.run_id();
|
let run_id = run_info.run_id();
|
||||||
|
|
@ -73,6 +79,7 @@ pub(crate) async fn dispatch(cmd: RunCommands, globals: &GlobalArgs) -> Result<(
|
||||||
false,
|
false,
|
||||||
styles,
|
styles,
|
||||||
globals.json,
|
globals.json,
|
||||||
|
printer,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
if exit_code != std::process::ExitCode::SUCCESS {
|
if exit_code != std::process::ExitCode::SUCCESS {
|
||||||
|
|
@ -87,31 +94,31 @@ pub(crate) async fn dispatch(cmd: RunCommands, globals: &GlobalArgs) -> Result<(
|
||||||
run_id,
|
run_id,
|
||||||
mode,
|
mode,
|
||||||
}) => runner::execute(run_id, server, artifact_upload_token, run_dir, mode).await,
|
}) => runner::execute(run_id, server, artifact_upload_token, run_dir, mode).await,
|
||||||
RunCommands::Diff(args) => diff::run(args, globals).await,
|
RunCommands::Diff(args) => diff::run(args, globals, printer).await,
|
||||||
RunCommands::Logs(args) => {
|
RunCommands::Logs(args) => {
|
||||||
let styles = Styles::detect_stdout();
|
let styles = Styles::detect_stdout();
|
||||||
logs::run(&args, &styles, globals).await
|
logs::run(&args, &styles, globals, printer).await
|
||||||
}
|
}
|
||||||
RunCommands::Resume(args) => {
|
RunCommands::Resume(args) => {
|
||||||
let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr()));
|
let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr()));
|
||||||
#[cfg(feature = "sleep_inhibitor")]
|
#[cfg(feature = "sleep_inhibitor")]
|
||||||
let _sleep_guard = {
|
let _sleep_guard = {
|
||||||
let ctx = CommandContext::for_target(&args.server)?;
|
let ctx = CommandContext::for_target(&args.server, printer)?;
|
||||||
crate::sleep_inhibitor::guard(ctx.cli_settings().exec.prevent_idle_sleep)
|
crate::sleep_inhibitor::guard(ctx.cli_settings().exec.prevent_idle_sleep)
|
||||||
};
|
};
|
||||||
resume::resume_command(args, styles, globals).await
|
resume::resume_command(args, styles, globals, printer).await
|
||||||
}
|
}
|
||||||
RunCommands::Rewind(args) => {
|
RunCommands::Rewind(args) => {
|
||||||
let styles = Styles::detect_stderr();
|
let styles = Styles::detect_stderr();
|
||||||
Box::pin(rewind::run(&args, &styles, globals)).await
|
Box::pin(rewind::run(&args, &styles, globals, printer)).await
|
||||||
}
|
}
|
||||||
RunCommands::Fork(args) => {
|
RunCommands::Fork(args) => {
|
||||||
let styles = Styles::detect_stderr();
|
let styles = Styles::detect_stderr();
|
||||||
Box::pin(fork::run(&args, &styles, globals)).await
|
Box::pin(fork::run(&args, &styles, globals, printer)).await
|
||||||
}
|
}
|
||||||
RunCommands::Wait(args) => {
|
RunCommands::Wait(args) => {
|
||||||
let styles = Styles::detect_stderr();
|
let styles = Styles::detect_stderr();
|
||||||
wait::run(&args, &styles, globals).await
|
wait::run(&args, &styles, globals, printer).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ use fabro_types::{
|
||||||
PullRequestRecord, RunBlobId, RunId, parse_blob_ref, parse_legacy_blob_file_ref,
|
PullRequestRecord, RunBlobId, RunId, parse_blob_ref, parse_legacy_blob_file_ref,
|
||||||
};
|
};
|
||||||
use fabro_util::check_report::{CheckDetail, CheckReport, CheckResult, CheckSection, CheckStatus};
|
use fabro_util::check_report::{CheckDetail, CheckReport, CheckResult, CheckSection, CheckStatus};
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use fabro_util::terminal::Styles;
|
use fabro_util::terminal::Styles;
|
||||||
use fabro_util::text::strip_goal_decoration;
|
use fabro_util::text::strip_goal_decoration;
|
||||||
use fabro_workflow::outcome::StageStatus;
|
use fabro_workflow::outcome::StageStatus;
|
||||||
|
|
@ -22,6 +23,7 @@ pub(crate) fn print_preflight_workflow_summary(
|
||||||
workflow: &types::PreflightWorkflowSummary,
|
workflow: &types::PreflightWorkflowSummary,
|
||||||
graph_path_override: Option<&Path>,
|
graph_path_override: Option<&Path>,
|
||||||
styles: &Styles,
|
styles: &Styles,
|
||||||
|
printer: Printer,
|
||||||
) {
|
) {
|
||||||
let graph_path = graph_path_override
|
let graph_path = graph_path_override
|
||||||
.map(relative_path)
|
.map(relative_path)
|
||||||
|
|
@ -42,7 +44,8 @@ pub(crate) fn print_preflight_workflow_summary(
|
||||||
.map(api_diagnostic_to_local)
|
.map(api_diagnostic_to_local)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
"{} {} {}",
|
"{} {} {}",
|
||||||
styles.bold.apply_to("Workflow:"),
|
styles.bold.apply_to("Workflow:"),
|
||||||
workflow.name,
|
workflow.name,
|
||||||
|
|
@ -51,7 +54,8 @@ pub(crate) fn print_preflight_workflow_summary(
|
||||||
workflow.nodes, workflow.edges
|
workflow.nodes, workflow.edges
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
"{} {}",
|
"{} {}",
|
||||||
styles.dim.apply_to("Graph:"),
|
styles.dim.apply_to("Graph:"),
|
||||||
styles.dim.apply_to(graph_path),
|
styles.dim.apply_to(graph_path),
|
||||||
|
|
@ -59,10 +63,10 @@ pub(crate) fn print_preflight_workflow_summary(
|
||||||
|
|
||||||
if !workflow.goal.is_empty() {
|
if !workflow.goal.is_empty() {
|
||||||
let stripped = strip_goal_decoration(&workflow.goal);
|
let stripped = strip_goal_decoration(&workflow.goal);
|
||||||
eprintln!("{} {stripped}\n", styles.bold.apply_to("Goal:"));
|
fabro_util::printerr!(printer, "{} {stripped}\n", styles.bold.apply_to("Goal:"));
|
||||||
}
|
}
|
||||||
|
|
||||||
print_diagnostics(&diagnostics, styles);
|
print_diagnostics(&diagnostics, styles, printer);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn api_diagnostic_to_local(diagnostic: &types::WorkflowDiagnostic) -> fabro_validate::Diagnostic {
|
fn api_diagnostic_to_local(diagnostic: &types::WorkflowDiagnostic) -> fabro_validate::Diagnostic {
|
||||||
|
|
@ -129,6 +133,7 @@ pub(crate) async fn print_run_summary_with_client(
|
||||||
run_id: &fabro_types::RunId,
|
run_id: &fabro_types::RunId,
|
||||||
local_run_dir: Option<&Path>,
|
local_run_dir: Option<&Path>,
|
||||||
styles: &Styles,
|
styles: &Styles,
|
||||||
|
printer: Printer,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let run_state = client.get_run_state(run_id).await?;
|
let run_state = client.get_run_state(run_id).await?;
|
||||||
let checkpoint = run_state.checkpoint.clone();
|
let checkpoint = run_state.checkpoint.clone();
|
||||||
|
|
@ -148,12 +153,13 @@ pub(crate) async fn print_run_summary_with_client(
|
||||||
None,
|
None,
|
||||||
pr_url.as_deref(),
|
pr_url.as_deref(),
|
||||||
styles,
|
styles,
|
||||||
|
printer,
|
||||||
);
|
);
|
||||||
let final_output =
|
let final_output =
|
||||||
resolve_final_output_with_client(client, run_id, checkpoint.as_ref()).await?;
|
resolve_final_output_with_client(client, run_id, checkpoint.as_ref()).await?;
|
||||||
print_final_output(final_output.as_deref(), styles);
|
print_final_output(final_output.as_deref(), styles, printer);
|
||||||
if local_run_dir.is_some() {
|
if local_run_dir.is_some() {
|
||||||
print_assets_with_client(client, run_id, styles).await?;
|
print_assets_with_client(client, run_id, styles, printer).await?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -165,18 +171,24 @@ pub(crate) fn print_run_conclusion(
|
||||||
pushed_branch: Option<&str>,
|
pushed_branch: Option<&str>,
|
||||||
pr_url: Option<&str>,
|
pr_url: Option<&str>,
|
||||||
styles: &Styles,
|
styles: &Styles,
|
||||||
|
printer: Printer,
|
||||||
) {
|
) {
|
||||||
let run_id = run_id.to_string();
|
let run_id = run_id.to_string();
|
||||||
eprintln!("\n{}", styles.bold.apply_to("=== Run Result ==="));
|
fabro_util::printerr!(printer, "\n{}", styles.bold.apply_to("=== Run Result ==="));
|
||||||
eprintln!("{}", styles.dim.apply_to(format!("Run: {run_id}")));
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
|
"{}",
|
||||||
|
styles.dim.apply_to(format!("Run: {run_id}"))
|
||||||
|
);
|
||||||
|
|
||||||
let status_str = conclusion.status.to_string().to_uppercase();
|
let status_str = conclusion.status.to_string().to_uppercase();
|
||||||
let status_color = match conclusion.status {
|
let status_color = match conclusion.status {
|
||||||
StageStatus::Success | StageStatus::PartialSuccess => &styles.bold_green,
|
StageStatus::Success | StageStatus::PartialSuccess => &styles.bold_green,
|
||||||
_ => &styles.bold_red,
|
_ => &styles.bold_red,
|
||||||
};
|
};
|
||||||
eprintln!("Status: {}", status_color.apply_to(&status_str));
|
fabro_util::printerr!(printer, "Status: {}", status_color.apply_to(&status_str));
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
"Duration: {}",
|
"Duration: {}",
|
||||||
HumanDuration(Duration::from_millis(conclusion.duration_ms))
|
HumanDuration(Duration::from_millis(conclusion.duration_ms))
|
||||||
);
|
);
|
||||||
|
|
@ -186,7 +198,8 @@ pub(crate) fn print_run_conclusion(
|
||||||
if total_tokens > 0 {
|
if total_tokens > 0 {
|
||||||
if let Some(total_usd_micros) = billing.total_usd_micros {
|
if let Some(total_usd_micros) = billing.total_usd_micros {
|
||||||
if total_usd_micros > 0 {
|
if total_usd_micros > 0 {
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
"{}",
|
"{}",
|
||||||
styles.dim.apply_to(format!(
|
styles.dim.apply_to(format!(
|
||||||
"Cost: {} ({} toks)",
|
"Cost: {} ({} toks)",
|
||||||
|
|
@ -196,7 +209,8 @@ pub(crate) fn print_run_conclusion(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
"{}",
|
"{}",
|
||||||
styles
|
styles
|
||||||
.dim
|
.dim
|
||||||
|
|
@ -204,7 +218,8 @@ pub(crate) fn print_run_conclusion(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if billing.cache_read_tokens > 0 || billing.cache_write_tokens > 0 {
|
if billing.cache_read_tokens > 0 || billing.cache_write_tokens > 0 {
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
"{}",
|
"{}",
|
||||||
styles.dim.apply_to(format!(
|
styles.dim.apply_to(format!(
|
||||||
"Cache: {} read, {} write",
|
"Cache: {} read, {} write",
|
||||||
|
|
@ -214,7 +229,8 @@ pub(crate) fn print_run_conclusion(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if billing.reasoning_tokens > 0 {
|
if billing.reasoning_tokens > 0 {
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
"{}",
|
"{}",
|
||||||
styles.dim.apply_to(format!(
|
styles.dim.apply_to(format!(
|
||||||
"Reasoning: {} tokens",
|
"Reasoning: {} tokens",
|
||||||
|
|
@ -223,7 +239,8 @@ pub(crate) fn print_run_conclusion(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if billing.total_usd_micros.is_none() {
|
} else if billing.total_usd_micros.is_none() {
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
"{}",
|
"{}",
|
||||||
styles
|
styles
|
||||||
.dim
|
.dim
|
||||||
|
|
@ -233,7 +250,8 @@ pub(crate) fn print_run_conclusion(
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(run_dir) = run_dir {
|
if let Some(run_dir) = run_dir {
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
"{}",
|
"{}",
|
||||||
styles
|
styles
|
||||||
.dim
|
.dim
|
||||||
|
|
@ -242,28 +260,32 @@ pub(crate) fn print_run_conclusion(
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref failure) = conclusion.failure_reason {
|
if let Some(ref failure) = conclusion.failure_reason {
|
||||||
eprintln!("Failure: {}", styles.red.apply_to(failure));
|
fabro_util::printerr!(printer, "Failure: {}", styles.red.apply_to(failure));
|
||||||
}
|
}
|
||||||
|
|
||||||
if pushed_branch.is_some() || pr_url.is_some() {
|
if pushed_branch.is_some() || pr_url.is_some() {
|
||||||
eprintln!();
|
fabro_util::printerr!(printer, "");
|
||||||
if let Some(branch) = pushed_branch {
|
if let Some(branch) = pushed_branch {
|
||||||
eprintln!("{} {branch}", styles.bold.apply_to("Pushed branch:"));
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
|
"{} {branch}",
|
||||||
|
styles.bold.apply_to("Pushed branch:")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if let Some(url) = pr_url {
|
if let Some(url) = pr_url {
|
||||||
eprintln!("{} {url}", styles.bold.apply_to("Pull request:"));
|
fabro_util::printerr!(printer, "{} {url}", styles.bold.apply_to("Pull request:"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn print_final_output(output: Option<&str>, styles: &Styles) {
|
pub(crate) fn print_final_output(output: Option<&str>, styles: &Styles, printer: Printer) {
|
||||||
let Some(output) = output else {
|
let Some(output) = output else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let text = output.trim();
|
let text = output.trim();
|
||||||
if !text.is_empty() {
|
if !text.is_empty() {
|
||||||
eprintln!("\n{}", styles.bold.apply_to("=== Output ==="));
|
fabro_util::printerr!(printer, "\n{}", styles.bold.apply_to("=== Output ==="));
|
||||||
eprintln!("{}", styles.render_markdown(text));
|
fabro_util::printerr!(printer, "{}", styles.render_markdown(text));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -335,6 +357,7 @@ async fn print_assets_with_client(
|
||||||
client: &server_client::ServerStoreClient,
|
client: &server_client::ServerStoreClient,
|
||||||
run_id: &RunId,
|
run_id: &RunId,
|
||||||
styles: &Styles,
|
styles: &Styles,
|
||||||
|
printer: Printer,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let entries = list_artifact_display_entries_with_client(client, run_id).await?;
|
let entries = list_artifact_display_entries_with_client(client, run_id).await?;
|
||||||
if entries.is_empty() {
|
if entries.is_empty() {
|
||||||
|
|
@ -354,13 +377,22 @@ async fn print_assets_with_client(
|
||||||
.unwrap_or(5)
|
.unwrap_or(5)
|
||||||
.max(5);
|
.max(5);
|
||||||
|
|
||||||
eprintln!("\n{}", styles.bold.apply_to("=== Artifacts ==="));
|
fabro_util::printerr!(printer, "\n{}", styles.bold.apply_to("=== Artifacts ==="));
|
||||||
eprintln!("{:<node_width$} {:>retry_width$} PATH", "NODE", "RETRY");
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
|
"{:<node_width$} {:>retry_width$} PATH",
|
||||||
|
"NODE",
|
||||||
|
"RETRY"
|
||||||
|
);
|
||||||
for (node_slug, retry, relative_path) in &entries {
|
for (node_slug, retry, relative_path) in &entries {
|
||||||
eprintln!("{node_slug:<node_width$} {retry:>retry_width$} {relative_path}");
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
|
"{node_slug:<node_width$} {retry:>retry_width$} {relative_path}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
eprintln!();
|
fabro_util::printerr!(printer, "");
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
"{}",
|
"{}",
|
||||||
styles.dim.apply_to(format!(
|
styles.dim.apply_to(format!(
|
||||||
"Copy with: fabro artifact cp {run_id}:<path> <dest> --node <node_slug> --retry <retry>"
|
"Copy with: fabro artifact cp {run_id}:<path> <dest> --node <node_slug> --retry <retry>"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
use crate::args::{GlobalArgs, PreviewArgs};
|
use crate::args::{GlobalArgs, PreviewArgs};
|
||||||
|
|
@ -6,8 +7,8 @@ use crate::command_context::CommandContext;
|
||||||
use crate::server_runs::ServerSummaryLookup;
|
use crate::server_runs::ServerSummaryLookup;
|
||||||
use crate::shared::print_json_pretty;
|
use crate::shared::print_json_pretty;
|
||||||
|
|
||||||
pub(crate) async fn run(args: PreviewArgs, globals: &GlobalArgs) -> Result<()> {
|
pub(crate) async fn run(args: PreviewArgs, globals: &GlobalArgs, printer: Printer) -> Result<()> {
|
||||||
let ctx = CommandContext::for_target(&args.server)?;
|
let ctx = CommandContext::for_target(&args.server, printer)?;
|
||||||
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
||||||
let run = lookup.resolve(&args.run)?;
|
let run = lookup.resolve(&args.run)?;
|
||||||
let run_id = run.run_id();
|
let run_id = run.run_id();
|
||||||
|
|
@ -35,9 +36,19 @@ pub(crate) async fn run(args: PreviewArgs, globals: &GlobalArgs) -> Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let Some(token) = response.token.as_deref() {
|
} else if let Some(token) = response.token.as_deref() {
|
||||||
print!("{}", format_standard_output(&response.url, token));
|
{
|
||||||
|
use std::fmt::Write as _;
|
||||||
|
let _ = write!(
|
||||||
|
printer.stdout(),
|
||||||
|
"{}",
|
||||||
|
format_standard_output(&response.url, token)
|
||||||
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
print!("{}", format_signed_output(&response.url));
|
{
|
||||||
|
use std::fmt::Write as _;
|
||||||
|
let _ = write!(printer.stdout(), "{}", format_signed_output(&response.url));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if args.open && !globals.json {
|
if args.open && !globals.json {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use fabro_util::terminal::Styles;
|
use fabro_util::terminal::Styles;
|
||||||
|
|
||||||
use crate::args::{GlobalArgs, ResumeArgs};
|
use crate::args::{GlobalArgs, ResumeArgs};
|
||||||
|
|
@ -14,8 +15,9 @@ pub(crate) async fn resume_command(
|
||||||
args: ResumeArgs,
|
args: ResumeArgs,
|
||||||
styles: &'static Styles,
|
styles: &'static Styles,
|
||||||
globals: &GlobalArgs,
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let ctx = CommandContext::for_target(&args.server)?;
|
let ctx = CommandContext::for_target(&args.server, printer)?;
|
||||||
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
||||||
let run = lookup.resolve(&args.run)?;
|
let run = lookup.resolve(&args.run)?;
|
||||||
let run_id = run.run_id();
|
let run_id = run.run_id();
|
||||||
|
|
@ -26,7 +28,7 @@ pub(crate) async fn resume_command(
|
||||||
if globals.json {
|
if globals.json {
|
||||||
print_json_pretty(&serde_json::json!({ "run_id": run_id }))?;
|
print_json_pretty(&serde_json::json!({ "run_id": run_id }))?;
|
||||||
} else {
|
} else {
|
||||||
println!("{run_id}");
|
fabro_util::printout!(printer, "{run_id}");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let exit_code = super::attach::attach_run_with_client(
|
let exit_code = super::attach::attach_run_with_client(
|
||||||
|
|
@ -35,11 +37,18 @@ pub(crate) async fn resume_command(
|
||||||
true,
|
true,
|
||||||
styles,
|
styles,
|
||||||
globals.json,
|
globals.json,
|
||||||
|
printer,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
if !globals.json {
|
if !globals.json {
|
||||||
super::output::print_run_summary_with_client(lookup.client(), &run_id, None, styles)
|
super::output::print_run_summary_with_client(
|
||||||
.await?;
|
lookup.client(),
|
||||||
|
&run_id,
|
||||||
|
None,
|
||||||
|
styles,
|
||||||
|
printer,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
if exit_code != std::process::ExitCode::SUCCESS {
|
if exit_code != std::process::ExitCode::SUCCESS {
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ use cli_table::{Cell, CellStruct, Color, Style, Table};
|
||||||
use fabro_checkpoint::git::Store;
|
use fabro_checkpoint::git::Store;
|
||||||
use fabro_types::run_event::{CheckpointCompletedProps, RunRewoundProps, RunSubmittedProps};
|
use fabro_types::run_event::{CheckpointCompletedProps, RunRewoundProps, RunSubmittedProps};
|
||||||
use fabro_types::{EventBody, RunEvent};
|
use fabro_types::{EventBody, RunEvent};
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use fabro_util::terminal::Styles;
|
use fabro_util::terminal::Styles;
|
||||||
use fabro_workflow::git::MetadataStore;
|
use fabro_workflow::git::MetadataStore;
|
||||||
use fabro_workflow::operations::{
|
use fabro_workflow::operations::{
|
||||||
|
|
@ -28,9 +29,14 @@ pub(crate) struct TimelineEntryJson {
|
||||||
run_commit_sha: Option<String>,
|
run_commit_sha: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn run(args: &RewindArgs, styles: &Styles, globals: &GlobalArgs) -> Result<()> {
|
pub(crate) async fn run(
|
||||||
|
args: &RewindArgs,
|
||||||
|
styles: &Styles,
|
||||||
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
|
) -> Result<()> {
|
||||||
let repo = Repository::discover(".").context("not in a git repository")?;
|
let repo = Repository::discover(".").context("not in a git repository")?;
|
||||||
let ctx = CommandContext::for_target(&args.server)?;
|
let ctx = CommandContext::for_target(&args.server, printer)?;
|
||||||
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
||||||
let run = lookup.resolve(&args.run_id)?;
|
let run = lookup.resolve(&args.run_id)?;
|
||||||
let run_id = run.run_id();
|
let run_id = run.run_id();
|
||||||
|
|
@ -48,7 +54,7 @@ pub(crate) async fn run(args: &RewindArgs, styles: &Styles, globals: &GlobalArgs
|
||||||
print_json_pretty(&timeline_entries_json(&timeline))?;
|
print_json_pretty(&timeline_entries_json(&timeline))?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
print_timeline(&timeline, styles);
|
print_timeline(&timeline, styles, printer);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,7 +76,8 @@ pub(crate) async fn run(args: &RewindArgs, styles: &Styles, globals: &GlobalArgs
|
||||||
"target": args.target.as_deref().unwrap(),
|
"target": args.target.as_deref().unwrap(),
|
||||||
}))?;
|
}))?;
|
||||||
} else {
|
} else {
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
"\nTo resume: fabro resume {}",
|
"\nTo resume: fabro resume {}",
|
||||||
&run_id_string[..8.min(run_id_string.len())]
|
&run_id_string[..8.min(run_id_string.len())]
|
||||||
);
|
);
|
||||||
|
|
@ -205,9 +212,9 @@ fn run_event(run_id: fabro_types::RunId, node_id: Option<String>, body: EventBod
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn print_timeline(timeline: &RunTimeline, styles: &Styles) {
|
pub(crate) fn print_timeline(timeline: &RunTimeline, styles: &Styles, printer: Printer) {
|
||||||
if timeline.entries.is_empty() {
|
if timeline.entries.is_empty() {
|
||||||
eprintln!("No checkpoints found.");
|
fabro_util::printerr!(printer, "No checkpoints found.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use anyhow::{Result, bail};
|
use anyhow::{Result, bail};
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
use crate::args::{GlobalArgs, SshArgs};
|
use crate::args::{GlobalArgs, SshArgs};
|
||||||
|
|
@ -6,12 +7,12 @@ use crate::command_context::CommandContext;
|
||||||
use crate::server_runs::ServerSummaryLookup;
|
use crate::server_runs::ServerSummaryLookup;
|
||||||
use crate::shared::print_json_pretty;
|
use crate::shared::print_json_pretty;
|
||||||
|
|
||||||
pub(crate) async fn run(args: SshArgs, globals: &GlobalArgs) -> Result<()> {
|
pub(crate) async fn run(args: SshArgs, globals: &GlobalArgs, printer: Printer) -> Result<()> {
|
||||||
if globals.json && !args.print {
|
if globals.json && !args.print {
|
||||||
globals.require_no_json()?;
|
globals.require_no_json()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let ctx = CommandContext::for_target(&args.server)?;
|
let ctx = CommandContext::for_target(&args.server, printer)?;
|
||||||
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
||||||
let run = lookup.resolve(&args.run)?;
|
let run = lookup.resolve(&args.run)?;
|
||||||
let run_id = run.run_id();
|
let run_id = run.run_id();
|
||||||
|
|
@ -26,7 +27,10 @@ pub(crate) async fn run(args: SshArgs, globals: &GlobalArgs) -> Result<()> {
|
||||||
if globals.json {
|
if globals.json {
|
||||||
print_json_pretty(&serde_json::json!({ "command": ssh.command }))?;
|
print_json_pretty(&serde_json::json!({ "command": ssh.command }))?;
|
||||||
} else {
|
} else {
|
||||||
print!("{}", format_output(&ssh.command));
|
{
|
||||||
|
use std::fmt::Write as _;
|
||||||
|
let _ = write!(printer.stdout(), "{}", format_output(&ssh.command));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
exec_ssh(&ssh.command)?;
|
exec_ssh(&ssh.command)?;
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ use std::io::Write;
|
||||||
|
|
||||||
use anyhow::{Result, bail};
|
use anyhow::{Result, bail};
|
||||||
use fabro_types::RunId;
|
use fabro_types::RunId;
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use fabro_util::terminal::Styles;
|
use fabro_util::terminal::Styles;
|
||||||
use fabro_workflow::records::Conclusion;
|
use fabro_workflow::records::Conclusion;
|
||||||
use fabro_workflow::run_status::RunStatus;
|
use fabro_workflow::run_status::RunStatus;
|
||||||
|
|
@ -17,8 +18,13 @@ const WAIT_STARTUP_GRACE: std::time::Duration = std::time::Duration::from_millis
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
const WAIT_STARTUP_GRACE: std::time::Duration = std::time::Duration::from_secs(3);
|
const WAIT_STARTUP_GRACE: std::time::Duration = std::time::Duration::from_secs(3);
|
||||||
|
|
||||||
pub(crate) async fn run(args: &WaitArgs, styles: &Styles, globals: &GlobalArgs) -> Result<()> {
|
pub(crate) async fn run(
|
||||||
let ctx = CommandContext::for_target(&args.server)?;
|
args: &WaitArgs,
|
||||||
|
styles: &Styles,
|
||||||
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
|
) -> Result<()> {
|
||||||
|
let ctx = CommandContext::for_target(&args.server, printer)?;
|
||||||
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
||||||
let run_info = lookup.resolve(&args.run)?;
|
let run_info = lookup.resolve(&args.run)?;
|
||||||
let client = lookup.client();
|
let client = lookup.client();
|
||||||
|
|
@ -73,7 +79,7 @@ pub(crate) async fn run(args: &WaitArgs, styles: &Styles, globals: &GlobalArgs)
|
||||||
serde_json::to_writer_pretty(&mut out, &json_value)?;
|
serde_json::to_writer_pretty(&mut out, &json_value)?;
|
||||||
writeln!(out)?;
|
writeln!(out)?;
|
||||||
} else {
|
} else {
|
||||||
print_human_output(final_status, &run_id, conclusion.as_ref(), styles);
|
print_human_output(final_status, &run_id, conclusion.as_ref(), styles, printer);
|
||||||
}
|
}
|
||||||
|
|
||||||
if final_status == RunStatus::Succeeded {
|
if final_status == RunStatus::Succeeded {
|
||||||
|
|
@ -110,6 +116,7 @@ fn print_human_output(
|
||||||
run_id: &RunId,
|
run_id: &RunId,
|
||||||
conclusion: Option<&Conclusion>,
|
conclusion: Option<&Conclusion>,
|
||||||
styles: &Styles,
|
styles: &Styles,
|
||||||
|
printer: Printer,
|
||||||
) {
|
) {
|
||||||
let (style, label) = match status {
|
let (style, label) = match status {
|
||||||
RunStatus::Succeeded => (&styles.bold_green, "Succeeded"),
|
RunStatus::Succeeded => (&styles.bold_green, "Succeeded"),
|
||||||
|
|
@ -134,7 +141,8 @@ fn print_human_output(
|
||||||
None => String::new(),
|
None => String::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
"{} {}{details}",
|
"{} {}{details}",
|
||||||
status_display,
|
status_display,
|
||||||
styles.dim.apply_to(run_id),
|
styles.dim.apply_to(run_id),
|
||||||
|
|
@ -239,13 +247,25 @@ mod tests {
|
||||||
total_retries: 0,
|
total_retries: 0,
|
||||||
};
|
};
|
||||||
// Just verify no panic; actual stderr output is hard to capture
|
// Just verify no panic; actual stderr output is hard to capture
|
||||||
print_human_output(RunStatus::Succeeded, &run_id, Some(&conclusion), &styles);
|
print_human_output(
|
||||||
|
RunStatus::Succeeded,
|
||||||
|
&run_id,
|
||||||
|
Some(&conclusion),
|
||||||
|
&styles,
|
||||||
|
Printer::Default,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn human_output_failed_no_conclusion() {
|
fn human_output_failed_no_conclusion() {
|
||||||
let styles = no_color_styles();
|
let styles = no_color_styles();
|
||||||
print_human_output(RunStatus::Failed, &fixtures::RUN_6, None, &styles);
|
print_human_output(
|
||||||
|
RunStatus::Failed,
|
||||||
|
&fixtures::RUN_6,
|
||||||
|
None,
|
||||||
|
&styles,
|
||||||
|
Printer::Default,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use fabro_workflow::run_status::RunStatus;
|
use fabro_workflow::run_status::RunStatus;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
|
|
@ -18,15 +19,15 @@ pub(crate) struct InspectOutput {
|
||||||
pub sandbox: Option<serde_json::Value>,
|
pub sandbox: Option<serde_json::Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn run(args: &InspectArgs, _globals: &GlobalArgs) -> Result<()> {
|
pub(crate) async fn run(args: &InspectArgs, _globals: &GlobalArgs, printer: Printer) -> Result<()> {
|
||||||
let ctx = CommandContext::for_target(&args.server)?;
|
let ctx = CommandContext::for_target(&args.server, printer)?;
|
||||||
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
||||||
let run = lookup.resolve(&args.run)?;
|
let run = lookup.resolve(&args.run)?;
|
||||||
let run_id = run.run_id();
|
let run_id = run.run_id();
|
||||||
let state = lookup.client().get_run_state(&run_id).await?;
|
let state = lookup.client().get_run_state(&run_id).await?;
|
||||||
let output = inspect_run_state(&run, state);
|
let output = inspect_run_state(&run, state);
|
||||||
let json = serde_json::to_string_pretty(&[output])?;
|
let json = serde_json::to_string_pretty(&[output])?;
|
||||||
println!("{json}");
|
fabro_util::printout!(printer, "{json}");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ use anyhow::Result;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use cli_table::format::{Border, Separator};
|
use cli_table::format::{Border, Separator};
|
||||||
use cli_table::{Cell, CellStruct, Color, Style, Table};
|
use cli_table::{Cell, CellStruct, Color, Style, Table};
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use fabro_util::terminal::Styles;
|
use fabro_util::terminal::Styles;
|
||||||
use fabro_util::text::strip_goal_decoration;
|
use fabro_util::text::strip_goal_decoration;
|
||||||
use fabro_workflow::run_status::RunStatus;
|
use fabro_workflow::run_status::RunStatus;
|
||||||
|
|
@ -14,13 +15,13 @@ use crate::command_context::CommandContext;
|
||||||
use crate::server_runs::{ServerSummaryLookup, filter_server_runs};
|
use crate::server_runs::{ServerSummaryLookup, filter_server_runs};
|
||||||
use crate::shared::{color_if, format_duration_ms, tilde_path};
|
use crate::shared::{color_if, format_duration_ms, tilde_path};
|
||||||
|
|
||||||
#[allow(clippy::print_stdout)]
|
|
||||||
pub(crate) async fn list_command(
|
pub(crate) async fn list_command(
|
||||||
args: &RunsListArgs,
|
args: &RunsListArgs,
|
||||||
styles: &Styles,
|
styles: &Styles,
|
||||||
globals: &GlobalArgs,
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let ctx = CommandContext::for_target(&args.server)?;
|
let ctx = CommandContext::for_target(&args.server, printer)?;
|
||||||
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
||||||
let label_filters = parse_label_filters(&args.filter.label);
|
let label_filters = parse_label_filters(&args.filter.label);
|
||||||
let filtered = filter_server_runs(
|
let filtered = filter_server_runs(
|
||||||
|
|
@ -50,13 +51,13 @@ pub(crate) async fn list_command(
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
println!("{}", serde_json::to_string_pretty(&json_rows)?);
|
fabro_util::printout!(printer, "{}", serde_json::to_string_pretty(&json_rows)?);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
if args.quiet {
|
if args.quiet {
|
||||||
for run in &filtered {
|
for run in &filtered {
|
||||||
println!("{}", run.run_id());
|
fabro_util::printout!(printer, "{}", run.run_id());
|
||||||
}
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
@ -130,9 +131,9 @@ pub(crate) async fn list_command(
|
||||||
.color_choice(color_choice)
|
.color_choice(color_choice)
|
||||||
.border(Border::builder().build())
|
.border(Border::builder().build())
|
||||||
.separator(Separator::builder().build());
|
.separator(Separator::builder().build());
|
||||||
println!("{}", table.display()?);
|
fabro_util::printout!(printer, "{}", table.display()?);
|
||||||
|
|
||||||
eprintln!("\n{} run(s) listed.", display_runs.len());
|
fabro_util::printerr!(printer, "\n{} run(s) listed.", display_runs.len());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use fabro_util::terminal::Styles;
|
use fabro_util::terminal::Styles;
|
||||||
|
|
||||||
use crate::args::{GlobalArgs, RunsCommands};
|
use crate::args::{GlobalArgs, RunsCommands};
|
||||||
|
|
@ -7,14 +8,18 @@ pub(crate) mod inspect;
|
||||||
pub(crate) mod list;
|
pub(crate) mod list;
|
||||||
pub(crate) mod rm;
|
pub(crate) mod rm;
|
||||||
|
|
||||||
pub(crate) async fn dispatch(cmd: RunsCommands, globals: &GlobalArgs) -> Result<()> {
|
pub(crate) async fn dispatch(
|
||||||
|
cmd: RunsCommands,
|
||||||
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
|
) -> Result<()> {
|
||||||
match cmd {
|
match cmd {
|
||||||
RunsCommands::Ps(args) => {
|
RunsCommands::Ps(args) => {
|
||||||
let styles = Styles::detect_stdout();
|
let styles = Styles::detect_stdout();
|
||||||
list::list_command(&args, &styles, globals).await
|
list::list_command(&args, &styles, globals, printer).await
|
||||||
}
|
}
|
||||||
RunsCommands::Rm(args) => rm::remove_command(&args, globals).await,
|
RunsCommands::Rm(args) => rm::remove_command(&args, globals, printer).await,
|
||||||
RunsCommands::Inspect(args) => inspect::run(&args, globals).await,
|
RunsCommands::Inspect(args) => inspect::run(&args, globals, printer).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use anyhow::{Context, Result, bail};
|
use anyhow::{Context, Result, bail};
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
|
|
||||||
use super::short_run_id;
|
use super::short_run_id;
|
||||||
use crate::args::{GlobalArgs, RunsRemoveArgs};
|
use crate::args::{GlobalArgs, RunsRemoveArgs};
|
||||||
|
|
@ -9,10 +10,14 @@ use crate::server_runs::{
|
||||||
};
|
};
|
||||||
use crate::shared::print_json_pretty;
|
use crate::shared::print_json_pretty;
|
||||||
|
|
||||||
pub(crate) async fn remove_command(args: &RunsRemoveArgs, globals: &GlobalArgs) -> Result<()> {
|
pub(crate) async fn remove_command(
|
||||||
let ctx = CommandContext::for_target(&args.server)?;
|
args: &RunsRemoveArgs,
|
||||||
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
|
) -> Result<()> {
|
||||||
|
let ctx = CommandContext::for_target(&args.server, printer)?;
|
||||||
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
|
||||||
remove_from(args, lookup.client(), lookup.runs(), globals).await
|
remove_from(args, lookup.client(), lookup.runs(), globals, printer).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn remove_from(
|
async fn remove_from(
|
||||||
|
|
@ -20,6 +25,7 @@ async fn remove_from(
|
||||||
client: &server_client::ServerStoreClient,
|
client: &server_client::ServerStoreClient,
|
||||||
runs: &[ServerRunSummaryInfo],
|
runs: &[ServerRunSummaryInfo],
|
||||||
globals: &GlobalArgs,
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut had_errors = false;
|
let mut had_errors = false;
|
||||||
let mut removed = Vec::new();
|
let mut removed = Vec::new();
|
||||||
|
|
@ -30,7 +36,7 @@ async fn remove_from(
|
||||||
Ok(run) => run,
|
Ok(run) => run,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
if !globals.json {
|
if !globals.json {
|
||||||
eprintln!("error: {identifier}: {err}");
|
fabro_util::printerr!(printer, "error: {identifier}: {err}");
|
||||||
}
|
}
|
||||||
errors.push(serde_json::json!({
|
errors.push(serde_json::json!({
|
||||||
"identifier": identifier,
|
"identifier": identifier,
|
||||||
|
|
@ -49,7 +55,7 @@ async fn remove_from(
|
||||||
run.status()
|
run.status()
|
||||||
);
|
);
|
||||||
if !globals.json {
|
if !globals.json {
|
||||||
eprintln!("{error}");
|
fabro_util::printerr!(printer, "{error}");
|
||||||
}
|
}
|
||||||
errors.push(serde_json::json!({
|
errors.push(serde_json::json!({
|
||||||
"identifier": identifier,
|
"identifier": identifier,
|
||||||
|
|
@ -62,7 +68,7 @@ async fn remove_from(
|
||||||
let run_id = run.run_id().to_string();
|
let run_id = run.run_id().to_string();
|
||||||
if let Err(err) = delete_server_run(client, &run).await {
|
if let Err(err) = delete_server_run(client, &run).await {
|
||||||
if !globals.json {
|
if !globals.json {
|
||||||
eprintln!("error: {identifier}: {err}");
|
fabro_util::printerr!(printer, "error: {identifier}: {err}");
|
||||||
}
|
}
|
||||||
errors.push(serde_json::json!({
|
errors.push(serde_json::json!({
|
||||||
"identifier": identifier,
|
"identifier": identifier,
|
||||||
|
|
@ -73,7 +79,7 @@ async fn remove_from(
|
||||||
}
|
}
|
||||||
removed.push(run_id.clone());
|
removed.push(run_id.clone());
|
||||||
if !globals.json {
|
if !globals.json {
|
||||||
eprintln!("{}", short_run_id(&run_id));
|
fabro_util::printerr!(printer, "{}", short_run_id(&run_id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,16 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
|
|
||||||
use crate::args::{GlobalArgs, SandboxCommand};
|
use crate::args::{GlobalArgs, SandboxCommand};
|
||||||
|
|
||||||
pub(crate) async fn dispatch(command: SandboxCommand, globals: &GlobalArgs) -> Result<()> {
|
pub(crate) async fn dispatch(
|
||||||
|
command: SandboxCommand,
|
||||||
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
|
) -> Result<()> {
|
||||||
match command {
|
match command {
|
||||||
SandboxCommand::Cp(args) => super::run::cp::cp_command(args, globals).await,
|
SandboxCommand::Cp(args) => super::run::cp::cp_command(args, globals, printer).await,
|
||||||
SandboxCommand::Preview(args) => super::run::preview::run(args, globals).await,
|
SandboxCommand::Preview(args) => super::run::preview::run(args, globals, printer).await,
|
||||||
SandboxCommand::Ssh(args) => super::run::ssh::run(args, globals).await,
|
SandboxCommand::Ssh(args) => super::run::ssh::run(args, globals, printer).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use fabro_api::Client;
|
use fabro_api::Client;
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
|
|
||||||
use crate::args::{GlobalArgs, SecretListArgs};
|
use crate::args::{GlobalArgs, SecretListArgs};
|
||||||
use crate::server_client;
|
use crate::server_client;
|
||||||
|
|
@ -9,6 +10,7 @@ pub(super) async fn list_command(
|
||||||
client: &Client,
|
client: &Client,
|
||||||
args: &SecretListArgs,
|
args: &SecretListArgs,
|
||||||
globals: &GlobalArgs,
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let response = client
|
let response = client
|
||||||
.list_secrets()
|
.list_secrets()
|
||||||
|
|
@ -22,7 +24,7 @@ pub(super) async fn list_command(
|
||||||
}
|
}
|
||||||
let _ = args;
|
let _ = args;
|
||||||
for secret in secrets {
|
for secret in secrets {
|
||||||
println!("{}\t{}", secret.name, secret.updated_at);
|
fabro_util::printout!(printer, "{}\t{}", secret.name, secret.updated_at);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,23 @@ mod rm;
|
||||||
mod set;
|
mod set;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
|
|
||||||
use crate::args::{GlobalArgs, SecretCommand, SecretNamespace};
|
use crate::args::{GlobalArgs, SecretCommand, SecretNamespace};
|
||||||
use crate::command_context::CommandContext;
|
use crate::command_context::CommandContext;
|
||||||
|
|
||||||
pub(crate) async fn dispatch(ns: SecretNamespace, globals: &GlobalArgs) -> Result<()> {
|
pub(crate) async fn dispatch(
|
||||||
let ctx = CommandContext::for_target(&ns.target)?;
|
ns: SecretNamespace,
|
||||||
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
|
) -> Result<()> {
|
||||||
|
let ctx = CommandContext::for_target(&ns.target, printer)?;
|
||||||
let server = ctx.server().await?;
|
let server = ctx.server().await?;
|
||||||
match ns.command {
|
match ns.command {
|
||||||
SecretCommand::List(args) => list::list_command(server.api(), &args, globals).await,
|
SecretCommand::List(args) => {
|
||||||
SecretCommand::Rm(args) => rm::rm_command(server.api(), &args, globals).await,
|
list::list_command(server.api(), &args, globals, printer).await
|
||||||
SecretCommand::Set(args) => set::set_command(server.api(), &args, globals).await,
|
}
|
||||||
|
SecretCommand::Rm(args) => rm::rm_command(server.api(), &args, globals, printer).await,
|
||||||
|
SecretCommand::Set(args) => set::set_command(server.api(), &args, globals, printer).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use fabro_api::Client;
|
use fabro_api::Client;
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
|
|
||||||
use crate::args::{GlobalArgs, SecretRmArgs};
|
use crate::args::{GlobalArgs, SecretRmArgs};
|
||||||
use crate::server_client;
|
use crate::server_client;
|
||||||
|
|
@ -9,6 +10,7 @@ pub(super) async fn rm_command(
|
||||||
client: &Client,
|
client: &Client,
|
||||||
args: &SecretRmArgs,
|
args: &SecretRmArgs,
|
||||||
globals: &GlobalArgs,
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
client
|
client
|
||||||
.delete_secret()
|
.delete_secret()
|
||||||
|
|
@ -19,7 +21,7 @@ pub(super) async fn rm_command(
|
||||||
if globals.json {
|
if globals.json {
|
||||||
print_json_pretty(&serde_json::json!({ "key": args.key }))?;
|
print_json_pretty(&serde_json::json!({ "key": args.key }))?;
|
||||||
} else {
|
} else {
|
||||||
eprintln!("Removed {}", args.key);
|
fabro_util::printerr!(printer, "Removed {}", args.key);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use fabro_api::{Client, types};
|
use fabro_api::{Client, types};
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
|
|
||||||
use crate::args::{GlobalArgs, SecretSetArgs};
|
use crate::args::{GlobalArgs, SecretSetArgs};
|
||||||
use crate::server_client;
|
use crate::server_client;
|
||||||
|
|
@ -9,6 +10,7 @@ pub(super) async fn set_command(
|
||||||
client: &Client,
|
client: &Client,
|
||||||
args: &SecretSetArgs,
|
args: &SecretSetArgs,
|
||||||
globals: &GlobalArgs,
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let meta = client
|
let meta = client
|
||||||
.set_secret()
|
.set_secret()
|
||||||
|
|
@ -23,7 +25,7 @@ pub(super) async fn set_command(
|
||||||
if globals.json {
|
if globals.json {
|
||||||
print_json_pretty(&meta)?;
|
print_json_pretty(&meta)?;
|
||||||
} else {
|
} else {
|
||||||
eprintln!("Set {}", meta.name);
|
fabro_util::printerr!(printer, "Set {}", meta.name);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ use fabro_config::Storage;
|
||||||
use fabro_server::bind::BindRequest;
|
use fabro_server::bind::BindRequest;
|
||||||
use fabro_server::serve;
|
use fabro_server::serve;
|
||||||
use fabro_server::serve::ServeArgs;
|
use fabro_server::serve::ServeArgs;
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use fabro_util::terminal::Styles;
|
use fabro_util::terminal::Styles;
|
||||||
|
|
||||||
use super::record;
|
use super::record;
|
||||||
|
|
@ -16,7 +17,9 @@ pub(crate) async fn execute(
|
||||||
bind: BindRequest,
|
bind: BindRequest,
|
||||||
storage_dir: Option<PathBuf>,
|
storage_dir: Option<PathBuf>,
|
||||||
styles: &'static Styles,
|
styles: &'static Styles,
|
||||||
|
printer: Printer,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
let _ = printer;
|
||||||
serve_args.bind = Some(bind.to_string());
|
serve_args.bind = Some(bind.to_string());
|
||||||
|
|
||||||
let _record_guard = scopeguard::guard(record_path.clone(), |path| {
|
let _record_guard = scopeguard::guard(record_path.clone(), |path| {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ use anyhow::Result;
|
||||||
use fabro_server::bind;
|
use fabro_server::bind;
|
||||||
use fabro_server::bind::BindRequest;
|
use fabro_server::bind::BindRequest;
|
||||||
use fabro_server::serve::ServeArgs;
|
use fabro_server::serve::ServeArgs;
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use fabro_util::terminal::Styles;
|
use fabro_util::terminal::Styles;
|
||||||
|
|
||||||
use crate::args::{
|
use crate::args::{
|
||||||
|
|
@ -17,7 +18,11 @@ use crate::args::{
|
||||||
};
|
};
|
||||||
use crate::user_config;
|
use crate::user_config;
|
||||||
|
|
||||||
pub(crate) async fn dispatch(command: ServerCommand, _globals: &GlobalArgs) -> Result<()> {
|
pub(crate) async fn dispatch(
|
||||||
|
command: ServerCommand,
|
||||||
|
_globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
|
) -> Result<()> {
|
||||||
match command {
|
match command {
|
||||||
ServerCommand::Start(ServerStartArgs {
|
ServerCommand::Start(ServerStartArgs {
|
||||||
storage_dir,
|
storage_dir,
|
||||||
|
|
@ -40,6 +45,7 @@ pub(crate) async fn dispatch(command: ServerCommand, _globals: &GlobalArgs) -> R
|
||||||
serve_args,
|
serve_args,
|
||||||
storage_dir,
|
storage_dir,
|
||||||
styles,
|
styles,
|
||||||
|
printer,
|
||||||
))
|
))
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
@ -49,13 +55,13 @@ pub(crate) async fn dispatch(command: ServerCommand, _globals: &GlobalArgs) -> R
|
||||||
}) => {
|
}) => {
|
||||||
let settings = user_config::load_settings_with_storage_dir(storage_dir.as_deref())?;
|
let settings = user_config::load_settings_with_storage_dir(storage_dir.as_deref())?;
|
||||||
let storage_dir = user_config::storage_dir(&settings)?;
|
let storage_dir = user_config::storage_dir(&settings)?;
|
||||||
stop::execute(&storage_dir, Duration::from_secs(timeout));
|
stop::execute(&storage_dir, Duration::from_secs(timeout), printer);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
ServerCommand::Status(ServerStatusArgs { storage_dir, json }) => {
|
ServerCommand::Status(ServerStatusArgs { storage_dir, json }) => {
|
||||||
let settings = user_config::load_settings_with_storage_dir(storage_dir.as_deref())?;
|
let settings = user_config::load_settings_with_storage_dir(storage_dir.as_deref())?;
|
||||||
let storage_dir = user_config::storage_dir(&settings)?;
|
let storage_dir = user_config::storage_dir(&settings)?;
|
||||||
status::execute(&storage_dir, json)
|
status::execute(&storage_dir, json, printer)
|
||||||
}
|
}
|
||||||
ServerCommand::Serve(ServerServeArgs {
|
ServerCommand::Serve(ServerServeArgs {
|
||||||
storage_dir,
|
storage_dir,
|
||||||
|
|
@ -85,6 +91,7 @@ pub(crate) async fn dispatch(command: ServerCommand, _globals: &GlobalArgs) -> R
|
||||||
bind_addr,
|
bind_addr,
|
||||||
storage_dir.clone_path(),
|
storage_dir.clone_path(),
|
||||||
styles,
|
styles,
|
||||||
|
printer,
|
||||||
))
|
))
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ use fabro_server::bind::{Bind, BindRequest};
|
||||||
use fabro_server::jwt_auth::FABRO_LOCAL_NO_AUTH_ENV;
|
use fabro_server::jwt_auth::FABRO_LOCAL_NO_AUTH_ENV;
|
||||||
use fabro_server::serve;
|
use fabro_server::serve;
|
||||||
use fabro_server::serve::{DEFAULT_TCP_PORT, ServeArgs};
|
use fabro_server::serve::{DEFAULT_TCP_PORT, ServeArgs};
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use fabro_util::terminal::Styles;
|
use fabro_util::terminal::Styles;
|
||||||
|
|
||||||
use super::record;
|
use super::record;
|
||||||
|
|
@ -20,13 +21,14 @@ pub(crate) async fn execute(
|
||||||
mut serve_args: ServeArgs,
|
mut serve_args: ServeArgs,
|
||||||
storage_dir: PathBuf,
|
storage_dir: PathBuf,
|
||||||
styles: &'static Styles,
|
styles: &'static Styles,
|
||||||
|
printer: Printer,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
serve_args.bind = Some(bind.to_string());
|
serve_args.bind = Some(bind.to_string());
|
||||||
|
|
||||||
if foreground {
|
if foreground {
|
||||||
Box::pin(execute_foreground(bind, serve_args, storage_dir, styles)).await
|
Box::pin(execute_foreground(bind, serve_args, storage_dir, styles)).await
|
||||||
} else {
|
} else {
|
||||||
execute_daemon(&bind, &serve_args, &storage_dir, true)
|
execute_daemon(&bind, &serve_args, &storage_dir, true, printer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,7 +87,13 @@ fn ensure_server_running_with_bind(
|
||||||
Bind::Tcp(addr) => BindRequest::Tcp(*addr),
|
Bind::Tcp(addr) => BindRequest::Tcp(*addr),
|
||||||
};
|
};
|
||||||
|
|
||||||
match execute_daemon(&bind_request, &serve_args, storage_dir, false) {
|
match execute_daemon(
|
||||||
|
&bind_request,
|
||||||
|
&serve_args,
|
||||||
|
storage_dir,
|
||||||
|
false,
|
||||||
|
Printer::Silent,
|
||||||
|
) {
|
||||||
Ok(()) => Ok(bind),
|
Ok(()) => Ok(bind),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
if let Some(existing) = record::active_server_record(storage_dir) {
|
if let Some(existing) = record::active_server_record(storage_dir) {
|
||||||
|
|
@ -168,6 +176,7 @@ fn execute_daemon(
|
||||||
serve_args: &ServeArgs,
|
serve_args: &ServeArgs,
|
||||||
storage_dir: &Path,
|
storage_dir: &Path,
|
||||||
announce: bool,
|
announce: bool,
|
||||||
|
printer: Printer,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let lock_file = acquire_lock(storage_dir)?;
|
let lock_file = acquire_lock(storage_dir)?;
|
||||||
let _lock_file = lock_file; // keep alive until function returns
|
let _lock_file = lock_file; // keep alive until function returns
|
||||||
|
|
@ -245,7 +254,7 @@ fn execute_daemon(
|
||||||
record::remove_server_record(&record_path);
|
record::remove_server_record(&record_path);
|
||||||
let tail = read_log_tail(&log_path, 20);
|
let tail = read_log_tail(&log_path, 20);
|
||||||
if !tail.is_empty() {
|
if !tail.is_empty() {
|
||||||
eprintln!("{tail}");
|
fabro_util::printerr!(printer, "{tail}");
|
||||||
}
|
}
|
||||||
bail!("Server exited immediately with status {status}");
|
bail!("Server exited immediately with status {status}");
|
||||||
}
|
}
|
||||||
|
|
@ -258,8 +267,13 @@ fn execute_daemon(
|
||||||
if let Some(record) = record::read_server_record(&record_path) {
|
if let Some(record) = record::read_server_record(&record_path) {
|
||||||
if try_connect(&record.bind) {
|
if try_connect(&record.bind) {
|
||||||
if announce {
|
if announce {
|
||||||
maybe_warn_host_port_fallback(bind, &record.bind);
|
maybe_warn_host_port_fallback(bind, &record.bind, printer);
|
||||||
eprintln!("Server started (pid {}) on {}", child.id(), record.bind);
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
|
"Server started (pid {}) on {}",
|
||||||
|
child.id(),
|
||||||
|
record.bind
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
@ -269,7 +283,7 @@ fn execute_daemon(
|
||||||
record::remove_server_record(&record_path);
|
record::remove_server_record(&record_path);
|
||||||
let tail = read_log_tail(&log_path, 20);
|
let tail = read_log_tail(&log_path, 20);
|
||||||
if !tail.is_empty() {
|
if !tail.is_empty() {
|
||||||
eprintln!("{tail}");
|
fabro_util::printerr!(printer, "{tail}");
|
||||||
}
|
}
|
||||||
bail!("Server exited during startup with status {status}");
|
bail!("Server exited during startup with status {status}");
|
||||||
}
|
}
|
||||||
|
|
@ -283,7 +297,7 @@ fn execute_daemon(
|
||||||
let _ = child.wait();
|
let _ = child.wait();
|
||||||
let tail = read_log_tail(&log_path, 20);
|
let tail = read_log_tail(&log_path, 20);
|
||||||
if !tail.is_empty() {
|
if !tail.is_empty() {
|
||||||
eprintln!("{tail}");
|
fabro_util::printerr!(printer, "{tail}");
|
||||||
}
|
}
|
||||||
bail!("Server did not become ready within {timeout:?}");
|
bail!("Server did not become ready within {timeout:?}");
|
||||||
}
|
}
|
||||||
|
|
@ -327,7 +341,7 @@ fn try_connect(bind: &Bind) -> bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn maybe_warn_host_port_fallback(requested: &BindRequest, resolved: &Bind) {
|
fn maybe_warn_host_port_fallback(requested: &BindRequest, resolved: &Bind, printer: Printer) {
|
||||||
let BindRequest::TcpHost(host) = requested else {
|
let BindRequest::TcpHost(host) = requested else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
@ -335,7 +349,8 @@ fn maybe_warn_host_port_fallback(requested: &BindRequest, resolved: &Bind) {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if addr.ip() == *host && addr.port() != DEFAULT_TCP_PORT {
|
if addr.ip() == *host && addr.port() != DEFAULT_TCP_PORT {
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
"Warning: TCP port {DEFAULT_TCP_PORT} is unavailable on {host}; falling back to a random port."
|
"Warning: TCP port {DEFAULT_TCP_PORT} is unavailable on {host}; falling back to a random port."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,16 @@ use std::path::Path;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
|
|
||||||
use super::record;
|
use super::record;
|
||||||
|
|
||||||
pub(crate) fn execute(storage_dir: &Path, json: bool) -> Result<()> {
|
pub(crate) fn execute(storage_dir: &Path, json: bool, printer: Printer) -> Result<()> {
|
||||||
let Some(record) = record::active_server_record(storage_dir) else {
|
let Some(record) = record::active_server_record(storage_dir) else {
|
||||||
if json {
|
if json {
|
||||||
println!(r#"{{"status":"stopped"}}"#);
|
fabro_util::printout!(printer, r#"{{"status":"stopped"}}"#);
|
||||||
} else {
|
} else {
|
||||||
eprintln!("Server is not running");
|
fabro_util::printerr!(printer, "Server is not running");
|
||||||
}
|
}
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
};
|
};
|
||||||
|
|
@ -24,12 +25,15 @@ pub(crate) fn execute(storage_dir: &Path, json: bool) -> Result<()> {
|
||||||
"started_at": record.started_at.to_rfc3339(),
|
"started_at": record.started_at.to_rfc3339(),
|
||||||
"uptime_seconds": uptime_seconds,
|
"uptime_seconds": uptime_seconds,
|
||||||
});
|
});
|
||||||
println!("{}", serde_json::to_string_pretty(&output)?);
|
fabro_util::printout!(printer, "{}", serde_json::to_string_pretty(&output)?);
|
||||||
} else {
|
} else {
|
||||||
let uptime = format_uptime(Utc::now() - record.started_at);
|
let uptime = format_uptime(Utc::now() - record.started_at);
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
"Server running (pid {}) on {}, started {} ago",
|
"Server running (pid {}) on {}, started {} ago",
|
||||||
record.pid, record.bind, uptime
|
record.pid,
|
||||||
|
record.bind,
|
||||||
|
uptime
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,13 @@ use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use fabro_server::bind::Bind;
|
use fabro_server::bind::Bind;
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
|
|
||||||
use super::record;
|
use super::record;
|
||||||
|
|
||||||
pub(crate) fn execute(storage_dir: &Path, timeout: Duration) {
|
pub(crate) fn execute(storage_dir: &Path, timeout: Duration, printer: Printer) {
|
||||||
let Some(active) = record::active_server_record_details(storage_dir) else {
|
let Some(active) = record::active_server_record_details(storage_dir) else {
|
||||||
eprintln!("Server is not running");
|
fabro_util::printerr!(printer, "Server is not running");
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
};
|
};
|
||||||
let record = active.record;
|
let record = active.record;
|
||||||
|
|
@ -36,5 +37,5 @@ pub(crate) fn execute(storage_dir: &Path, timeout: Duration) {
|
||||||
let _ = std::fs::remove_file(path);
|
let _ = std::fs::remove_file(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
eprintln!("Server stopped");
|
fabro_util::printerr!(printer, "Server stopped");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ use bytes::Bytes;
|
||||||
use fabro_store::{ArtifactStore, RunDatabase};
|
use fabro_store::{ArtifactStore, RunDatabase};
|
||||||
use fabro_store::{EventEnvelope, RunProjection, StageId};
|
use fabro_store::{EventEnvelope, RunProjection, StageId};
|
||||||
use fabro_types::{RunBlobId, RunId};
|
use fabro_types::{RunBlobId, RunId};
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use fabro_workflow::run_dump::RunDump;
|
use fabro_workflow::run_dump::RunDump;
|
||||||
use futures::future::BoxFuture;
|
use futures::future::BoxFuture;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
@ -18,7 +19,11 @@ use crate::server_runs::ServerRunLookup;
|
||||||
use crate::shared::{absolute_or_current, print_json_pretty};
|
use crate::shared::{absolute_or_current, print_json_pretty};
|
||||||
use crate::user_config::{load_settings_with_storage_dir, storage_dir};
|
use crate::user_config::{load_settings_with_storage_dir, storage_dir};
|
||||||
|
|
||||||
pub(crate) async fn dump_command(args: &StoreDumpArgs, globals: &GlobalArgs) -> Result<()> {
|
pub(crate) async fn dump_command(
|
||||||
|
args: &StoreDumpArgs,
|
||||||
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
|
) -> Result<()> {
|
||||||
let cli_settings = load_settings_with_storage_dir(args.storage_dir.as_deref())?;
|
let cli_settings = load_settings_with_storage_dir(args.storage_dir.as_deref())?;
|
||||||
let lookup = ServerRunLookup::connect(&storage_dir(&cli_settings)?).await?;
|
let lookup = ServerRunLookup::connect(&storage_dir(&cli_settings)?).await?;
|
||||||
let run = lookup.resolve(&args.run)?;
|
let run = lookup.resolve(&args.run)?;
|
||||||
|
|
@ -33,7 +38,8 @@ pub(crate) async fn dump_command(args: &StoreDumpArgs, globals: &GlobalArgs) ->
|
||||||
"file_count": file_count,
|
"file_count": file_count,
|
||||||
}))?;
|
}))?;
|
||||||
} else {
|
} else {
|
||||||
println!(
|
fabro_util::printout!(
|
||||||
|
printer,
|
||||||
"Exported {file_count} files for run {} to {}",
|
"Exported {file_count} files for run {} to {}",
|
||||||
run_id,
|
run_id,
|
||||||
args.output.display()
|
args.output.display()
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,16 @@ pub(crate) mod dump;
|
||||||
pub(crate) mod rebuild;
|
pub(crate) mod rebuild;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
|
|
||||||
use crate::args::{GlobalArgs, StoreCommand, StoreNamespace};
|
use crate::args::{GlobalArgs, StoreCommand, StoreNamespace};
|
||||||
|
|
||||||
pub(crate) async fn dispatch(ns: StoreNamespace, globals: &GlobalArgs) -> Result<()> {
|
pub(crate) async fn dispatch(
|
||||||
|
ns: StoreNamespace,
|
||||||
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
|
) -> Result<()> {
|
||||||
match ns.command {
|
match ns.command {
|
||||||
StoreCommand::Dump(args) => dump::dump_command(&args, globals).await,
|
StoreCommand::Dump(args) => dump::dump_command(&args, globals, printer).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,19 @@ use chrono::{DateTime, Utc};
|
||||||
use cli_table::format::{Border, Justify, Separator};
|
use cli_table::format::{Border, Justify, Separator};
|
||||||
use cli_table::{Cell, CellStruct, Style, Table};
|
use cli_table::{Cell, CellStruct, Style, Table};
|
||||||
use fabro_api::types;
|
use fabro_api::types;
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
|
|
||||||
use crate::args::{DfArgs, GlobalArgs};
|
use crate::args::{DfArgs, GlobalArgs};
|
||||||
use crate::command_context::CommandContext;
|
use crate::command_context::CommandContext;
|
||||||
use crate::server_client;
|
use crate::server_client;
|
||||||
use crate::shared::{format_size, print_json_pretty};
|
use crate::shared::{format_size, print_json_pretty};
|
||||||
|
|
||||||
pub(super) async fn df_command(args: &DfArgs, globals: &GlobalArgs) -> Result<()> {
|
pub(super) async fn df_command(
|
||||||
let ctx = CommandContext::for_connection(&args.connection)?;
|
args: &DfArgs,
|
||||||
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
|
) -> Result<()> {
|
||||||
|
let ctx = CommandContext::for_connection(&args.connection, printer)?;
|
||||||
let server = ctx.server().await?;
|
let server = ctx.server().await?;
|
||||||
let output = server
|
let output = server
|
||||||
.api()
|
.api()
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,17 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
|
|
||||||
use crate::args::{GlobalArgs, SystemEventsArgs};
|
use crate::args::{GlobalArgs, SystemEventsArgs};
|
||||||
use crate::command_context::CommandContext;
|
use crate::command_context::CommandContext;
|
||||||
use crate::{server_client, sse};
|
use crate::{server_client, sse};
|
||||||
|
|
||||||
pub(super) async fn events_command(args: &SystemEventsArgs, globals: &GlobalArgs) -> Result<()> {
|
pub(super) async fn events_command(
|
||||||
let ctx = CommandContext::for_connection(&args.connection)?;
|
args: &SystemEventsArgs,
|
||||||
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
|
) -> Result<()> {
|
||||||
|
let ctx = CommandContext::for_connection(&args.connection, printer)?;
|
||||||
let server = ctx.server().await?;
|
let server = ctx.server().await?;
|
||||||
|
|
||||||
let mut request = server.api().attach_events();
|
let mut request = server.api().attach_events();
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,17 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
|
|
||||||
use crate::args::{GlobalArgs, SystemInfoArgs};
|
use crate::args::{GlobalArgs, SystemInfoArgs};
|
||||||
use crate::command_context::CommandContext;
|
use crate::command_context::CommandContext;
|
||||||
use crate::server_client;
|
use crate::server_client;
|
||||||
use crate::shared::print_json_pretty;
|
use crate::shared::print_json_pretty;
|
||||||
|
|
||||||
pub(super) async fn info_command(args: &SystemInfoArgs, globals: &GlobalArgs) -> Result<()> {
|
pub(super) async fn info_command(
|
||||||
let ctx = CommandContext::for_connection(&args.connection)?;
|
args: &SystemInfoArgs,
|
||||||
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
|
) -> Result<()> {
|
||||||
|
let ctx = CommandContext::for_connection(&args.connection, printer)?;
|
||||||
let server = ctx.server().await?;
|
let server = ctx.server().await?;
|
||||||
let response = server
|
let response = server
|
||||||
.api()
|
.api()
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,20 @@ mod info;
|
||||||
mod prune;
|
mod prune;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
pub(crate) use prune::parse_duration;
|
pub(crate) use prune::parse_duration;
|
||||||
|
|
||||||
use crate::args::{GlobalArgs, SystemCommand, SystemNamespace};
|
use crate::args::{GlobalArgs, SystemCommand, SystemNamespace};
|
||||||
|
|
||||||
pub(crate) async fn dispatch(ns: SystemNamespace, globals: &GlobalArgs) -> Result<()> {
|
pub(crate) async fn dispatch(
|
||||||
|
ns: SystemNamespace,
|
||||||
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
|
) -> Result<()> {
|
||||||
match ns.command {
|
match ns.command {
|
||||||
SystemCommand::Info(args) => info::info_command(&args, globals).await,
|
SystemCommand::Info(args) => info::info_command(&args, globals, printer).await,
|
||||||
SystemCommand::Prune(args) => prune::prune_command(&args, globals).await,
|
SystemCommand::Prune(args) => prune::prune_command(&args, globals, printer).await,
|
||||||
SystemCommand::Df(args) => df::df_command(&args, globals).await,
|
SystemCommand::Df(args) => df::df_command(&args, globals, printer).await,
|
||||||
SystemCommand::Events(args) => events::events_command(&args, globals).await,
|
SystemCommand::Events(args) => events::events_command(&args, globals, printer).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
use anyhow::{Context, Result, bail};
|
use anyhow::{Context, Result, bail};
|
||||||
use fabro_api::types;
|
use fabro_api::types;
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use tracing::{debug, info};
|
use tracing::{debug, info};
|
||||||
|
|
||||||
use crate::args::{GlobalArgs, RunsPruneArgs};
|
use crate::args::{GlobalArgs, RunsPruneArgs};
|
||||||
|
|
@ -9,8 +10,12 @@ use crate::command_context::CommandContext;
|
||||||
use crate::server_client;
|
use crate::server_client;
|
||||||
use crate::shared::{format_size, print_json_pretty};
|
use crate::shared::{format_size, print_json_pretty};
|
||||||
|
|
||||||
pub(super) async fn prune_command(args: &RunsPruneArgs, globals: &GlobalArgs) -> Result<()> {
|
pub(super) async fn prune_command(
|
||||||
let ctx = CommandContext::for_connection(&args.connection)?;
|
args: &RunsPruneArgs,
|
||||||
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
|
) -> Result<()> {
|
||||||
|
let ctx = CommandContext::for_connection(&args.connection, printer)?;
|
||||||
let server = ctx.server().await?;
|
let server = ctx.server().await?;
|
||||||
let response = server
|
let response = server
|
||||||
.api()
|
.api()
|
||||||
|
|
@ -27,7 +32,7 @@ pub(super) async fn prune_command(args: &RunsPruneArgs, globals: &GlobalArgs) ->
|
||||||
.await
|
.await
|
||||||
.map_err(server_client::map_api_error)?
|
.map_err(server_client::map_api_error)?
|
||||||
.into_inner();
|
.into_inner();
|
||||||
prune_from(&response, globals)
|
prune_from(&response, globals, printer)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn parse_duration(s: &str) -> Result<chrono::Duration> {
|
pub(crate) fn parse_duration(s: &str) -> Result<chrono::Duration> {
|
||||||
|
|
@ -46,7 +51,11 @@ pub(crate) fn parse_duration(s: &str) -> Result<chrono::Duration> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prune_from(response: &types::PruneRunsResponse, globals: &GlobalArgs) -> Result<()> {
|
fn prune_from(
|
||||||
|
response: &types::PruneRunsResponse,
|
||||||
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
|
) -> Result<()> {
|
||||||
let total_count = response.total_count.unwrap_or_default();
|
let total_count = response.total_count.unwrap_or_default();
|
||||||
let total_size_bytes = response.total_size_bytes.unwrap_or_default();
|
let total_size_bytes = response.total_size_bytes.unwrap_or_default();
|
||||||
|
|
||||||
|
|
@ -63,7 +72,7 @@ fn prune_from(response: &types::PruneRunsResponse, globals: &GlobalArgs) -> Resu
|
||||||
}
|
}
|
||||||
|
|
||||||
if total_count == 0 {
|
if total_count == 0 {
|
||||||
eprintln!("No matching runs to prune.");
|
fabro_util::printerr!(printer, "No matching runs to prune.");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -73,13 +82,15 @@ fn prune_from(response: &types::PruneRunsResponse, globals: &GlobalArgs) -> Resu
|
||||||
run_id = run.run_id.as_deref().unwrap_or("-"),
|
run_id = run.run_id.as_deref().unwrap_or("-"),
|
||||||
"would delete run (dry-run)"
|
"would delete run (dry-run)"
|
||||||
);
|
);
|
||||||
println!(
|
fabro_util::printout!(
|
||||||
|
printer,
|
||||||
"would delete: {} ({})",
|
"would delete: {} ({})",
|
||||||
run.dir_name.as_deref().unwrap_or("-"),
|
run.dir_name.as_deref().unwrap_or("-"),
|
||||||
run.workflow_name.as_deref().unwrap_or("-")
|
run.workflow_name.as_deref().unwrap_or("-")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
"\n{} run(s) would be deleted ({} freed). Pass --yes to confirm.",
|
"\n{} run(s) would be deleted ({} freed). Pass --yes to confirm.",
|
||||||
total_count,
|
total_count,
|
||||||
format_size(as_u64(total_size_bytes))
|
format_size(as_u64(total_size_bytes))
|
||||||
|
|
@ -87,7 +98,8 @@ fn prune_from(response: &types::PruneRunsResponse, globals: &GlobalArgs) -> Resu
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
"{} run(s) deleted ({} freed).",
|
"{} run(s) deleted ({} freed).",
|
||||||
response.deleted_count.unwrap_or(total_count),
|
response.deleted_count.unwrap_or(total_count),
|
||||||
format_size(as_u64(response.freed_bytes.unwrap_or(total_size_bytes)))
|
format_size(as_u64(response.freed_bytes.unwrap_or(total_size_bytes)))
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use fabro_util::Home;
|
use fabro_util::Home;
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
|
|
@ -26,7 +27,11 @@ struct Inventory {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::unused_async)] // call site requires async
|
#[allow(clippy::unused_async)] // call site requires async
|
||||||
pub(crate) async fn run_uninstall(args: &UninstallArgs, globals: &GlobalArgs) -> Result<()> {
|
pub(crate) async fn run_uninstall(
|
||||||
|
args: &UninstallArgs,
|
||||||
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
|
) -> Result<()> {
|
||||||
let home = Home::from_env();
|
let home = Home::from_env();
|
||||||
let home_root = home.root().to_path_buf();
|
let home_root = home.root().to_path_buf();
|
||||||
|
|
||||||
|
|
@ -34,7 +39,7 @@ pub(crate) async fn run_uninstall(args: &UninstallArgs, globals: &GlobalArgs) ->
|
||||||
if globals.json {
|
if globals.json {
|
||||||
print_json_pretty(&serde_json::json!({ "status": "not_installed" }))?;
|
print_json_pretty(&serde_json::json!({ "status": "not_installed" }))?;
|
||||||
} else {
|
} else {
|
||||||
eprintln!("Fabro is not installed.");
|
fabro_util::printerr!(printer, "Fabro is not installed.");
|
||||||
}
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
@ -50,12 +55,12 @@ pub(crate) async fn run_uninstall(args: &UninstallArgs, globals: &GlobalArgs) ->
|
||||||
if globals.json {
|
if globals.json {
|
||||||
print_json_pretty(&inventory)?;
|
print_json_pretty(&inventory)?;
|
||||||
} else {
|
} else {
|
||||||
print_preview(&inventory);
|
print_preview(&inventory, printer);
|
||||||
}
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
execute_uninstall(&inventory, globals.json)
|
execute_uninstall(&inventory, globals.json, printer)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_inventory(home_root: &Path, storage_dir: &Path) -> Inventory {
|
fn build_inventory(home_root: &Path, storage_dir: &Path) -> Inventory {
|
||||||
|
|
@ -135,13 +140,18 @@ fn resolve_binary(home_root: &Path) -> (Option<PathBuf>, bool) {
|
||||||
(binary_path, is_managed)
|
(binary_path, is_managed)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_preview(inventory: &Inventory) {
|
fn print_preview(inventory: &Inventory, printer: Printer) {
|
||||||
let green = console::Style::new().green();
|
let green = console::Style::new().green();
|
||||||
let dim = console::Style::new().dim();
|
let dim = console::Style::new().dim();
|
||||||
let bold = console::Style::new().bold();
|
let bold = console::Style::new().bold();
|
||||||
|
|
||||||
eprintln!("\n{}", bold.apply_to("The following will be removed:"));
|
fabro_util::printerr!(
|
||||||
eprintln!(
|
printer,
|
||||||
|
"\n{}",
|
||||||
|
bold.apply_to("The following will be removed:")
|
||||||
|
);
|
||||||
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" {} {} {}",
|
" {} {} {}",
|
||||||
green.apply_to("~"),
|
green.apply_to("~"),
|
||||||
tilde_path(&inventory.home_root),
|
tilde_path(&inventory.home_root),
|
||||||
|
|
@ -152,7 +162,8 @@ fn print_preview(inventory: &Inventory) {
|
||||||
&& !inventory.storage_dir.starts_with(&inventory.home_root)
|
&& !inventory.storage_dir.starts_with(&inventory.home_root)
|
||||||
{
|
{
|
||||||
let storage_size = dir_size(&inventory.storage_dir);
|
let storage_size = dir_size(&inventory.storage_dir);
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" {} {} {}",
|
" {} {} {}",
|
||||||
green.apply_to("~"),
|
green.apply_to("~"),
|
||||||
tilde_path(&inventory.storage_dir),
|
tilde_path(&inventory.storage_dir),
|
||||||
|
|
@ -161,29 +172,32 @@ fn print_preview(inventory: &Inventory) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if inventory.server_running {
|
if inventory.server_running {
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
"\n {} A running server will be stopped first.",
|
"\n {} A running server will be stopped first.",
|
||||||
console::Style::new().yellow().apply_to("!")
|
console::Style::new().yellow().apply_to("!")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !inventory.shell_configs.is_empty() {
|
if !inventory.shell_configs.is_empty() {
|
||||||
eprintln!("\n Shell configs with PATH entries:");
|
fabro_util::printerr!(printer, "\n Shell configs with PATH entries:");
|
||||||
for path in &inventory.shell_configs {
|
for path in &inventory.shell_configs {
|
||||||
eprintln!(" {}", tilde_path(path));
|
fabro_util::printerr!(printer, " {}", tilde_path(path));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match (&inventory.binary_path, inventory.binary_is_managed) {
|
match (&inventory.binary_path, inventory.binary_is_managed) {
|
||||||
(Some(_), true) => {
|
(Some(_), true) => {
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
"\n {} Binary is inside {} and will be removed.",
|
"\n {} Binary is inside {} and will be removed.",
|
||||||
dim.apply_to("i"),
|
dim.apply_to("i"),
|
||||||
tilde_path(&inventory.home_root)
|
tilde_path(&inventory.home_root)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
(Some(bin), false) => {
|
(Some(bin), false) => {
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
"\n {} Binary at {} is outside {} and must be removed manually.",
|
"\n {} Binary at {} is outside {} and must be removed manually.",
|
||||||
dim.apply_to("i"),
|
dim.apply_to("i"),
|
||||||
tilde_path(bin),
|
tilde_path(bin),
|
||||||
|
|
@ -191,14 +205,15 @@ fn print_preview(inventory: &Inventory) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
(None, _) => {
|
(None, _) => {
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
"\n {} Could not determine binary location.",
|
"\n {} Could not determine binary location.",
|
||||||
dim.apply_to("i")
|
dim.apply_to("i")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
eprintln!("\nPass --yes to confirm.");
|
fabro_util::printerr!(printer, "\nPass --yes to confirm.");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
|
|
@ -211,7 +226,7 @@ struct UninstallResult {
|
||||||
binary_hint: Option<String>,
|
binary_hint: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn execute_uninstall(inventory: &Inventory, json: bool) -> Result<()> {
|
fn execute_uninstall(inventory: &Inventory, json: bool, printer: Printer) -> Result<()> {
|
||||||
let green = console::Style::new().green();
|
let green = console::Style::new().green();
|
||||||
let dim = console::Style::new().dim();
|
let dim = console::Style::new().dim();
|
||||||
let bold = console::Style::new().bold();
|
let bold = console::Style::new().bold();
|
||||||
|
|
@ -227,13 +242,17 @@ fn execute_uninstall(inventory: &Inventory, json: bool) -> Result<()> {
|
||||||
|
|
||||||
// Unit 3a: Server stop
|
// Unit 3a: Server stop
|
||||||
if inventory.server_running {
|
if inventory.server_running {
|
||||||
server::stop::execute(&inventory.storage_dir, Duration::from_secs(5));
|
server::stop::execute(&inventory.storage_dir, Duration::from_secs(5), printer);
|
||||||
result.server_stopped = true;
|
result.server_stopped = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unit 3b: Safety guardrails
|
// Unit 3b: Safety guardrails
|
||||||
if let Err(e) = validate_safe_to_delete(&inventory.home_root) {
|
if let Err(e) = validate_safe_to_delete(&inventory.home_root) {
|
||||||
eprintln!("Refusing to delete {}: {e}", inventory.home_root.display());
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
|
"Refusing to delete {}: {e}",
|
||||||
|
inventory.home_root.display()
|
||||||
|
);
|
||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -242,7 +261,8 @@ fn execute_uninstall(inventory: &Inventory, json: bool) -> Result<()> {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
result.home_removed = true;
|
result.home_removed = true;
|
||||||
if !json {
|
if !json {
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" {} Removed {}",
|
" {} Removed {}",
|
||||||
green.apply_to("\u{2714}"),
|
green.apply_to("\u{2714}"),
|
||||||
tilde_path(&inventory.home_root)
|
tilde_path(&inventory.home_root)
|
||||||
|
|
@ -252,7 +272,8 @@ fn execute_uninstall(inventory: &Inventory, json: bool) -> Result<()> {
|
||||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
||||||
result.home_removed = true;
|
result.home_removed = true;
|
||||||
if !json {
|
if !json {
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" {} {} already removed",
|
" {} {} already removed",
|
||||||
dim.apply_to("-"),
|
dim.apply_to("-"),
|
||||||
tilde_path(&inventory.home_root)
|
tilde_path(&inventory.home_root)
|
||||||
|
|
@ -261,7 +282,8 @@ fn execute_uninstall(inventory: &Inventory, json: bool) -> Result<()> {
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if !json {
|
if !json {
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" Failed to remove {}: {e}",
|
" Failed to remove {}: {e}",
|
||||||
tilde_path(&inventory.home_root)
|
tilde_path(&inventory.home_root)
|
||||||
);
|
);
|
||||||
|
|
@ -274,7 +296,8 @@ fn execute_uninstall(inventory: &Inventory, json: bool) -> Result<()> {
|
||||||
if !inventory.storage_dir.starts_with(&inventory.home_root) && inventory.storage_dir.exists() {
|
if !inventory.storage_dir.starts_with(&inventory.home_root) && inventory.storage_dir.exists() {
|
||||||
if let Err(e) = validate_safe_to_delete(&inventory.storage_dir) {
|
if let Err(e) = validate_safe_to_delete(&inventory.storage_dir) {
|
||||||
if !json {
|
if !json {
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
"Refusing to delete storage dir {}: {e}",
|
"Refusing to delete storage dir {}: {e}",
|
||||||
inventory.storage_dir.display()
|
inventory.storage_dir.display()
|
||||||
);
|
);
|
||||||
|
|
@ -284,7 +307,8 @@ fn execute_uninstall(inventory: &Inventory, json: bool) -> Result<()> {
|
||||||
match fs::remove_dir_all(&inventory.storage_dir) {
|
match fs::remove_dir_all(&inventory.storage_dir) {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
if !json {
|
if !json {
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" {} Removed {}",
|
" {} Removed {}",
|
||||||
green.apply_to("\u{2714}"),
|
green.apply_to("\u{2714}"),
|
||||||
tilde_path(&inventory.storage_dir)
|
tilde_path(&inventory.storage_dir)
|
||||||
|
|
@ -294,7 +318,8 @@ fn execute_uninstall(inventory: &Inventory, json: bool) -> Result<()> {
|
||||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
|
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if !json {
|
if !json {
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" Failed to remove {}: {e}",
|
" Failed to remove {}: {e}",
|
||||||
tilde_path(&inventory.storage_dir)
|
tilde_path(&inventory.storage_dir)
|
||||||
);
|
);
|
||||||
|
|
@ -311,7 +336,8 @@ fn execute_uninstall(inventory: &Inventory, json: bool) -> Result<()> {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
result.shell_configs_cleaned.push(path.clone());
|
result.shell_configs_cleaned.push(path.clone());
|
||||||
if !json {
|
if !json {
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" {} Cleaned {}",
|
" {} Cleaned {}",
|
||||||
green.apply_to("\u{2714}"),
|
green.apply_to("\u{2714}"),
|
||||||
tilde_path(path)
|
tilde_path(path)
|
||||||
|
|
@ -321,7 +347,8 @@ fn execute_uninstall(inventory: &Inventory, json: bool) -> Result<()> {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("Failed to clean shell config {}: {e}", path.display());
|
warn!("Failed to clean shell config {}: {e}", path.display());
|
||||||
if !json {
|
if !json {
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" {} Could not clean {}: {e}",
|
" {} Could not clean {}: {e}",
|
||||||
console::Style::new().yellow().apply_to("!"),
|
console::Style::new().yellow().apply_to("!"),
|
||||||
tilde_path(path)
|
tilde_path(path)
|
||||||
|
|
@ -336,7 +363,8 @@ fn execute_uninstall(inventory: &Inventory, json: bool) -> Result<()> {
|
||||||
(Some(_), true) => {
|
(Some(_), true) => {
|
||||||
result.binary_removed = true;
|
result.binary_removed = true;
|
||||||
if !json {
|
if !json {
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" {} Binary removed {}",
|
" {} Binary removed {}",
|
||||||
green.apply_to("\u{2714}"),
|
green.apply_to("\u{2714}"),
|
||||||
dim.apply_to("(was inside ~/.fabro/bin/)")
|
dim.apply_to("(was inside ~/.fabro/bin/)")
|
||||||
|
|
@ -347,7 +375,7 @@ fn execute_uninstall(inventory: &Inventory, json: bool) -> Result<()> {
|
||||||
let hint = binary_removal_hint(bin);
|
let hint = binary_removal_hint(bin);
|
||||||
result.binary_hint = Some(hint.clone());
|
result.binary_hint = Some(hint.clone());
|
||||||
if !json {
|
if !json {
|
||||||
eprintln!("\n {} {}", dim.apply_to("i"), hint);
|
fabro_util::printerr!(printer, "\n {} {}", dim.apply_to("i"), hint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(None, _) => {
|
(None, _) => {
|
||||||
|
|
@ -363,7 +391,11 @@ fn execute_uninstall(inventory: &Inventory, json: bool) -> Result<()> {
|
||||||
if json {
|
if json {
|
||||||
print_json_pretty(&result)?;
|
print_json_pretty(&result)?;
|
||||||
} else {
|
} else {
|
||||||
eprintln!("\n{}", bold.apply_to("Fabro has been uninstalled."));
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
|
"\n{}",
|
||||||
|
bold.apply_to("Fabro has been uninstalled.")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if critical_failure {
|
if critical_failure {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ use std::io::{IsTerminal, Write};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use anyhow::{Context, Result, bail};
|
use anyhow::{Context, Result, bail};
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
use tokio::process::Command as TokioCommand;
|
use tokio::process::Command as TokioCommand;
|
||||||
|
|
@ -223,7 +224,11 @@ impl UpgradeCheckState {
|
||||||
|
|
||||||
// ── Main upgrade command ───────────────────────────────────────────────────
|
// ── Main upgrade command ───────────────────────────────────────────────────
|
||||||
|
|
||||||
pub(crate) async fn run_upgrade(args: UpgradeArgs, globals: &GlobalArgs) -> Result<()> {
|
pub(crate) async fn run_upgrade(
|
||||||
|
args: UpgradeArgs,
|
||||||
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
|
) -> Result<()> {
|
||||||
let backend = select_backend().await;
|
let backend = select_backend().await;
|
||||||
|
|
||||||
let current =
|
let current =
|
||||||
|
|
@ -249,7 +254,7 @@ pub(crate) async fn run_upgrade(args: UpgradeArgs, globals: &GlobalArgs) -> Resu
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Explicit --version: warn + prompt
|
// Explicit --version: warn + prompt
|
||||||
eprintln!("Warning: downgrading from {current} to {target}");
|
fabro_util::printerr!(printer, "Warning: downgrading from {current} to {target}");
|
||||||
if std::io::stdin().is_terminal() {
|
if std::io::stdin().is_terminal() {
|
||||||
let confirm = dialoguer::Confirm::new()
|
let confirm = dialoguer::Confirm::new()
|
||||||
.with_prompt("Continue with downgrade?")
|
.with_prompt("Continue with downgrade?")
|
||||||
|
|
@ -269,7 +274,7 @@ pub(crate) async fn run_upgrade(args: UpgradeArgs, globals: &GlobalArgs) -> Resu
|
||||||
"installed_version": current.to_string(),
|
"installed_version": current.to_string(),
|
||||||
}))?;
|
}))?;
|
||||||
} else {
|
} else {
|
||||||
eprintln!("Already on version {current}");
|
fabro_util::printerr!(printer, "Already on version {current}");
|
||||||
}
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
@ -284,9 +289,9 @@ pub(crate) async fn run_upgrade(args: UpgradeArgs, globals: &GlobalArgs) -> Resu
|
||||||
"dry_run": true,
|
"dry_run": true,
|
||||||
}))?;
|
}))?;
|
||||||
} else {
|
} else {
|
||||||
eprintln!("Would upgrade fabro from {current} to {target}");
|
fabro_util::printerr!(printer, "Would upgrade fabro from {current} to {target}");
|
||||||
eprintln!(" tag: {tag}");
|
fabro_util::printerr!(printer, " tag: {tag}");
|
||||||
eprintln!(" target: {}", detect_target()?);
|
fabro_util::printerr!(printer, " target: {}", detect_target()?);
|
||||||
}
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
@ -305,7 +310,7 @@ pub(crate) async fn run_upgrade(args: UpgradeArgs, globals: &GlobalArgs) -> Resu
|
||||||
.context("failed to create temp directory")?;
|
.context("failed to create temp directory")?;
|
||||||
|
|
||||||
// Download tarball and checksum in parallel
|
// Download tarball and checksum in parallel
|
||||||
eprintln!("Downloading fabro {target}...");
|
fabro_util::printerr!(printer, "Downloading fabro {target}...");
|
||||||
let (tarball_path, checksum_path) = tokio::try_join!(
|
let (tarball_path, checksum_path) = tokio::try_join!(
|
||||||
backend.download_release(&tag, &tarball_name, tmp_dir.path()),
|
backend.download_release(&tag, &tarball_name, tmp_dir.path()),
|
||||||
backend.download_release(&tag, &checksum_name, tmp_dir.path()),
|
backend.download_release(&tag, &checksum_name, tmp_dir.path()),
|
||||||
|
|
@ -358,7 +363,7 @@ pub(crate) async fn run_upgrade(args: UpgradeArgs, globals: &GlobalArgs) -> Resu
|
||||||
"installed_version": target.to_string(),
|
"installed_version": target.to_string(),
|
||||||
}))?;
|
}))?;
|
||||||
} else {
|
} else {
|
||||||
eprintln!("Upgraded fabro to {target}");
|
fabro_util::printerr!(printer, "Upgraded fabro to {target}");
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -371,18 +376,19 @@ pub(crate) async fn run_upgrade(args: UpgradeArgs, globals: &GlobalArgs) -> Resu
|
||||||
pub(crate) fn spawn_upgrade_check(
|
pub(crate) fn spawn_upgrade_check(
|
||||||
no_upgrade_check: bool,
|
no_upgrade_check: bool,
|
||||||
upgrade_check_enabled: bool,
|
upgrade_check_enabled: bool,
|
||||||
|
printer: Printer,
|
||||||
) -> Option<JoinHandle<()>> {
|
) -> Option<JoinHandle<()>> {
|
||||||
if no_upgrade_check || !upgrade_check_enabled {
|
if no_upgrade_check || !upgrade_check_enabled {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
Some(tokio::spawn(async {
|
Some(tokio::spawn(async move {
|
||||||
if let Err(e) = check_and_print_notice().await {
|
if let Err(e) = check_and_print_notice(printer).await {
|
||||||
debug!(%e, "Upgrade check failed (silently swallowed)");
|
debug!(%e, "Upgrade check failed (silently swallowed)");
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn check_and_print_notice() -> Result<()> {
|
async fn check_and_print_notice(printer: Printer) -> Result<()> {
|
||||||
let state_path = fabro_util::Home::from_env().root().join(LAST_CHECK_FILE);
|
let state_path = fabro_util::Home::from_env().root().join(LAST_CHECK_FILE);
|
||||||
|
|
||||||
let current = Version::parse(env!("CARGO_PKG_VERSION"))?;
|
let current = Version::parse(env!("CARGO_PKG_VERSION"))?;
|
||||||
|
|
@ -392,7 +398,7 @@ async fn check_and_print_notice() -> Result<()> {
|
||||||
if !state.is_stale() {
|
if !state.is_stale() {
|
||||||
if let Ok(latest) = Version::parse(&state.latest_version) {
|
if let Ok(latest) = Version::parse(&state.latest_version) {
|
||||||
if latest > current {
|
if latest > current {
|
||||||
print_notice(¤t, &latest);
|
print_notice(¤t, &latest, printer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|
@ -416,15 +422,18 @@ async fn check_and_print_notice() -> Result<()> {
|
||||||
let _ = state.save(&state_path);
|
let _ = state.save(&state_path);
|
||||||
|
|
||||||
if latest > current {
|
if latest > current {
|
||||||
print_notice(¤t, &latest);
|
print_notice(¤t, &latest, printer);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_notice(current: &Version, latest: &Version) {
|
fn print_notice(current: &Version, latest: &Version, printer: Printer) {
|
||||||
eprintln!("A new version of fabro is available: {latest} (current: {current})");
|
fabro_util::printerr!(
|
||||||
eprintln!("Run `fabro upgrade` to update.");
|
printer,
|
||||||
|
"A new version of fabro is available: {latest} (current: {current})"
|
||||||
|
);
|
||||||
|
fabro_util::printerr!(printer, "Run `fabro upgrade` to update.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Tests ──────────────────────────────────────────────────────────────────
|
// ── Tests ──────────────────────────────────────────────────────────────────
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ use anyhow::bail;
|
||||||
use fabro_config::load::load_settings_user;
|
use fabro_config::load::load_settings_user;
|
||||||
use fabro_config::user::active_settings_path;
|
use fabro_config::user::active_settings_path;
|
||||||
use fabro_types::settings::SettingsLayer;
|
use fabro_types::settings::SettingsLayer;
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use fabro_util::terminal::Styles;
|
use fabro_util::terminal::Styles;
|
||||||
|
|
||||||
use crate::args::{GlobalArgs, ValidateArgs};
|
use crate::args::{GlobalArgs, ValidateArgs};
|
||||||
|
|
@ -14,8 +15,9 @@ pub(crate) async fn run(
|
||||||
args: &ValidateArgs,
|
args: &ValidateArgs,
|
||||||
styles: &Styles,
|
styles: &Styles,
|
||||||
globals: &GlobalArgs,
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let ctx = CommandContext::for_target(&args.target)?;
|
let ctx = CommandContext::for_target(&args.target, printer)?;
|
||||||
let built = build_run_manifest(ManifestBuildInput {
|
let built = build_run_manifest(ManifestBuildInput {
|
||||||
workflow: args.workflow.clone(),
|
workflow: args.workflow.clone(),
|
||||||
cwd: ctx.cwd().to_path_buf(),
|
cwd: ctx.cwd().to_path_buf(),
|
||||||
|
|
@ -47,7 +49,8 @@ pub(crate) async fn run(
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
"{} ({} nodes, {} edges)",
|
"{} ({} nodes, {} edges)",
|
||||||
styles
|
styles
|
||||||
.bold
|
.bold
|
||||||
|
|
@ -55,13 +58,14 @@ pub(crate) async fn run(
|
||||||
response.workflow.nodes,
|
response.workflow.nodes,
|
||||||
response.workflow.edges,
|
response.workflow.edges,
|
||||||
);
|
);
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
"{} {}",
|
"{} {}",
|
||||||
styles.dim.apply_to("Graph:"),
|
styles.dim.apply_to("Graph:"),
|
||||||
styles.dim.apply_to(relative_path(&built.target_path)),
|
styles.dim.apply_to(relative_path(&built.target_path)),
|
||||||
);
|
);
|
||||||
|
|
||||||
print_diagnostics(&diagnostics, styles);
|
print_diagnostics(&diagnostics, styles, printer);
|
||||||
|
|
||||||
if diagnostics
|
if diagnostics
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -70,6 +74,6 @@ pub(crate) async fn run(
|
||||||
bail!("Validation failed");
|
bail!("Validation failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
eprintln!("Validation: {}", styles.green.apply_to("OK"));
|
fabro_util::printerr!(printer, "Validation: {}", styles.green.apply_to("OK"));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,16 @@ use std::path::Path;
|
||||||
|
|
||||||
use anyhow::{Context, Result, bail};
|
use anyhow::{Context, Result, bail};
|
||||||
use fabro_config::project::{discover_project_config, resolve_fabro_root};
|
use fabro_config::project::{discover_project_config, resolve_fabro_root};
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
|
|
||||||
use crate::args::{GlobalArgs, WorkflowCreateArgs};
|
use crate::args::{GlobalArgs, WorkflowCreateArgs};
|
||||||
use crate::shared::{print_json_pretty, relative_path};
|
use crate::shared::{print_json_pretty, relative_path};
|
||||||
|
|
||||||
pub(super) fn create_command(args: &WorkflowCreateArgs, globals: &GlobalArgs) -> Result<()> {
|
pub(super) fn create_command(
|
||||||
|
args: &WorkflowCreateArgs,
|
||||||
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
|
) -> Result<()> {
|
||||||
let cwd = std::env::current_dir()?;
|
let cwd = std::env::current_dir()?;
|
||||||
|
|
||||||
let Some((config_path, config)) = discover_project_config(&cwd)? else {
|
let Some((config_path, config)) = discover_project_config(&cwd)? else {
|
||||||
|
|
@ -35,27 +40,36 @@ pub(super) fn create_command(args: &WorkflowCreateArgs, globals: &GlobalArgs) ->
|
||||||
let dim = console::Style::new().dim();
|
let dim = console::Style::new().dim();
|
||||||
|
|
||||||
let rel_dir = relative_path(&workflows_dir);
|
let rel_dir = relative_path(&workflows_dir);
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" {} {}",
|
" {} {}",
|
||||||
green.apply_to("✔"),
|
green.apply_to("✔"),
|
||||||
dim.apply_to(format!("{rel_dir}/workflow.fabro"))
|
dim.apply_to(format!("{rel_dir}/workflow.fabro"))
|
||||||
);
|
);
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" {} {}",
|
" {} {}",
|
||||||
green.apply_to("✔"),
|
green.apply_to("✔"),
|
||||||
dim.apply_to(format!("{rel_dir}/workflow.toml"))
|
dim.apply_to(format!("{rel_dir}/workflow.toml"))
|
||||||
);
|
);
|
||||||
|
|
||||||
eprintln!("\n{} Next steps:\n", bold.apply_to("Workflow created!"));
|
fabro_util::printerr!(
|
||||||
eprintln!(
|
printer,
|
||||||
|
"\n{} Next steps:\n",
|
||||||
|
bold.apply_to("Workflow created!")
|
||||||
|
);
|
||||||
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" 1. Edit the graph: {}",
|
" 1. Edit the graph: {}",
|
||||||
cyan_bold.apply_to(format!("{rel_dir}/workflow.fabro"))
|
cyan_bold.apply_to(format!("{rel_dir}/workflow.fabro"))
|
||||||
);
|
);
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" 2. Validate: {}",
|
" 2. Validate: {}",
|
||||||
cyan_bold.apply_to(format!("fabro validate {}", args.name))
|
cyan_bold.apply_to(format!("fabro validate {}", args.name))
|
||||||
);
|
);
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" 3. Run: {}",
|
" 3. Run: {}",
|
||||||
cyan_bold.apply_to(format!("fabro run {}", args.name))
|
cyan_bold.apply_to(format!("fabro run {}", args.name))
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ use fabro_config::project::{
|
||||||
WorkflowInfo, WorkflowSource, discover_project_config, list_workflows_detailed,
|
WorkflowInfo, WorkflowSource, discover_project_config, list_workflows_detailed,
|
||||||
resolve_fabro_root,
|
resolve_fabro_root,
|
||||||
};
|
};
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use fabro_util::terminal::Styles;
|
use fabro_util::terminal::Styles;
|
||||||
|
|
||||||
use crate::args::{GlobalArgs, WorkflowListArgs};
|
use crate::args::{GlobalArgs, WorkflowListArgs};
|
||||||
|
|
@ -10,7 +11,11 @@ use crate::shared::{print_json_pretty, relative_path};
|
||||||
|
|
||||||
const GOAL_MAX_LEN: usize = 60;
|
const GOAL_MAX_LEN: usize = 60;
|
||||||
|
|
||||||
pub(super) fn list_command(_args: &WorkflowListArgs, globals: &GlobalArgs) -> Result<()> {
|
pub(super) fn list_command(
|
||||||
|
_args: &WorkflowListArgs,
|
||||||
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
|
) -> Result<()> {
|
||||||
let styles = Styles::detect_stderr();
|
let styles = Styles::detect_stderr();
|
||||||
let cwd = std::env::current_dir()?;
|
let cwd = std::env::current_dir()?;
|
||||||
|
|
||||||
|
|
@ -43,7 +48,8 @@ pub(super) fn list_command(_args: &WorkflowListArgs, globals: &GlobalArgs) -> Re
|
||||||
|
|
||||||
let name_width = workflows.iter().map(|w| w.name.len()).max().unwrap_or(0);
|
let name_width = workflows.iter().map(|w| w.name.len()).max().unwrap_or(0);
|
||||||
|
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
"{} workflow(s) found\n",
|
"{} workflow(s) found\n",
|
||||||
styles.bold.apply_to(workflows.len())
|
styles.bold.apply_to(workflows.len())
|
||||||
);
|
);
|
||||||
|
|
@ -51,9 +57,16 @@ pub(super) fn list_command(_args: &WorkflowListArgs, globals: &GlobalArgs) -> Re
|
||||||
let user_path = user_wf_dir
|
let user_path = user_wf_dir
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.map_or_else(|| "~/.fabro/workflows".to_string(), relative_path);
|
.map_or_else(|| "~/.fabro/workflows".to_string(), relative_path);
|
||||||
print_section("User Workflows", &user_path, &user, name_width, &styles);
|
print_section(
|
||||||
|
"User Workflows",
|
||||||
|
&user_path,
|
||||||
|
&user,
|
||||||
|
name_width,
|
||||||
|
&styles,
|
||||||
|
printer,
|
||||||
|
);
|
||||||
|
|
||||||
eprintln!();
|
fabro_util::printerr!(printer, "");
|
||||||
|
|
||||||
print_section(
|
print_section(
|
||||||
"Project Workflows",
|
"Project Workflows",
|
||||||
|
|
@ -61,6 +74,7 @@ pub(super) fn list_command(_args: &WorkflowListArgs, globals: &GlobalArgs) -> Re
|
||||||
&project,
|
&project,
|
||||||
name_width,
|
name_width,
|
||||||
&styles,
|
&styles,
|
||||||
|
printer,
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -72,18 +86,21 @@ fn print_section(
|
||||||
workflows: &[&WorkflowInfo],
|
workflows: &[&WorkflowInfo],
|
||||||
name_width: usize,
|
name_width: usize,
|
||||||
styles: &Styles,
|
styles: &Styles,
|
||||||
|
printer: Printer,
|
||||||
) {
|
) {
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
"{} {}",
|
"{} {}",
|
||||||
styles.bold.apply_to(title),
|
styles.bold.apply_to(title),
|
||||||
styles.dim.apply_to(format!("({path})")),
|
styles.dim.apply_to(format!("({path})")),
|
||||||
);
|
);
|
||||||
if workflows.is_empty() {
|
if workflows.is_empty() {
|
||||||
eprintln!(" {}", styles.dim.apply_to("(none)"));
|
fabro_util::printerr!(printer, " {}", styles.dim.apply_to("(none)"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
eprintln!();
|
fabro_util::printerr!(printer, "");
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" {:<name_width$} {}",
|
" {:<name_width$} {}",
|
||||||
styles.bold_dim.apply_to("NAME"),
|
styles.bold_dim.apply_to("NAME"),
|
||||||
styles.bold_dim.apply_to("DESCRIPTION"),
|
styles.bold_dim.apply_to("DESCRIPTION"),
|
||||||
|
|
@ -94,7 +111,8 @@ fn print_section(
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.map(|g| truncate_str(g, GOAL_MAX_LEN))
|
.map(|g| truncate_str(g, GOAL_MAX_LEN))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" {:<name_width$} {}",
|
" {:<name_width$} {}",
|
||||||
styles.cyan.apply_to(&w.name),
|
styles.cyan.apply_to(&w.name),
|
||||||
styles.dim.apply_to(goal_str),
|
styles.dim.apply_to(goal_str),
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,17 @@ mod create;
|
||||||
mod list;
|
mod list;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
|
|
||||||
use crate::args::{GlobalArgs, WorkflowCommand, WorkflowNamespace};
|
use crate::args::{GlobalArgs, WorkflowCommand, WorkflowNamespace};
|
||||||
|
|
||||||
pub(crate) fn dispatch(ns: WorkflowNamespace, globals: &GlobalArgs) -> Result<()> {
|
pub(crate) fn dispatch(
|
||||||
|
ns: WorkflowNamespace,
|
||||||
|
globals: &GlobalArgs,
|
||||||
|
printer: Printer,
|
||||||
|
) -> Result<()> {
|
||||||
match ns.command {
|
match ns.command {
|
||||||
WorkflowCommand::List(args) => list::list_command(&args, globals),
|
WorkflowCommand::List(args) => list::list_command(&args, globals, printer),
|
||||||
WorkflowCommand::Create(args) => create::create_command(&args, globals),
|
WorkflowCommand::Create(args) => create::create_command(&args, globals, printer),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#![allow(clippy::print_stdout, clippy::print_stderr, clippy::exit)]
|
#![allow(clippy::exit)]
|
||||||
|
|
||||||
mod args;
|
mod args;
|
||||||
mod command_context;
|
mod command_context;
|
||||||
|
|
@ -53,6 +53,7 @@ impl Cli {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::print_stderr, reason = "fatal error reporting before exit")]
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
tel_panic::install_panic_hook();
|
tel_panic::install_panic_hook();
|
||||||
|
|
@ -119,7 +120,7 @@ async fn main_inner() -> (String, Result<()>) {
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
|
|
||||||
let Cli { globals, command } = cli;
|
let Cli { globals, command } = cli;
|
||||||
let _printer = Printer::from_flags(globals.quiet, globals.verbose);
|
let printer = Printer::from_flags(globals.quiet, globals.verbose);
|
||||||
let command_name = command.name().to_string();
|
let command_name = command.name().to_string();
|
||||||
|
|
||||||
let (config_log_level, upgrade_check_enabled) = {
|
let (config_log_level, upgrade_check_enabled) = {
|
||||||
|
|
@ -165,7 +166,7 @@ async fn main_inner() -> (String, Result<()>) {
|
||||||
};
|
};
|
||||||
if let Err(err) = logging::init_tracing(globals.debug, config_log_level.as_deref(), log_prefix)
|
if let Err(err) = logging::init_tracing(globals.debug, config_log_level.as_deref(), log_prefix)
|
||||||
{
|
{
|
||||||
eprintln!("Warning: failed to initialize logging: {err:#}");
|
fabro_util::printerr!(printer, "Warning: failed to initialize logging: {err:#}");
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!(command = %command_name, "CLI command started");
|
debug!(command = %command_name, "CLI command started");
|
||||||
|
|
@ -177,33 +178,43 @@ async fn main_inner() -> (String, Result<()>) {
|
||||||
| Commands::Repo(_)
|
| Commands::Repo(_)
|
||||||
| Commands::Install(_)
|
| Commands::Install(_)
|
||||||
) {
|
) {
|
||||||
commands::upgrade::spawn_upgrade_check(globals.no_upgrade_check, upgrade_check_enabled)
|
commands::upgrade::spawn_upgrade_check(
|
||||||
|
globals.no_upgrade_check,
|
||||||
|
upgrade_check_enabled,
|
||||||
|
printer,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = Box::pin(async move {
|
let result = Box::pin(async move {
|
||||||
match *command {
|
match *command {
|
||||||
Commands::Exec(args) => commands::exec::execute(args, &globals).await?,
|
Commands::Exec(args) => commands::exec::execute(args, &globals, printer).await?,
|
||||||
Commands::RunCmd(cmd) => Box::pin(commands::run::dispatch(cmd, &globals)).await?,
|
Commands::RunCmd(cmd) => {
|
||||||
Commands::Preflight(args) => commands::preflight::execute(args, &globals).await?,
|
Box::pin(commands::run::dispatch(cmd, &globals, printer)).await?;
|
||||||
|
}
|
||||||
|
Commands::Preflight(args) => {
|
||||||
|
commands::preflight::execute(args, &globals, printer).await?;
|
||||||
|
}
|
||||||
Commands::Validate(args) => {
|
Commands::Validate(args) => {
|
||||||
let styles = Styles::detect_stderr();
|
let styles = Styles::detect_stderr();
|
||||||
commands::validate::run(&args, &styles, &globals).await?;
|
commands::validate::run(&args, &styles, &globals, printer).await?;
|
||||||
}
|
}
|
||||||
Commands::Graph(args) => {
|
Commands::Graph(args) => {
|
||||||
let styles = Styles::detect_stderr();
|
let styles = Styles::detect_stderr();
|
||||||
commands::graph::run(&args, &styles, &globals).await?;
|
commands::graph::run(&args, &styles, &globals, printer).await?;
|
||||||
}
|
}
|
||||||
Commands::Parse(args) => {
|
Commands::Parse(args) => {
|
||||||
commands::parse::run(&args, &globals)?;
|
commands::parse::run(&args, &globals, printer)?;
|
||||||
|
}
|
||||||
|
Commands::Artifact(ns) => commands::artifact::dispatch(ns, &globals, printer).await?,
|
||||||
|
Commands::Store(ns) => commands::store::dispatch(ns, &globals, printer).await?,
|
||||||
|
Commands::RunsCmd(cmd) => commands::runs::dispatch(cmd, &globals, printer).await?,
|
||||||
|
Commands::Model { command } => {
|
||||||
|
commands::model::execute(command, &globals, printer).await?;
|
||||||
}
|
}
|
||||||
Commands::Artifact(ns) => commands::artifact::dispatch(ns, &globals).await?,
|
|
||||||
Commands::Store(ns) => commands::store::dispatch(ns, &globals).await?,
|
|
||||||
Commands::RunsCmd(cmd) => commands::runs::dispatch(cmd, &globals).await?,
|
|
||||||
Commands::Model { command } => commands::model::execute(command, &globals).await?,
|
|
||||||
Commands::Server(ns) => {
|
Commands::Server(ns) => {
|
||||||
Box::pin(commands::server::dispatch(ns.command, &globals)).await?;
|
Box::pin(commands::server::dispatch(ns.command, &globals, printer)).await?;
|
||||||
}
|
}
|
||||||
Commands::Doctor(args) => {
|
Commands::Doctor(args) => {
|
||||||
let cli_settings = user_config::load_settings()?;
|
let cli_settings = user_config::load_settings()?;
|
||||||
|
|
@ -212,7 +223,8 @@ async fn main_inner() -> (String, Result<()>) {
|
||||||
.output
|
.output
|
||||||
.verbosity
|
.verbosity
|
||||||
== OutputVerbosity::Verbose;
|
== OutputVerbosity::Verbose;
|
||||||
let exit_code = commands::doctor::run_doctor(&args, verbose, &globals).await?;
|
let exit_code =
|
||||||
|
commands::doctor::run_doctor(&args, verbose, &globals, printer).await?;
|
||||||
std::process::exit(exit_code);
|
std::process::exit(exit_code);
|
||||||
}
|
}
|
||||||
Commands::Discord => {
|
Commands::Discord => {
|
||||||
|
|
@ -233,25 +245,27 @@ async fn main_inner() -> (String, Result<()>) {
|
||||||
open::that("https://docs.fabro.sh/")?;
|
open::that("https://docs.fabro.sh/")?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Commands::Repo(ns) => commands::repo::dispatch(ns, &globals).await?,
|
Commands::Repo(ns) => commands::repo::dispatch(ns, &globals, printer).await?,
|
||||||
Commands::Install(args) => {
|
Commands::Install(args) => {
|
||||||
commands::install::run_install(&args, &globals).await?;
|
commands::install::run_install(&args, &globals, printer).await?;
|
||||||
}
|
}
|
||||||
Commands::Uninstall(args) => {
|
Commands::Uninstall(args) => {
|
||||||
commands::uninstall::run_uninstall(&args, &globals).await?;
|
commands::uninstall::run_uninstall(&args, &globals, printer).await?;
|
||||||
}
|
}
|
||||||
Commands::Pr(ns) => Box::pin(commands::pr::dispatch(ns, &globals)).await?,
|
Commands::Pr(ns) => Box::pin(commands::pr::dispatch(ns, &globals, printer)).await?,
|
||||||
Commands::Secret(ns) => commands::secret::dispatch(ns, &globals).await?,
|
Commands::Secret(ns) => commands::secret::dispatch(ns, &globals, printer).await?,
|
||||||
Commands::Settings(args) => {
|
Commands::Settings(args) => {
|
||||||
Box::pin(commands::config::execute(&args, &globals)).await?;
|
Box::pin(commands::config::execute(&args, &globals, printer)).await?;
|
||||||
}
|
}
|
||||||
Commands::Workflow(ns) => commands::workflow::dispatch(ns, &globals)?,
|
Commands::Workflow(ns) => commands::workflow::dispatch(ns, &globals, printer)?,
|
||||||
Commands::Upgrade(args) => {
|
Commands::Upgrade(args) => {
|
||||||
commands::upgrade::run_upgrade(args, &globals).await?;
|
commands::upgrade::run_upgrade(args, &globals, printer).await?;
|
||||||
}
|
}
|
||||||
Commands::Provider(ns) => commands::provider::dispatch(ns, &globals).await?,
|
Commands::Provider(ns) => commands::provider::dispatch(ns, &globals, printer).await?,
|
||||||
Commands::Sandbox { command } => commands::sandbox::dispatch(command, &globals).await?,
|
Commands::Sandbox { command } => {
|
||||||
Commands::System(ns) => commands::system::dispatch(ns, &globals).await?,
|
commands::sandbox::dispatch(command, &globals, printer).await?;
|
||||||
|
}
|
||||||
|
Commands::System(ns) => commands::system::dispatch(ns, &globals, printer).await?,
|
||||||
Commands::Completion(args) => {
|
Commands::Completion(args) => {
|
||||||
globals.require_no_json()?;
|
globals.require_no_json()?;
|
||||||
let mut cmd = Cli::command();
|
let mut cmd = Cli::command();
|
||||||
|
|
@ -288,7 +302,7 @@ async fn main_inner() -> (String, Result<()>) {
|
||||||
Commands::TestPanic { message } => {
|
Commands::TestPanic { message } => {
|
||||||
let event = tel_panic::build_event(&message);
|
let event = tel_panic::build_event(&message);
|
||||||
let json = serde_json::to_string_pretty(&event)?;
|
let json = serde_json::to_string_pretty(&event)?;
|
||||||
println!("{json}");
|
fabro_util::printout!(printer, "{json}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ use dialoguer::{Confirm, Password};
|
||||||
use fabro_llm::client::Client as LlmClient;
|
use fabro_llm::client::Client as LlmClient;
|
||||||
use fabro_llm::generate::{GenerateParams, generate};
|
use fabro_llm::generate::{GenerateParams, generate};
|
||||||
use fabro_model::{Catalog, Provider};
|
use fabro_model::{Catalog, Provider};
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use fabro_util::terminal::Styles;
|
use fabro_util::terminal::Styles;
|
||||||
use tokio::task::spawn_blocking;
|
use tokio::task::spawn_blocking;
|
||||||
use tokio::time::timeout;
|
use tokio::time::timeout;
|
||||||
|
|
@ -74,8 +75,12 @@ pub(crate) fn openai_oauth_env_pairs(
|
||||||
|
|
||||||
/// Run the OpenAI OAuth browser flow, falling back to manual API key entry on
|
/// Run the OpenAI OAuth browser flow, falling back to manual API key entry on
|
||||||
/// failure. Returns the env-var pairs to persist.
|
/// failure. Returns the env-var pairs to persist.
|
||||||
pub(crate) async fn run_openai_oauth_or_api_key(s: &Styles) -> Result<Vec<(String, String)>> {
|
pub(crate) async fn run_openai_oauth_or_api_key(
|
||||||
eprintln!(
|
s: &Styles,
|
||||||
|
printer: Printer,
|
||||||
|
) -> Result<Vec<(String, String)>> {
|
||||||
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" {}",
|
" {}",
|
||||||
s.dim.apply_to("Opening browser for OpenAI login...")
|
s.dim.apply_to("Opening browser for OpenAI login...")
|
||||||
);
|
);
|
||||||
|
|
@ -100,7 +105,8 @@ pub(crate) async fn run_openai_oauth_or_api_key(s: &Styles) -> Result<Vec<(Strin
|
||||||
.ok_or_else(|| anyhow::anyhow!("OpenAI did not return a refresh token"))?;
|
.ok_or_else(|| anyhow::anyhow!("OpenAI did not return a refresh token"))?;
|
||||||
let pairs =
|
let pairs =
|
||||||
openai_oauth_env_pairs(&tokens.access_token, refresh_token, account_id.as_deref());
|
openai_oauth_env_pairs(&tokens.access_token, refresh_token, account_id.as_deref());
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" {} OpenAI configured via browser login",
|
" {} OpenAI configured via browser login",
|
||||||
s.green.apply_to("✔")
|
s.green.apply_to("✔")
|
||||||
);
|
);
|
||||||
|
|
@ -108,12 +114,13 @@ pub(crate) async fn run_openai_oauth_or_api_key(s: &Styles) -> Result<Vec<(Strin
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::warn!(error = %e, "OpenAI OAuth browser flow failed");
|
tracing::warn!(error = %e, "OpenAI OAuth browser flow failed");
|
||||||
eprintln!(" Browser login failed: {e}");
|
fabro_util::printerr!(printer, " Browser login failed: {e}");
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" {}",
|
" {}",
|
||||||
s.dim.apply_to("Falling back to manual API key entry.")
|
s.dim.apply_to("Falling back to manual API key entry.")
|
||||||
);
|
);
|
||||||
let (env_var, key) = prompt_and_validate_key(Provider::OpenAi, s).await?;
|
let (env_var, key) = prompt_and_validate_key(Provider::OpenAi, s, printer).await?;
|
||||||
Ok(vec![(env_var, key)])
|
Ok(vec![(env_var, key)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -173,10 +180,12 @@ pub(crate) async fn validate_api_key(provider: Provider, api_key: &str) -> Resul
|
||||||
pub(crate) async fn prompt_and_validate_key(
|
pub(crate) async fn prompt_and_validate_key(
|
||||||
provider: Provider,
|
provider: Provider,
|
||||||
s: &Styles,
|
s: &Styles,
|
||||||
|
printer: Printer,
|
||||||
) -> Result<(String, String)> {
|
) -> Result<(String, String)> {
|
||||||
let env_var = provider.api_key_env_vars()[0];
|
let env_var = provider.api_key_env_vars()[0];
|
||||||
let url = provider_key_url(provider);
|
let url = provider_key_url(provider);
|
||||||
eprintln!(
|
fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
" {}",
|
" {}",
|
||||||
s.dim.apply_to(format!("Get your API key at: {url}"))
|
s.dim.apply_to(format!("Get your API key at: {url}"))
|
||||||
);
|
);
|
||||||
|
|
@ -185,14 +194,14 @@ pub(crate) async fn prompt_and_validate_key(
|
||||||
let prompt = env_var.to_string();
|
let prompt = env_var.to_string();
|
||||||
let key: String = spawn_blocking(move || prompt_password(&prompt)).await??;
|
let key: String = spawn_blocking(move || prompt_password(&prompt)).await??;
|
||||||
|
|
||||||
eprintln!(" {}", s.dim.apply_to("Validating API key..."));
|
fabro_util::printerr!(printer, " {}", s.dim.apply_to("Validating API key..."));
|
||||||
match validate_api_key(provider, &key).await {
|
match validate_api_key(provider, &key).await {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
eprintln!(" {} API key is valid", s.green.apply_to("✔"));
|
fabro_util::printerr!(printer, " {} API key is valid", s.green.apply_to("✔"));
|
||||||
return Ok((env_var.to_string(), key));
|
return Ok((env_var.to_string(), key));
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!(" [error] API key validation failed: {e}");
|
fabro_util::printerr!(printer, " [error] API key validation failed: {e}");
|
||||||
let retry =
|
let retry =
|
||||||
spawn_blocking(|| prompt_confirm("Try again with a different key?", true))
|
spawn_blocking(|| prompt_confirm("Try again with a different key?", true))
|
||||||
.await??;
|
.await??;
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ use std::path::{Path, PathBuf};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use cli_table::Color;
|
use cli_table::Color;
|
||||||
|
use fabro_util::printer::Printer;
|
||||||
use fabro_util::terminal::Styles;
|
use fabro_util::terminal::Styles;
|
||||||
use fabro_validate::{Diagnostic, Severity};
|
use fabro_validate::{Diagnostic, Severity};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
@ -23,7 +24,7 @@ where
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn print_diagnostics(diagnostics: &[Diagnostic], styles: &Styles) {
|
pub(crate) fn print_diagnostics(diagnostics: &[Diagnostic], styles: &Styles, printer: Printer) {
|
||||||
for d in diagnostics {
|
for d in diagnostics {
|
||||||
let location = match (&d.node_id, &d.edge) {
|
let location = match (&d.node_id, &d.edge) {
|
||||||
(Some(node), _) => format!(" [node: {node}]"),
|
(Some(node), _) => format!(" [node: {node}]"),
|
||||||
|
|
@ -31,19 +32,22 @@ pub(crate) fn print_diagnostics(diagnostics: &[Diagnostic], styles: &Styles) {
|
||||||
_ => String::new(),
|
_ => String::new(),
|
||||||
};
|
};
|
||||||
match d.severity {
|
match d.severity {
|
||||||
Severity::Error => eprintln!(
|
Severity::Error => fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
"{}{location}: {} ({})",
|
"{}{location}: {} ({})",
|
||||||
styles.red.apply_to("error"),
|
styles.red.apply_to("error"),
|
||||||
d.message,
|
d.message,
|
||||||
styles.dim.apply_to(&d.rule),
|
styles.dim.apply_to(&d.rule),
|
||||||
),
|
),
|
||||||
Severity::Warning => eprintln!(
|
Severity::Warning => fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
"{}{location}: {} ({})",
|
"{}{location}: {} ({})",
|
||||||
styles.yellow.apply_to("warning"),
|
styles.yellow.apply_to("warning"),
|
||||||
d.message,
|
d.message,
|
||||||
styles.dim.apply_to(&d.rule),
|
styles.dim.apply_to(&d.rule),
|
||||||
),
|
),
|
||||||
Severity::Info => eprintln!(
|
Severity::Info => fabro_util::printerr!(
|
||||||
|
printer,
|
||||||
"{}",
|
"{}",
|
||||||
styles
|
styles
|
||||||
.dim
|
.dim
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,31 @@
|
||||||
|
/// Like `println!`, but respects the `Printer`'s verbosity level.
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// printout!(printer, "Created workflow: {name}");
|
||||||
|
/// ```
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! printout {
|
||||||
|
($printer:expr, $($arg:tt)*) => {{
|
||||||
|
use ::std::fmt::Write as _;
|
||||||
|
let mut out = $printer.stdout_important();
|
||||||
|
let _ = writeln!(out, $($arg)*);
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Like `eprintln!`, but respects the `Printer`'s verbosity level.
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// printerr!(printer, "Connecting to {url}…");
|
||||||
|
/// ```
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! printerr {
|
||||||
|
($printer:expr, $($arg:tt)*) => {{
|
||||||
|
use ::std::fmt::Write as _;
|
||||||
|
let mut out = $printer.stderr();
|
||||||
|
let _ = writeln!(out, $($arg)*);
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum Printer {
|
pub enum Printer {
|
||||||
/// Suppresses all output.
|
/// Suppresses all output.
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue