diff --git a/.changeset/funny-waves-confess.md b/.changeset/funny-waves-confess.md new file mode 100644 index 000000000..c52988e1a --- /dev/null +++ b/.changeset/funny-waves-confess.md @@ -0,0 +1,5 @@ +--- +'@lagon/cli': minor +--- + +Introduce a brand new design diff --git a/.changeset/great-cars-design.md b/.changeset/great-cars-design.md new file mode 100644 index 000000000..e59c2d9d6 --- /dev/null +++ b/.changeset/great-cars-design.md @@ -0,0 +1,5 @@ +--- +'@lagon/cli': patch +--- + +Pre-select confirmation inputs diff --git a/.changeset/tame-planets-wave.md b/.changeset/tame-planets-wave.md new file mode 100644 index 000000000..8dc07b316 --- /dev/null +++ b/.changeset/tame-planets-wave.md @@ -0,0 +1,5 @@ +--- +'@lagon/cli': patch +--- + +Improve UX by showing file errors instead of exiting immediately diff --git a/Cargo.lock b/Cargo.lock index c8923b2df..833e8ecfa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -193,17 +193,6 @@ dependencies = [ "url", ] -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -618,17 +607,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" -[[package]] -name = "colored" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" -dependencies = [ - "atty", - "lazy_static", - "winapi", -] - [[package]] name = "combine" version = "4.6.6" @@ -645,16 +623,15 @@ dependencies = [ [[package]] name = "console" -version = "0.15.1" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89eab4d20ce20cea182308bca13088fecea9c05f6776cf287205d41a0ed3c847" +checksum = "c3d79fbe8970a77e3e34151cc13d3b3e248aa0faaecb9f6091fa07ebefe5ad60" dependencies = [ "encode_unicode", + "lazy_static", "libc", - "once_cell", - "terminal_size", "unicode-width", - "winapi", + "windows-sys 0.42.0", ] [[package]] @@ -1622,7 +1599,6 @@ dependencies = [ "anyhow", "chrono", "clap", - "colored", "dialoguer", "dirs 5.0.1", "envfile", @@ -1635,6 +1611,7 @@ dependencies = [ "lagon-runtime-isolate", "lagon-runtime-utils", "notify", + "once_cell", "pathdiff", "serde", "serde_json", @@ -1794,7 +1771,7 @@ dependencies = [ name = "lagon-wpt-runner" version = "0.1.0" dependencies = [ - "colored", + "console", "flume", "lagon-runtime", "lagon-runtime-http", @@ -3316,16 +3293,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "terminal_size" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "thiserror" version = "1.0.34" diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 8877671a4..487e844e2 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -9,9 +9,8 @@ lagon-runtime-http = { path = "../runtime_http" } lagon-runtime-isolate = { path = "../runtime_isolate" } lagon-runtime-utils = { path = "../runtime_utils" } clap = { version = "4.2.7", features = ["derive"] } -dialoguer = "0.10.4" +dialoguer = { version = "0.10.4", features = ["password"] } indicatif = "0.17.3" -colored = "2.0.0" dirs = "5.0.1" webbrowser = "0.8.9" tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync"] } @@ -27,3 +26,4 @@ notify = "5.1.0" envfile = "0.2.1" anyhow = "1.0.71" urlencoding = "2.1.2" +once_cell = "1.17.1" diff --git a/crates/cli/src/commands/build.rs b/crates/cli/src/commands/build.rs index 5b5a2401c..79e32afa7 100644 --- a/crates/cli/src/commands/build.rs +++ b/crates/cli/src/commands/build.rs @@ -1,8 +1,7 @@ -use std::{fs, path::PathBuf}; - +use crate::utils::{bundle_function, print_progress, resolve_path}; use anyhow::{anyhow, Result}; - -use crate::utils::{bundle_function, debug, print_progress, resolve_path, success}; +use dialoguer::console::style; +use std::{fs, path::PathBuf}; pub fn build( path: Option, @@ -12,33 +11,29 @@ pub fn build( let (root, function_config) = resolve_path(path, client, public_dir)?; let (index, assets) = bundle_function(&function_config, &root, true)?; - let end_progress = print_progress("Writting index.js..."); - - fs::create_dir_all(root.join(".lagon"))?; - fs::write(root.join(".lagon").join("index.js"), index)?; + let end_progress = print_progress("Writting files"); + let root = root.join(".lagon"); - end_progress(); + fs::create_dir_all(&root)?; + fs::write(root.join("index.js"), index)?; for (path, content) in assets { - let message = format!("Writting {path}..."); - let end_progress = print_progress(&message); - - let dir = root.join(".lagon").join("public").join( + let dir = root.join("public").join( PathBuf::from(&path) .parent() .ok_or_else(|| anyhow!("Could not find parent of {}", path))?, ); fs::create_dir_all(dir)?; - fs::write(root.join(".lagon").join("public").join(path), content)?; - - end_progress(); + fs::write(root.join("public").join(path), content)?; } + end_progress(); + println!(); + println!(" {} Build successful!", style("◼").magenta()); println!( - "{} {}", - success("Build successful!"), - debug("You can find it in .lagon folder.") + " {}", + style(format!("You can find it in {:?}", root)).black().bright() ); Ok(()) diff --git a/crates/cli/src/commands/deploy.rs b/crates/cli/src/commands/deploy.rs index 1ee29f460..99ed47968 100644 --- a/crates/cli/src/commands/deploy.rs +++ b/crates/cli/src/commands/deploy.rs @@ -1,16 +1,12 @@ +use crate::utils::{create_deployment, print_progress, resolve_path, Config, TrpcClient, THEME}; +use anyhow::{anyhow, Result}; +use dialoguer::{console::style, Confirm, Input, Select}; +use serde::{Deserialize, Serialize}; use std::{ fmt::{Display, Formatter}, path::PathBuf, }; -use anyhow::{anyhow, Result}; -use dialoguer::{Confirm, Input, Select}; -use serde::{Deserialize, Serialize}; - -use crate::utils::{ - create_deployment, debug, info, print_progress, resolve_path, Config, TrpcClient, -}; - #[derive(Deserialize, Debug)] pub struct Organization { pub name: String, @@ -69,7 +65,10 @@ pub async fn deploy( let (root, mut function_config) = resolve_path(path, client, public_dir)?; if function_config.function_id.is_empty() { - println!("{}", debug("No deployment config found...")); + println!( + "{}", + style("No previous Deployment found...").black().bright() + ); println!(); let trpc_client = TrpcClient::new(config.clone()); @@ -78,15 +77,16 @@ pub async fn deploy( .await?; let organizations = response.result.data; - let index = Select::new() + let index = Select::with_theme(&*THEME) .items(&organizations) .default(0) - .with_prompt(info("Select an Organization to deploy to")) + .with_prompt("Which Organization would you like to deploy to?") .interact()?; let organization = &organizations[index]; - match Confirm::new() - .with_prompt(info("Link to an existing Function?")) + match Confirm::with_theme(&*THEME) + .with_prompt("Link to an existing Function?") + .default(false) .interact()? { true => { @@ -95,10 +95,10 @@ pub async fn deploy( .await?; let functions = response.result.data; - let index = Select::new() + let index = Select::with_theme(&*THEME) .items(&functions) .default(0) - .with_prompt(info("Select a Function to link from")) + .with_prompt("Which Function would you like to link?") .interact()?; let function = &functions[index]; @@ -106,15 +106,16 @@ pub async fn deploy( function_config.organization_id = organization.id.clone(); function_config.write(&root)?; + println!(); create_deployment(config, &function_config, is_production, &root, true).await?; } false => { - let name = Input::::new() - .with_prompt(info("What is the name of this new Function?")) + let name = Input::::with_theme(&*THEME) + .with_prompt("What's the name of this new Function?") .interact_text()?; println!(); - let message = format!("Creating Function {name}..."); + let message = format!("Creating Function {name}"); let end_progress = print_progress(&message); let response = trpc_client diff --git a/crates/cli/src/commands/dev.rs b/crates/cli/src/commands/dev.rs index 4db4e0d8f..bbc91a387 100644 --- a/crates/cli/src/commands/dev.rs +++ b/crates/cli/src/commands/dev.rs @@ -1,6 +1,7 @@ +use crate::utils::{bundle_function, resolve_path, Assets}; use anyhow::{anyhow, Error, Result}; use chrono::offset::Local; -use colored::Colorize; +use dialoguer::console::style; use envfile::EnvFile; use hyper::server::conn::AddrStream; use hyper::service::{make_service_fn, service_fn}; @@ -21,10 +22,6 @@ use std::time::Duration; use tokio::runtime::Handle; use tokio::sync::Mutex; -use crate::utils::{ - bundle_function, debug, error, info, input, resolve_path, success, warn, Assets, -}; - const LOCAL_REGION: &str = "local"; fn parse_environment_variables( @@ -39,12 +36,17 @@ fn parse_environment_variables( for (key, value) in envfile.store { environment_variables.insert(key, value); } + + println!("{}", style("Loaded .env file...").black().bright()); } else if let Ok(envfile) = EnvFile::new(root.join(".env")) { for (key, value) in envfile.store { environment_variables.insert(key, value); } - println!("{}", debug("Automatically loaded .env file...")); + println!( + "{}", + style("Automatically loaded .env file...").black().bright() + ); } Ok(environment_variables) @@ -62,20 +64,19 @@ async fn handle_request( ) -> Result> { let url = req.uri().path(); - println!( - "{} {} {}", - format!("{}", Local::now().time()).bright_black(), - req.method().to_string().blue(), - url - ); - let (tx, rx) = flume::unbounded(); let assets = assets.lock().await.to_owned(); let is_favicon = url == FAVICON_URL; if let Some(asset) = find_asset(url, &assets.keys().cloned().collect()) { - println!(" {}", input("Asset found")); + println!( + "{} {} {} {}", + style(format!("{}", Local::now().time())).black().bright(), + style(req.method().to_string()).blue(), + style(url).black().bright(), + style("(asset)").black().bright() + ); let run_result = match handle_asset(public_dir.unwrap(), asset) { Ok(response) => RunResult::Response(response, None), @@ -94,6 +95,13 @@ async fn handle_request( .await .unwrap_or(()); } else { + println!( + "{} {} {}", + style(format!("{}", Local::now().time())).black().bright(), + style(req.method().to_string()).blue(), + url + ); + match Request::from_hyper(req).await { Ok(mut request) => { request.set_header(X_FORWARDED_FOR.to_string(), ip); @@ -121,22 +129,29 @@ async fn handle_request( match event { ResponseEvent::StreamDoneNoDataError => { println!( - "{}", - error("The stream was done before sending a response/data") + "{} The stream was done before sending a response/data", + style("✕").red() ); } ResponseEvent::UnexpectedStreamResult(result) => { - println!("{} {:?}", error("Unexpected stream result:"), result); + println!( + "{} Unexpected stream result: {:?}", + style("✕").red(), + result + ); } ResponseEvent::LimitsReached(result) => { if result == RunResult::Timeout { - println!("{}", error("Function execution timed out")); + println!("{} Function execution timed out", style("✕").red()); } else { - println!("{}", error("Function execution reached memory limit")); + println!( + "{} Function execution reached memory limit", + style("✕").red() + ); } } ResponseEvent::Error(result) => { - println!("{}", error(result.as_error().as_str())); + println!("{} {}", style("✕").red(), result.as_error().as_str()); } _ => {} } @@ -220,9 +235,9 @@ pub async fn dev( while let Ok(log) = log_receiver.recv_async().await { let (level, message, ..) = log; let level = match level.as_str() { - "error" => "ERROR".red(), - "warn" => "WARN".yellow(), - _ => level.to_uppercase().blue(), + "error" => style("ERROR".into()).red(), + "warn" => style("WARN".into()).yellow(), + _ => style(level.to_uppercase()).blue(), }; println!("{} {}", level, message); @@ -273,12 +288,16 @@ pub async fn dev( if should_update { // Clear the screen and put the cursor at first row & first col of the screen. print!("\x1B[2J\x1B[1;1H"); - println!("{}", info("Found change, updating...")); + println!("{}", style("File modified, updating...").black().bright()); let (new_index, new_assets) = bundle_function(&function_config, &root, prod)?; *assets.lock().await = new_assets; index_tx.send_async(new_index).await.unwrap(); + + println!(); + println!(" {} Dev Server ready!", style("◼").magenta()); + println!(); } } @@ -286,21 +305,23 @@ pub async fn dev( }); println!(); - println!("{}", success("Dev Server started!")); + println!(" {} Dev Server started!", style("◼").magenta()); if allow_code_generation { println!( - "{}", - warn("Code generation is allowed due to `--allow-code-generation`") + " {} {}", + style("Code generation is allowed due to").yellow(), + style("--allow-code-generation").black().bright() ); } println!(); println!( - " {} {}", - "➤".bright_black(), - format!("http://{addr}").blue() + "{} {}", + style("›").black().bright(), + style(format!("http://{addr}")).blue().underlined() ); + println!(); server.await?; runtime.dispose(); diff --git a/crates/cli/src/commands/link.rs b/crates/cli/src/commands/link.rs index ca72e768e..7d1967b58 100644 --- a/crates/cli/src/commands/link.rs +++ b/crates/cli/src/commands/link.rs @@ -1,13 +1,10 @@ -use std::path::PathBuf; - -use anyhow::{anyhow, Result}; - -use dialoguer::Select; - use crate::{ commands::deploy::{FunctionsResponse, OrganizationsResponse}, - utils::{get_root, info, success, Config, FunctionConfig, TrpcClient}, + utils::{get_root, Config, FunctionConfig, TrpcClient, THEME}, }; +use anyhow::{anyhow, Result}; +use dialoguer::{console::style, Select}; +use std::path::PathBuf; pub async fn link(directory: Option) -> Result<()> { let config = Config::new()?; @@ -19,9 +16,10 @@ pub async fn link(directory: Option) -> Result<()> { } let root = get_root(directory); + let function_config = FunctionConfig::load(&root, None, None)?; - match root.join(".lagon").join("config.json").exists() { - true => Err(anyhow!("This directory is already linked to a Function.")), + match !function_config.function_id.is_empty() { + true => Err(anyhow!("This directory is already linked to a Function")), false => { let trpc_client = TrpcClient::new(config); let response = trpc_client @@ -29,10 +27,10 @@ pub async fn link(directory: Option) -> Result<()> { .await?; let organizations = response.result.data; - let index = Select::new() + let index = Select::with_theme(&*THEME) .items(&organizations) .default(0) - .with_prompt(info("Select an Organization to link from")) + .with_prompt("Which Organization would you like to link from?") .interact()?; let organization = &organizations[index]; @@ -41,10 +39,10 @@ pub async fn link(directory: Option) -> Result<()> { .await?; let functions = response.result.data; - let index = Select::new() + let index = Select::with_theme(&*THEME) .items(&functions) .default(0) - .with_prompt(info("Select a Function to link from")) + .with_prompt("Which Function would you like to link?") .interact()?; let function = &functions[index]; @@ -53,7 +51,8 @@ pub async fn link(directory: Option) -> Result<()> { function_config.organization_id = organization.id.clone(); function_config.write(&root)?; - println!("{}", success("Function linked!")); + println!(); + println!(" {} Function linked!", style("◼").magenta()); Ok(()) } diff --git a/crates/cli/src/commands/login.rs b/crates/cli/src/commands/login.rs index abdaddd93..9c19f27be 100644 --- a/crates/cli/src/commands/login.rs +++ b/crates/cli/src/commands/login.rs @@ -1,9 +1,8 @@ +use crate::utils::{print_progress, Config, TrpcClient, THEME}; use anyhow::{anyhow, Result}; -use dialoguer::{Confirm, Password}; +use dialoguer::{console::style, Confirm, Password}; use serde::{Deserialize, Serialize}; -use crate::utils::{debug, error, info, input, print_progress, success, Config, TrpcClient}; - #[derive(Deserialize, Debug)] struct CliResponse { token: String, @@ -18,34 +17,36 @@ pub async fn login() -> Result<()> { let mut config = Config::new()?; if config.token.is_some() - && !Confirm::new() - .with_prompt(info( - "You are already logged in. Are you sure you want to log in again?", - )) + && !Confirm::with_theme(&*THEME) + .with_prompt("You are already logged in. Do you want to log out and log in again?") + .default(true) .interact()? { - return Err(anyhow!("Login aborted.")); + println!(); + println!("{} Login aborted", style("✕").red()); + return Ok(()); } println!(); - let end_progress = print_progress("Opening browser..."); + let end_progress = print_progress("Opening browser"); let url = config.site_url.clone() + "/cli"; if webbrowser::open(&url).is_err() { - println!("{}", error("Couldn't open browser.")); + println!("{} Could not open browser", style("✕").red()); } end_progress(); - - println!(); println!( "{}", - info(&format!("Please copy and paste the verification from your browser. You can also manually visit {}", url)) + style(format!(" You can also manually access {}", url)) + .black() + .bright() ); + println!(); - let code = Password::new() - .with_prompt(input("Verification code")) + let code = Password::with_theme(&*THEME) + .with_prompt("Paste the verification code from your browser here") .interact()?; config.set_token(Some(code.clone())); @@ -62,10 +63,10 @@ pub async fn login() -> Result<()> { config.save()?; println!(); + println!(" {} You are now logged in!", style("◼").magenta()); println!( - "{} {}", - success("You are now logged in."), - debug("You can close your browser tab.") + " {}", + style("You can now close the browser tab").black().bright() ); Ok(()) diff --git a/crates/cli/src/commands/logout.rs b/crates/cli/src/commands/logout.rs index 393f249d3..6b216ad35 100644 --- a/crates/cli/src/commands/logout.rs +++ b/crates/cli/src/commands/logout.rs @@ -1,8 +1,6 @@ +use crate::utils::{Config, THEME}; use anyhow::{anyhow, Result}; - -use dialoguer::Confirm; - -use crate::utils::{info, success, Config}; +use dialoguer::{console::style, Confirm}; pub fn logout() -> Result<()> { let mut config = Config::new()?; @@ -11,8 +9,9 @@ pub fn logout() -> Result<()> { return Err(anyhow!("You are not logged in.")); } - match Confirm::new() - .with_prompt(info("Are you sure you want to log out?")) + match Confirm::with_theme(&*THEME) + .with_prompt("Do you really want to log out?") + .default(true) .interact()? { true => { @@ -20,10 +19,14 @@ pub fn logout() -> Result<()> { config.save()?; println!(); - println!("{}", success("You have been logged out.")); + println!(" {} You have been logged out!", style("◼").magenta()); Ok(()) } - false => Err(anyhow!("Logout aborted.")), + false => { + println!(); + println!("{} Logout aborted", style("✕").red()); + Ok(()) + } } } diff --git a/crates/cli/src/commands/ls.rs b/crates/cli/src/commands/ls.rs index 72c739d35..90e38ba2d 100644 --- a/crates/cli/src/commands/ls.rs +++ b/crates/cli/src/commands/ls.rs @@ -1,11 +1,8 @@ -use std::path::PathBuf; - +use crate::utils::{get_root, print_progress, Config, FunctionConfig, TrpcClient}; use anyhow::{anyhow, Result}; -use colored::Colorize; - +use dialoguer::console::style; use serde::{Deserialize, Serialize}; - -use crate::utils::{error, get_root, print_progress, Config, FunctionConfig, TrpcClient}; +use std::path::PathBuf; #[derive(Deserialize, Debug)] struct Function { @@ -39,7 +36,7 @@ pub async fn ls(directory: Option) -> Result<()> { let root = get_root(directory); let function_config = FunctionConfig::load(&root, None, None)?; - let end_progress = print_progress("Fetching deployments..."); + let end_progress = print_progress("Fetching Deployments"); let function = TrpcClient::new(config) .query::( @@ -52,32 +49,36 @@ pub async fn ls(directory: Option) -> Result<()> { end_progress(); println!(); + println!(" {} List of Deployments:", style("◼").magenta()); + println!(); let deployments = function.result.data.deployments; if deployments.is_empty() { - println!("{}", error("No deployments found.")); + println!("{} No deployments found", style("✕").red()); } else { for deployment in deployments { if deployment.is_production { println!( - "{} {} {}{}, {}{}", - "•".green(), - deployment.id, - "(".bright_black(), - deployment.created_at.bright_black(), - "production".green(), - ")".bright_black() + "{} Production: {} {}", + style("●").black().bright(), + style(format!("https://{}.lagon.dev", deployment.id)) + .blue() + .underlined(), + style(format!("({})", deployment.created_at)) + .black() + .bright(), ); } else { println!( - "{} {} {}{}, {}{}", - "•".blue(), - deployment.id, - "(".bright_black(), - deployment.created_at.bright_black(), - "preview".blue(), - ")".bright_black() + "{} Preview: {} {}", + style("○").black().bright(), + style(format!("https://{}.lagon.dev", deployment.id)) + .blue() + .underlined(), + style(format!("({})", deployment.created_at)) + .black() + .bright(), ); } } diff --git a/crates/cli/src/commands/promote.rs b/crates/cli/src/commands/promote.rs index 2db892c9a..33bbe6657 100644 --- a/crates/cli/src/commands/promote.rs +++ b/crates/cli/src/commands/promote.rs @@ -1,10 +1,8 @@ +use crate::utils::{get_root, print_progress, Config, FunctionConfig, TrpcClient, THEME}; use anyhow::{anyhow, Result}; -use dialoguer::Confirm; -use std::path::PathBuf; - +use dialoguer::{console::style, Confirm}; use serde::{Deserialize, Serialize}; - -use crate::utils::{get_root, info, print_progress, success, Config, FunctionConfig, TrpcClient}; +use std::path::PathBuf; #[derive(Serialize, Debug)] #[serde(rename_all = "camelCase")] @@ -31,14 +29,14 @@ pub async fn promote(deployment_id: String, directory: Option) -> Resul let root = get_root(directory); let function_config = FunctionConfig::load(&root, None, None)?; - match Confirm::new() - .with_prompt(info( - "Are you sure you want to promote this Deployment to production?", - )) + match Confirm::with_theme(&*THEME) + .with_prompt("Do you really want to promote this Deployment to production?") + .default(true) .interact()? { true => { - let end_progress = print_progress("Promoting Deployment..."); + println!(); + let end_progress = print_progress("Promoting Deployment"); TrpcClient::new(config) .mutation::( "deploymentPromote", @@ -51,10 +49,17 @@ pub async fn promote(deployment_id: String, directory: Option) -> Resul end_progress(); println!(); - println!("{}", success("Deployment promoted to production!")); + println!( + " {} Deployment promoted to production!", + style("◼").magenta() + ); Ok(()) } - false => Err(anyhow!("Promotion aborted.")), + false => { + println!(); + println!("{} Promotion aborted", style("✕").red()); + Ok(()) + } } } diff --git a/crates/cli/src/commands/rm.rs b/crates/cli/src/commands/rm.rs index 47fdb72ae..00f11b4af 100644 --- a/crates/cli/src/commands/rm.rs +++ b/crates/cli/src/commands/rm.rs @@ -1,11 +1,8 @@ -use std::path::PathBuf; - +use crate::utils::{get_root, print_progress, Config, FunctionConfig, TrpcClient, THEME}; use anyhow::{anyhow, Result}; - -use dialoguer::Confirm; +use dialoguer::{console::style, Confirm}; use serde::{Deserialize, Serialize}; - -use crate::utils::{get_root, info, print_progress, success, Config, FunctionConfig, TrpcClient}; +use std::path::PathBuf; #[derive(Serialize, Debug)] #[serde(rename_all = "camelCase")] @@ -31,14 +28,15 @@ pub async fn rm(directory: Option) -> Result<()> { let root = get_root(directory); let function_config = FunctionConfig::load(&root, None, None)?; - match Confirm::new() - .with_prompt(info( - "Are you sure you want to completely delete this Function?", - )) + match Confirm::with_theme(&*THEME) + .with_prompt( + "Do you really want to completely delete this Function, its Deployments, statistics and logs?", + ) + .default(false) .interact()? { true => { - let end_progress = print_progress("Deleting Function..."); + let end_progress = print_progress("Deleting Function"); TrpcClient::new(config) .mutation::( "functionDelete", @@ -52,10 +50,14 @@ pub async fn rm(directory: Option) -> Result<()> { function_config.delete(&root)?; println!(); - println!("{}", success("Function deleted.")); + println!(" {} Function deleted!", style("◼").magenta()); Ok(()) } - false => Err(anyhow!("Deletion aborted.")), + false => { + println!(); + println!("{} Deletion aborted", style("✕").red()); + Ok(()) + }, } } diff --git a/crates/cli/src/commands/undeploy.rs b/crates/cli/src/commands/undeploy.rs index cc39da4d9..18af0fdf9 100644 --- a/crates/cli/src/commands/undeploy.rs +++ b/crates/cli/src/commands/undeploy.rs @@ -1,11 +1,8 @@ -use std::path::PathBuf; - +use crate::utils::{get_root, print_progress, Config, FunctionConfig, TrpcClient, THEME}; use anyhow::{anyhow, Result}; -use dialoguer::Confirm; - +use dialoguer::{console::style, Confirm}; use serde::{Deserialize, Serialize}; - -use crate::utils::{get_root, info, print_progress, success, Config, FunctionConfig, TrpcClient}; +use std::path::PathBuf; #[derive(Serialize, Debug)] #[serde(rename_all = "camelCase")] @@ -32,12 +29,13 @@ pub async fn undeploy(deployment_id: String, directory: Option) -> Resu let root = get_root(directory); let function_config = FunctionConfig::load(&root, None, None)?; - match Confirm::new() - .with_prompt(info("Are you sure you want to delete this Deployment?")) + match Confirm::with_theme(&*THEME) + .with_prompt("Do you really want to delete this Deployment?") + .default(false) .interact()? { true => { - let end_progress = print_progress("Deleting Deployment..."); + let end_progress = print_progress("Deleting Deployment"); TrpcClient::new(config) .mutation::( "deploymentUndeploy", @@ -50,10 +48,14 @@ pub async fn undeploy(deployment_id: String, directory: Option) -> Resu end_progress(); println!(); - println!("{}", success("Deployment deleted.")); + println!(" {} Deployment deleted!", style("◼").magenta()); Ok(()) } - false => Err(anyhow!("Deletion aborted.")), + false => { + println!(); + println!("{} Deletion aborted", style("✕").red()); + Ok(()) + } } } diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 23cee34d6..6e69fd80f 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -1,9 +1,7 @@ -use std::{path::PathBuf, process::exit}; - use clap::{Parser, Subcommand}; +use dialoguer::console::style; use serde::Deserialize; - -use crate::utils::error; +use std::{path::PathBuf, process::exit}; mod commands; mod utils; @@ -174,7 +172,7 @@ async fn main() { directory, } => commands::promote(deployment_id, directory).await, } { - println!("{}", error(&err.to_string())); + println!("{} {}", style("✕").red(), err); exit(1); } } else { @@ -183,7 +181,10 @@ async fn main() { println!("{version}"); } _ => { - println!("{}", error("Couldn't extract version from package.json")); + println!( + "{} Couldn't extract version from package.json", + style("✕").red(), + ); exit(1); } } diff --git a/crates/cli/src/utils/console.rs b/crates/cli/src/utils/console.rs index e2fd7a773..2a0174b29 100644 --- a/crates/cli/src/utils/console.rs +++ b/crates/cli/src/utils/console.rs @@ -1,41 +1,59 @@ -use colored::Colorize; +use dialoguer::{ + console::{style, Style}, + theme::ColorfulTheme, +}; use indicatif::{ProgressBar, ProgressStyle}; - -pub fn info(message: &str) -> String { - format!("{} {}", "?".blue(), message) -} - -pub fn input(message: &str) -> String { - format!(" {} {}", "↳".bright_black(), message.bright_black()) -} - -pub fn debug(message: &str) -> String { - message.bright_black().to_string() -} - -pub fn debug_success(message: &str) -> String { - format!("{} {}", "✓".green(), message.bright_black()) -} - -pub fn success(message: &str) -> String { - format!("{} {}", "✓".green(), message) -} - -pub fn error(message: &str) -> String { - format!("{} {}", "✖".red(), message) -} - -pub fn warn(message: &str) -> String { - format!("{} {}", "○".yellow(), message) -} +use once_cell::sync::Lazy; +use std::time::Duration; pub fn print_progress(message: &str) -> impl Fn() + '_ { let index_progress = ProgressBar::new_spinner(); index_progress.set_style(ProgressStyle::default_spinner()); - index_progress.set_message(debug(message)); + index_progress.set_message( + style(format!("{}...", message)) + .black() + .bright() + .to_string(), + ); + index_progress.tick(); + + let handle = index_progress.clone(); + + tokio::task::spawn(async move { + loop { + if handle.is_finished() { + break; + } + + handle.tick(); + tokio::time::sleep(Duration::from_millis(10)).await; + } + }); move || { index_progress.finish_and_clear(); - println!("{}", debug_success(message)); + println!("{} {}", style("✓").green(), style(message).black().bright()); } } + +pub static THEME: Lazy = Lazy::new(|| ColorfulTheme { + defaults_style: Style::new().for_stderr().blue(), + prompt_style: Style::new().for_stderr().bold(), + prompt_prefix: style(" ○".to_string()).for_stderr().magenta(), + prompt_suffix: style("›".to_string()).for_stderr().black().bright(), + success_prefix: style(" ●".to_string()).for_stderr().magenta(), + success_suffix: style("›".to_string()).for_stderr().black().bright(), + error_prefix: style("✕".to_string()).for_stderr().red(), + error_style: Style::new().for_stderr(), + hint_style: Style::new().for_stderr().black().bright(), + values_style: Style::new().for_stderr().blue(), + active_item_style: Style::new().for_stderr().blue(), + inactive_item_style: Style::new().for_stderr(), + active_item_prefix: style("›".to_string()).for_stderr().blue(), + inactive_item_prefix: style(" ".to_string()).for_stderr(), + checked_item_prefix: style("✔".to_string()).for_stderr().green(), + unchecked_item_prefix: style("✔".to_string()).for_stderr().black(), + picked_item_prefix: style("❯".to_string()).for_stderr().green(), + unpicked_item_prefix: style(" ".to_string()).for_stderr(), + inline_selections: true, +}); diff --git a/crates/cli/src/utils/deployments.rs b/crates/cli/src/utils/deployments.rs index 7616a9505..311ead3c8 100644 --- a/crates/cli/src/utils/deployments.rs +++ b/crates/cli/src/utils/deployments.rs @@ -1,7 +1,13 @@ +use super::{ + validate_assets_dir, validate_code_file, Config, MAX_ASSET_SIZE_MB, MAX_FUNCTION_SIZE_MB, +}; +use crate::utils::{print_progress, TrpcClient, THEME}; use anyhow::{anyhow, Result}; -use colored::Colorize; +use dialoguer::console::style; use dialoguer::{Confirm, Input}; use hyper::{Body, Method, Request}; +use pathdiff::diff_paths; +use serde::{Deserialize, Serialize}; use std::sync::Arc; use std::{ collections::HashMap, @@ -12,15 +18,6 @@ use std::{ }; use walkdir::{DirEntry, WalkDir}; -use pathdiff::diff_paths; -use serde::{Deserialize, Serialize}; - -use crate::utils::{debug, info, print_progress, success, TrpcClient}; - -use super::{ - validate_assets_dir, validate_code_file, Config, MAX_ASSET_SIZE_MB, MAX_FUNCTION_SIZE_MB, -}; - pub type Assets = HashMap>; #[cfg(windows)] @@ -49,57 +46,63 @@ impl FunctionConfig { if !path.exists() { println!( "{}", - debug("No configuration found in current directory...") + style("No configuration found in directory...") + .black() + .bright() ); println!(); let index = match client_override { Some(index) => { - println!("{}", debug("Using custom entrypoint...")); + println!("{}", style("Using custom entrypoint...").black().bright()); index } None => { - let index = Input::::new() + let index = Input::::with_theme(&*THEME) .with_prompt(format!( - "{} {}", - info("Path to your Function's entrypoint?"), - debug( - format!("(relative to {:?})", root.canonicalize().unwrap()) - .as_str() - ), + "Path to your Function's entrypoint? {}", + style(format!("(relative to {:?})", root.canonicalize()?)) + .black() + .bright() )) + .validate_with(|input: &String| -> std::result::Result<(), String> { + validate_code_file(&root.join(input), root) + .map_err(|err| err.to_string()) + }) .interact_text()?; + PathBuf::from(index) } }; - validate_code_file(&root.join(&index), root)?; - let assets = match assets_override { Some(assets) => { - println!("{}", debug("Using custom public directory...")); + println!( + "{}", + style("Using custom public directory...").black().bright() + ); Some(assets) } - None => match Confirm::new() - .with_prompt(info("Do you have a public directory to serve assets from?")) + None => match Confirm::with_theme(&*THEME) + .with_prompt("Do you have a public directory to serve assets from?") + .default(false) .interact()? { true => { - let assets = Input::::new() + let assets = Input::::with_theme(&*THEME) .with_prompt(format!( - "{} {}", - info("Path to your Function's public directory?"), - debug( - format!("(relative to {:?})", root.canonicalize().unwrap()) - .as_str() - ), + "Path to your Function's public directory? {}", + style(format!("(relative to {:?})", root.canonicalize()?)) + .black() + .bright(), )) + .validate_with(|input: &String| -> std::result::Result<(), String> { + validate_assets_dir(&Some(root.join(input)), root) + .map_err(|err| err.to_string()) + }) .interact_text()?; - let assets = PathBuf::from(assets); - validate_assets_dir(&Some(root.join(&assets)), root)?; - - Some(assets) + Some(PathBuf::from(assets)) } false => None, }, @@ -114,20 +117,26 @@ impl FunctionConfig { }; config.write(root)?; + println!(); return Ok(config); } + println!("{}", style("Found configuration file...").black().bright()); + let content = fs::read_to_string(path)?; let mut config = serde_json::from_str::(&content)?; if let Some(client_override) = client_override { - println!("{}", debug("Using custom entrypoint...")); + println!("{}", style("Using custom client file...").black().bright()); config.client = Some(client_override); } if let Some(assets_override) = assets_override { - println!("{}", debug("Using custom public directory...")); + println!( + "{}", + style("Using custom public directory...").black().bright() + ); config.assets = Some(assets_override); } @@ -139,6 +148,8 @@ impl FunctionConfig { validate_assets_dir(&config.assets, root)?; + println!(); + Ok(config) } @@ -270,14 +281,14 @@ pub fn bundle_function( }; } - let end_progress = print_progress("Bundling Function handler..."); + let end_progress = print_progress("Bundling Function"); let index_output = esbuild(&function_config.index, root, prod)?; end_progress(); let mut final_assets = Assets::new(); if let Some(client) = &function_config.client { - let end_progress = print_progress("Bundling client file..."); + let end_progress = print_progress("Bundling client file"); let client_output = esbuild(client, root, prod)?; end_progress(); @@ -304,10 +315,7 @@ pub fn bundle_function( if let Some(assets) = &function_config.assets { let assets = root.join(assets); - let msg = format!( - "Found public directory ({:?}), bundling assets...", - assets.canonicalize().unwrap() - ); + let msg = format!("Processing assets ({:?})", assets.canonicalize().unwrap()); let end_progress = print_progress(&msg); let files = WalkDir::new(&assets) @@ -340,7 +348,7 @@ pub fn bundle_function( end_progress(); } else { - println!("{}", debug("No public directory found, skipping...")); + println!("{}", style("Skipping assets...").black().bright()); } Ok((index_output, final_assets)) @@ -390,7 +398,7 @@ pub async fn create_deployment( ) -> Result<()> { let (index, assets) = bundle_function(function_config, root, prod_bundle)?; - let end_progress = print_progress("Creating deployment..."); + let end_progress = print_progress("Creating Deployment"); let trpc_client = Arc::new(TrpcClient::new(config)); let response = trpc_client @@ -418,7 +426,7 @@ pub async fn create_deployment( assets_urls, } = response.result.data; - let end_progress = print_progress("Uploading files..."); + let end_progress = print_progress("Uploading files"); let request = Request::builder() .method(Method::PUT) @@ -454,17 +462,22 @@ pub async fn create_deployment( .await?; println!(); - println!("{}", success("Function deployed!")); + println!(" {} Function deployed!", style("◼").magenta()); if !is_production { - println!("{}", debug("Use --prod to deploy to production")); + println!( + " {}", + style("Append --prod to deploy to production") + .black() + .bright(), + ); } println!(); println!( - " {} {}", - "➤".bright_black(), - response.result.data.url.blue() + "{} {}", + style("›").black().bright(), + style(response.result.data.url).blue().underlined() ); Ok(()) diff --git a/crates/cli/src/utils/mod.rs b/crates/cli/src/utils/mod.rs index ee0f7d33d..ab84e9e3e 100644 --- a/crates/cli/src/utils/mod.rs +++ b/crates/cli/src/utils/mod.rs @@ -1,11 +1,11 @@ +use anyhow::{anyhow, Result}; +use std::path::{Path, PathBuf}; + mod config; mod console; mod deployments; mod trpc; -use std::path::{Path, PathBuf}; - -use anyhow::{anyhow, Result}; pub use config::*; pub use console::*; pub use deployments::*; diff --git a/crates/cli/src/utils/trpc.rs b/crates/cli/src/utils/trpc.rs index e50c0a5dd..7f27282c3 100644 --- a/crates/cli/src/utils/trpc.rs +++ b/crates/cli/src/utils/trpc.rs @@ -1,11 +1,10 @@ +use super::Config; use anyhow::{anyhow, Result}; use hyper::{body, client::HttpConnector, Body, Client, Method, Request}; use hyper_tls::HttpsConnector; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use urlencoding::encode; -use super::Config; - #[derive(Deserialize, Debug)] pub struct TrpcResponse { pub result: TrpcResult, @@ -28,7 +27,7 @@ pub struct TrpcErrorResult { pub struct TrpcClient { pub client: Client>, - config: Config, + config: Config, } impl TrpcClient { diff --git a/crates/runtime_http/src/lib.rs b/crates/runtime_http/src/lib.rs index 8d321f415..533e00887 100644 --- a/crates/runtime_http/src/lib.rs +++ b/crates/runtime_http/src/lib.rs @@ -1,6 +1,5 @@ -use std::time::Duration; - use anyhow::Result; +use std::time::Duration; mod headers; mod method; diff --git a/crates/runtime_http/src/request.rs b/crates/runtime_http/src/request.rs index 6c218e5b2..4f80a4d12 100644 --- a/crates/runtime_http/src/request.rs +++ b/crates/runtime_http/src/request.rs @@ -1,18 +1,13 @@ +use super::{FromV8, IntoV8, Method}; +use crate::{Headers, X_LAGON_ID}; use anyhow::{anyhow, Result}; use hyper::{ body::{self, Bytes}, - header::HeaderName, - http::{self, HeaderValue}, - Body, Request as HyperRequest, + http, Body, Request as HyperRequest, }; use lagon_runtime_v8_utils::{ extract_v8_headers_object, extract_v8_string, v8_headers_object, v8_string, }; -use std::str::FromStr; - -use crate::{Headers, X_LAGON_ID}; - -use super::{FromV8, IntoV8, Method}; #[derive(Debug)] pub struct Request { diff --git a/crates/runtime_http/src/response.rs b/crates/runtime_http/src/response.rs index 334aade6d..5fc6892ea 100644 --- a/crates/runtime_http/src/response.rs +++ b/crates/runtime_http/src/response.rs @@ -1,3 +1,4 @@ +use crate::{FromV8, Headers, IntoV8}; use anyhow::{anyhow, Result}; use hyper::{ body::{self, Bytes}, @@ -8,8 +9,6 @@ use lagon_runtime_v8_utils::{ v8_integer, v8_string, }; -use crate::{FromV8, Headers, IntoV8}; - static READABLE_STREAM_STR: &[u8] = b"[object ReadableStream]"; #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/crates/wpt-runner/Cargo.toml b/crates/wpt-runner/Cargo.toml index ab8453168..db7fc7205 100644 --- a/crates/wpt-runner/Cargo.toml +++ b/crates/wpt-runner/Cargo.toml @@ -9,5 +9,5 @@ lagon-runtime = { path = "../runtime" } lagon-runtime-http = { path = "../runtime_http" } lagon-runtime-isolate = { path = "../runtime_isolate" } flume = "0.10.14" -colored = "2.0.0" +console = "0.15.5" once_cell = "1.17.1" diff --git a/crates/wpt-runner/src/main.rs b/crates/wpt-runner/src/main.rs index 2b482eb0e..e9ab620f0 100644 --- a/crates/wpt-runner/src/main.rs +++ b/crates/wpt-runner/src/main.rs @@ -1,4 +1,4 @@ -use colored::*; +use console::style; use lagon_runtime::{options::RuntimeOptions, Runtime}; use lagon_runtime_http::{Request, RunResult}; use lagon_runtime_isolate::{options::IsolateOptions, Isolate, IsolateEvent, IsolateRequest}; @@ -79,11 +79,11 @@ async fn run_test(path: &Path) { } if SKIP_TESTS.iter().any(|&s| display.ends_with(s)) { - println!("{} {}", "Skipping".yellow(), display); + println!("{} {}", style("Skipping").yellow(), display); return; } - println!("{} {}", "Running".blue(), display); + println!("{} {}", style("Running").blue(), display); let code = fs::read_to_string(path).expect("Failed to read file"); @@ -128,10 +128,10 @@ export function handler() {{ if content.starts_with("TEST DONE 0") { RESULT.lock().unwrap().1 += 1; - println!("{}", content.green()); + println!("{}", style(content).green()); } else if content.starts_with("TEST DONE 1") { RESULT.lock().unwrap().2 += 1; - println!("{}", content.red()); + println!("{}", style(content).red()); } else if content.starts_with("TEST START") { RESULT.lock().unwrap().0 += 1; } @@ -207,8 +207,8 @@ async fn main() { println!( "{} tests, {} passed, {} failed", result.0, - result.1.to_string().green(), - result.2.to_string().red() + style(result.1.to_string()).green(), + style(result.2.to_string()).red() ); if result.2 == 0 {