forked from NationTech/harmony
fix(cli): reduce noise & better track progress within Harmony
This commit is contained in:
@@ -15,3 +15,7 @@ current_platform = "0.2.0"
|
||||
futures-util = "0.3.31"
|
||||
serde_json = "1.0.140"
|
||||
cargo_metadata = "0.20.0"
|
||||
harmony = { path = "../harmony" }
|
||||
indicatif = "0.18.0"
|
||||
console = "0.16.0"
|
||||
lazy_static = "1.5.0"
|
||||
|
||||
84
harmony_composer/src/cli_logger.rs
Normal file
84
harmony_composer/src/cli_logger.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
use console::Emoji;
|
||||
use harmony::instrumentation::{self, HarmonyEvent};
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
use lazy_static::lazy_static;
|
||||
use log::error;
|
||||
use std::{
|
||||
sync::{Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
static EMOJI_HARMONY: Emoji<'_, '_> = Emoji("🎼", "");
|
||||
static EMOJI_SUCCESS: Emoji<'_, '_> = Emoji("✅", "");
|
||||
static EMOJI_ERROR: Emoji<'_, '_> = Emoji("⚠️", "");
|
||||
static EMOJI_DEPLOY: Emoji<'_, '_> = Emoji("🚀", "");
|
||||
static EMOJI_TOPOLOGY: Emoji<'_, '_> = Emoji("📦", "");
|
||||
|
||||
lazy_static! {
|
||||
pub static ref SPINNER_STYLE: ProgressStyle = ProgressStyle::default_spinner()
|
||||
.template(" {spinner:.green} {msg}")
|
||||
.unwrap()
|
||||
.tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]);
|
||||
pub static ref SUCCESS_SPINNER_STYLE: ProgressStyle = SPINNER_STYLE
|
||||
.clone()
|
||||
.tick_strings(&[format!("{}", EMOJI_SUCCESS).as_str()]);
|
||||
pub static ref ERROR_SPINNER_STYLE: ProgressStyle = SPINNER_STYLE
|
||||
.clone()
|
||||
.tick_strings(&[format!("{}", EMOJI_ERROR).as_str()]);
|
||||
}
|
||||
|
||||
pub async fn init() {
|
||||
instrumentation::subscribe("CLI Logger", {
|
||||
let current_spinner = Arc::new(Mutex::new(None::<ProgressBar>));
|
||||
|
||||
move |event| {
|
||||
let spinner_clone = Arc::clone(¤t_spinner);
|
||||
|
||||
async move {
|
||||
let mut spinner_guard = spinner_clone.lock().unwrap();
|
||||
|
||||
match event {
|
||||
HarmonyEvent::ProjectInitializationStarted => {
|
||||
println!("{} Initializing Harmony project...", EMOJI_HARMONY);
|
||||
}
|
||||
HarmonyEvent::ProjectInitialized => println!("\n"),
|
||||
HarmonyEvent::ProjectCompilationStarted { details } => {
|
||||
let progress = ProgressBar::new_spinner();
|
||||
progress.set_style(SPINNER_STYLE.clone());
|
||||
progress.set_message(details);
|
||||
progress.enable_steady_tick(Duration::from_millis(100));
|
||||
*spinner_guard = Some(progress);
|
||||
}
|
||||
HarmonyEvent::ProjectCompiled => {
|
||||
if let Some(progress) = spinner_guard.take() {
|
||||
progress.set_style(SUCCESS_SPINNER_STYLE.clone());
|
||||
progress.finish_with_message("project compiled");
|
||||
}
|
||||
}
|
||||
HarmonyEvent::ProjectCompilationFailed { details } => {
|
||||
if let Some(progress) = spinner_guard.take() {
|
||||
progress.set_style(ERROR_SPINNER_STYLE.clone());
|
||||
progress.finish_with_message("failed to compile project");
|
||||
error!("{details}");
|
||||
}
|
||||
}
|
||||
HarmonyEvent::DeploymentStarted { target } => {
|
||||
println!("{} Starting deployment to {target}...", EMOJI_DEPLOY);
|
||||
}
|
||||
HarmonyEvent::DeploymentCompleted { details } => println!("\n"),
|
||||
HarmonyEvent::PrepareTopologyStarted { name } => {
|
||||
println!("{} Preparing environment: {name}...", EMOJI_TOPOLOGY);
|
||||
}
|
||||
HarmonyEvent::Shutdown => {
|
||||
if let Some(progress) = spinner_guard.take() {
|
||||
progress.abandon();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
@@ -7,12 +7,15 @@ use bollard::secret::HostConfig;
|
||||
use cargo_metadata::{Artifact, Message, MetadataCommand};
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
use futures_util::StreamExt;
|
||||
use log::info;
|
||||
use harmony::instrumentation::{self, HarmonyEvent};
|
||||
use log::{debug, info, log_enabled};
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, Stdio};
|
||||
use tokio::fs;
|
||||
|
||||
mod cli_logger;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(version, about, long_about = None, flatten_help = true, propagate_version = true)]
|
||||
struct GlobalArgs {
|
||||
@@ -67,12 +70,15 @@ struct AllArgs {
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
env_logger::init();
|
||||
let cli_logger_handle = tokio::spawn(cli_logger::init());
|
||||
let cli_args = GlobalArgs::parse();
|
||||
|
||||
let harmony_path = Path::new(&cli_args.harmony_path)
|
||||
.try_exists()
|
||||
.expect("couldn't check if path exists");
|
||||
|
||||
instrumentation::instrument(HarmonyEvent::ProjectInitializationStarted);
|
||||
|
||||
let harmony_bin_path: PathBuf = match harmony_path {
|
||||
true => {
|
||||
compile_harmony(
|
||||
@@ -85,6 +91,8 @@ async fn main() {
|
||||
false => todo!("implement autodetect code"),
|
||||
};
|
||||
|
||||
instrumentation::instrument(HarmonyEvent::ProjectInitialized);
|
||||
|
||||
match cli_args.command {
|
||||
Some(command) => match command {
|
||||
Commands::Check(args) => {
|
||||
@@ -116,19 +124,27 @@ async fn main() {
|
||||
}
|
||||
Commands::Deploy(args) => {
|
||||
let deploy = if args.staging {
|
||||
instrumentation::instrument(HarmonyEvent::DeploymentStarted {
|
||||
target: "staging".to_string(),
|
||||
});
|
||||
todo!("implement staging deployment")
|
||||
} else if args.prod {
|
||||
instrumentation::instrument(HarmonyEvent::DeploymentStarted {
|
||||
target: "prod".to_string(),
|
||||
});
|
||||
todo!("implement prod deployment")
|
||||
} else {
|
||||
instrumentation::instrument(HarmonyEvent::DeploymentStarted {
|
||||
target: "dev".to_string(),
|
||||
});
|
||||
Command::new(harmony_bin_path).arg("-y").arg("-a").spawn()
|
||||
}
|
||||
.expect("failed to run harmony deploy");
|
||||
|
||||
let deploy_output = deploy.wait_with_output().unwrap();
|
||||
println!(
|
||||
"deploy output: {}",
|
||||
String::from_utf8(deploy_output.stdout).expect("couldn't parse from utf8")
|
||||
);
|
||||
instrumentation::instrument(HarmonyEvent::DeploymentCompleted {
|
||||
details: String::from_utf8(deploy_output.stdout).unwrap(),
|
||||
});
|
||||
}
|
||||
Commands::All(_args) => todo!(
|
||||
"take all previous match arms and turn them into separate functions, and call them all one after the other"
|
||||
@@ -137,6 +153,10 @@ async fn main() {
|
||||
},
|
||||
None => todo!("run interactively, ask for info on CLI"),
|
||||
}
|
||||
|
||||
instrumentation::instrument(HarmonyEvent::Shutdown);
|
||||
|
||||
let _ = tokio::try_join!(cli_logger_handle);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, clap::ValueEnum)]
|
||||
@@ -166,17 +186,30 @@ async fn compile_harmony(
|
||||
Some(m) => m,
|
||||
None => {
|
||||
if cargo_exists {
|
||||
return compile_cargo(platform, harmony_location).await;
|
||||
CompileMethod::LocalCargo
|
||||
} else {
|
||||
return compile_docker(platform, harmony_location).await;
|
||||
CompileMethod::Docker
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
match method {
|
||||
CompileMethod::LocalCargo => return compile_cargo(platform, harmony_location).await,
|
||||
CompileMethod::Docker => return compile_docker(platform, harmony_location).await,
|
||||
let path = match method {
|
||||
CompileMethod::LocalCargo => {
|
||||
instrumentation::instrument(HarmonyEvent::ProjectCompilationStarted {
|
||||
details: "compiling project with cargo".to_string(),
|
||||
});
|
||||
compile_cargo(platform, harmony_location).await
|
||||
}
|
||||
CompileMethod::Docker => {
|
||||
instrumentation::instrument(HarmonyEvent::ProjectCompilationStarted {
|
||||
details: "compiling project with docker".to_string(),
|
||||
});
|
||||
compile_docker(platform, harmony_location).await
|
||||
}
|
||||
};
|
||||
|
||||
instrumentation::instrument(HarmonyEvent::ProjectCompiled);
|
||||
path
|
||||
}
|
||||
|
||||
// TODO: make sure this works with cargo workspaces
|
||||
@@ -186,6 +219,12 @@ async fn compile_cargo(platform: String, harmony_location: String) -> PathBuf {
|
||||
.exec()
|
||||
.unwrap();
|
||||
|
||||
let stderr = if log_enabled!(log::Level::Debug) {
|
||||
Stdio::inherit()
|
||||
} else {
|
||||
Stdio::piped()
|
||||
};
|
||||
|
||||
let mut cargo_build = Command::new("cargo")
|
||||
.current_dir(&harmony_location)
|
||||
.args(vec![
|
||||
@@ -195,6 +234,7 @@ async fn compile_cargo(platform: String, harmony_location: String) -> PathBuf {
|
||||
"--message-format=json-render-diagnostics",
|
||||
])
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(stderr)
|
||||
.spawn()
|
||||
.expect("run cargo command failed");
|
||||
|
||||
@@ -210,18 +250,20 @@ async fn compile_cargo(platform: String, harmony_location: String) -> PathBuf {
|
||||
.expect("failed to get root package")
|
||||
.manifest_path
|
||||
{
|
||||
println!("{:?}", artifact);
|
||||
debug!("{:?}", artifact);
|
||||
artifacts.push(artifact);
|
||||
}
|
||||
}
|
||||
Message::BuildScriptExecuted(_script) => (),
|
||||
Message::BuildFinished(finished) => {
|
||||
println!("{:?}", finished);
|
||||
debug!("{:?}", finished);
|
||||
}
|
||||
_ => (), // Unknown message
|
||||
}
|
||||
}
|
||||
|
||||
cargo_build.wait().expect("run cargo command failed");
|
||||
|
||||
let bin = artifacts
|
||||
.last()
|
||||
.expect("no binaries built")
|
||||
@@ -237,7 +279,8 @@ async fn compile_cargo(platform: String, harmony_location: String) -> PathBuf {
|
||||
bin_out = PathBuf::from(format!("{}/harmony", harmony_location));
|
||||
let _copy_res = fs::copy(&bin, &bin_out).await;
|
||||
}
|
||||
return bin_out;
|
||||
|
||||
bin_out
|
||||
}
|
||||
|
||||
async fn compile_docker(platform: String, harmony_location: String) -> PathBuf {
|
||||
|
||||
Reference in New Issue
Block a user