Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 70 additions & 66 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ mod shutdown;
use std::{
env,
path::{Path, PathBuf},
sync::{Arc, mpsc::Sender},
sync::mpsc::Sender,
thread::JoinHandle,
};

Expand All @@ -14,19 +14,19 @@ use display::{
render::spin_up_renderer,
};
use sentry::ClientInitGuard;
use superconsole::Line;
use tracing_subscriber::{filter::FilterFn, prelude::*};
use trunk_analytics_cli::{
context::gather_debug_props,
error_report::ErrorReport,
exit_code::ExitCode,
summary_output::{
ErrorSummary, UploadCommand, UploadReport, ValidateReport, github,
json::{self, Report},
text::{SummaryFileText, TextOptions, UploadText},
ErrorSummary, UploadCommand, UploadReport, ValidateReport, github, json,
text::{self, SummaryFileText, TextOptions},
},
test_command::{TestArgs, run_test},
upload_command::{RunUploadOptions, UploadArgs, UploadRunResult, run_upload},
validate_command::{ValidateArgs, ValidateRunResult, run_validate},
upload_command::{RunUploadOptions, UploadArgs, run_upload},
validate_command::{ValidateArgs, run_validate},
};

#[derive(Debug, Parser)]
Expand Down Expand Up @@ -195,75 +195,57 @@ fn main() -> anyhow::Result<()> {
);
close_out_and_exit(exit_code, guard, render_sender, render_handle)
}
MainOutcome::Finished(Ok(RunResult::Upload(run_result))) => {
let text = Arc::new(UploadText {
report: UploadReport::new(&run_result, UploadCommand::Upload),
options: TextOptions::from(&run_result),
});
send_message(
DisplayMessage::Final(text.clone(), String::from("upload display")),
MainOutcome::Finished(Ok(Report::Upload(report, options))) => {
show(
DisplayMessage::Notice,
text::render_notices(&report.notices),
"notices",
&render_sender,
);
write_summary_file(
summary_output_file.as_deref(),
Report::Upload(&text.report),
&render_sender,
);
close_out_and_exit(
text.report.cli_exit_code(),
guard,
render_sender,
render_handle,
)
}
MainOutcome::Finished(Ok(RunResult::Test(run_result))) => {
let text = Arc::new(UploadText {
report: UploadReport::new(&run_result, UploadCommand::Test),
options: TextOptions::from(&run_result),
});
send_message(
DisplayMessage::Final(text.clone(), String::from("test display")),
show(
DisplayMessage::Report,
text::render_upload(&report, &options),
"upload summary",
&render_sender,
);
write_summary_file(
summary_output_file.as_deref(),
Report::Upload(&text.report),
json::Report::Upload(&report),
&render_sender,
);
close_out_and_exit(
text.report.cli_exit_code(),
guard,
render_sender,
render_handle,
)
close_out_and_exit(report.cli_exit_code(), guard, render_sender, render_handle)
}
MainOutcome::Finished(Ok(RunResult::Validate(run_result))) => {
let report = ValidateReport::new(&run_result);
let exit_code = report.exit_code;
send_message(
DisplayMessage::Final(Arc::new(report), String::from("validate display")),
MainOutcome::Finished(Ok(Report::Validate(report))) => {
show(
DisplayMessage::Notice,
text::render_notices(&report.notices),
"notices",
&render_sender,
);
close_out_and_exit(exit_code, guard, render_sender, render_handle)
show(
DisplayMessage::Report,
text::render_validate(&report),
"validate summary",
&render_sender,
);
close_out_and_exit(report.exit_code, guard, render_sender, render_handle)
}
MainOutcome::Finished(Ok(RunResult::SummaryOutputSchema)) => {
MainOutcome::Finished(Ok(Report::SummaryOutputSchema)) => {
println!("{}", serde_json::to_string_pretty(&json::Report::schema())?);
close_out_and_exit(ExitCode::Success, guard, render_sender, render_handle)
}
MainOutcome::Finished(Err(error)) => {
let error = Arc::new(ErrorSummary::new(&ErrorReport::new(
error,
org_url_slug,
None,
)));
let error = ErrorSummary::new(&ErrorReport::new(error, org_url_slug, None));
let exit_code = error.exit_code.unwrap_or(ExitCode::Success);
send_message(
DisplayMessage::Final(error.clone(), String::from("error display")),
show(
DisplayMessage::Notice,
text::render_error(&error),
"error",
&render_sender,
);
write_summary_file(
summary_output_file.as_deref(),
Report::Error(&error),
json::Report::Error(&error),
&render_sender,
);
close_out_and_exit(exit_code, guard, render_sender, render_handle)
Expand All @@ -272,33 +254,49 @@ fn main() -> anyhow::Result<()> {
})
}

fn write_summary_file(path: Option<&Path>, report: Report, render_sender: &Sender<DisplayMessage>) {
fn show(
message: fn(Vec<Line>, String) -> DisplayMessage,
lines: anyhow::Result<Vec<Line>>,
description: &str,
render_sender: &Sender<DisplayMessage>,
) {
match lines {
Ok(lines) => send_message(message(lines, String::from(description)), render_sender),
Err(error) => tracing::error!("Failed to render {description}: {error:?}"),
}
}

fn write_summary_file(
path: Option<&Path>,
report: json::Report,
render_sender: &Sender<DisplayMessage>,
) {
let Some(path) = path else {
return;
};
let text = SummaryFileText::new(path, report.write_file(path));
send_message(
DisplayMessage::Final(Arc::new(text), String::from("summary file display")),
DisplayMessage::Notice(text.render(), String::from("summary file display")),
render_sender,
);
}

/// Outcome of racing the command against termination signals in `main`.
enum MainOutcome {
/// The command ran to completion (successfully or with an error).
Finished(anyhow::Result<RunResult>),
Finished(anyhow::Result<Report>),
/// A termination signal was received first; the payload is the exit code to use.
Interrupted(ExitCode),
}

enum RunResult {
Upload(UploadRunResult),
Test(UploadRunResult),
Validate(ValidateRunResult),
/// What a command found, to show as its summary and take the exit code from.
enum Report {
Upload(Box<UploadReport>, TextOptions),
Validate(ValidateReport),
SummaryOutputSchema,
}

async fn run(cli: Cli, render_sender: Sender<DisplayMessage>) -> anyhow::Result<RunResult> {
async fn run(cli: Cli, render_sender: Sender<DisplayMessage>) -> anyhow::Result<Report> {
tracing::info!(
"Starting trunk-analytics-cli {} (git={}) rustc={}",
env!("CARGO_PKG_VERSION"),
Expand All @@ -315,17 +313,23 @@ async fn run(cli: Cli, render_sender: Sender<DisplayMessage>) -> anyhow::Result<
},
)
.await?;
Ok(RunResult::Upload(result))
Ok(Report::Upload(
Box::new(UploadReport::new(&result, UploadCommand::Upload)),
TextOptions::from(&result),
))
}
Commands::Test(test_args) => {
let result = run_test(test_args, render_sender).await?;
Ok(RunResult::Test(result))
Ok(Report::Upload(
Box::new(UploadReport::new(&result, UploadCommand::Test)),
TextOptions::from(&result),
))
}
Commands::Validate(validate_args) => {
let result = run_validate(validate_args).await?;
Ok(RunResult::Validate(result))
Ok(Report::Validate(ValidateReport::new(&result)))
}
Commands::SummaryOutputSchema => Ok(RunResult::SummaryOutputSchema),
Commands::SummaryOutputSchema => Ok(Report::SummaryOutputSchema),
Commands::GithubAnnotations(..) => {
anyhow::bail!("github-annotations runs before the async runtime starts")
}
Expand Down
79 changes: 19 additions & 60 deletions cli/src/summary_output/text.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
use std::path::{Path, PathBuf};

use display::end_output::EndOutput;
use pluralizer::pluralize;
use superconsole::{
Hyperlink, Line, Lines, Span, SuperConsole,
style::{Attribute, Color, Stylize, style},
};

use super::{
ErrorSummary, FailureGroup, FileValidation, GroupKey, NoticeKind, Outcome, ParseError,
ErrorSummary, FailureGroup, FileValidation, GroupKey, Notice, NoticeKind, Outcome, ParseError,
Quarantining, TestCounts, UploadReport, ValidateReport, ValidationCounts,
};
use crate::{
error_report::{CONNECTION_REFUSED_CONTEXT, ErrorKind, HELP_TEXT, UNAUTHORIZED_CONTEXT},
exit_code::ExitCode,
report_limiting::ValidationReport,
upload_command::{UploadRunResult, get_bundle_upload_id_message},
validate_command::ValidateRunResult,
};

/// Flags that change how much of the summary to show, not what it says.
Expand All @@ -38,23 +36,6 @@ impl From<&UploadRunResult> for TextOptions {
}
}

pub struct UploadText {
pub report: UploadReport,
pub options: TextOptions,
}

impl EndOutput for UploadText {
fn output(&self) -> anyhow::Result<Vec<Line>> {
render_upload(&self.report, &self.options)
}
}

impl EndOutput for ErrorSummary {
fn output(&self) -> anyhow::Result<Vec<Line>> {
render_error(self)
}
}

pub struct SummaryFileText {
path: PathBuf,
error: Option<String>,
Expand Down Expand Up @@ -83,14 +64,12 @@ impl SummaryFileText {
Some(error) => format!("⚠️ Failed to write the summary to {path}: {error}"),
}
}
}

impl EndOutput for SummaryFileText {
fn output(&self) -> anyhow::Result<Vec<Line>> {
Ok(vec![
pub fn render(&self) -> Vec<Line> {
vec![
Line::default(),
Line::from_iter([Span::new_unstyled_lossy(self.message())]),
])
]
}
}

Expand Down Expand Up @@ -181,19 +160,6 @@ pub fn render_upload(report: &UploadReport, options: &TextOptions) -> anyhow::Re
output.push(Line::default());
}

// The console shows tracing only when verbose, so these are shown here or not at all.
for notice in &report.notices {
if matches!(
notice.kind,
NoticeKind::Deprecation | NoticeKind::UploadSkipped
) {
output.push(Line::from_iter([Span::new_styled_lossy(
style(format!("⚠️ {}", notice.message)).with(Color::Yellow),
)]));
output.push(Line::default());
}
}

let bold = |text: String| Span::new_styled(style(text).attribute(Attribute::Bold));
let upload_line = match (&report.upload_url, &report.bundle_upload_id) {
(Some(url), _) => Some(Line::from_iter([
Expand Down Expand Up @@ -567,35 +533,28 @@ fn url_span(url: String, attribute: Attribute, clickable: bool) -> anyhow::Resul
})
}

impl EndOutput for ValidateReport {
fn output(&self) -> anyhow::Result<Vec<Line>> {
render_validate(self)
/// Shown on stderr, apart from the summary.
pub fn render_notices(notices: &[Notice]) -> anyhow::Result<Vec<Line>> {
let mut output: Vec<Line> = Vec::new();
for notice in notices {
let prefix = match notice.kind {
NoticeKind::Deprecation | NoticeKind::UploadSkipped => "⚠️ ",
NoticeKind::BepParseError => "⚠️ BEP file had a parse error: ",
};
output.push(Line::from_iter([
Span::new_unstyled(prefix)?,
Span::new_unstyled_lossy(&notice.message),
]));
}
}

impl EndOutput for ValidateRunResult {
fn output(&self) -> anyhow::Result<Vec<Line>> {
render_validate(&ValidateReport::new(self))
if !output.is_empty() {
output.push(Line::default());
}
Ok(output)
}

pub fn render_validate(report: &ValidateReport) -> anyhow::Result<Vec<Line>> {
let mut output: Vec<Line> = Vec::new();

let bep_parse_errors: Vec<&String> = report
.notices
.iter()
.filter(|notice| notice.kind == NoticeKind::BepParseError)
.map(|notice| &notice.message)
.collect();
if !bep_parse_errors.is_empty() {
output.push(Line::from_iter([
Span::new_unstyled("⚠️ BEP file had parse errors: ")?,
Span::new_unstyled_lossy(format!("{:?}", bep_parse_errors)),
]));
output.push(Line::default());
}

let num_files: usize = report
.matched_files
.iter()
Expand Down
Loading
Loading