diff --git a/Cargo.lock b/Cargo.lock index bac742953..e93a1cadd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1170,30 +1170,15 @@ dependencies = [ "which 4.4.2", ] -[[package]] -name = "bit-set" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" -dependencies = [ - "bit-vec 0.6.3", -] - [[package]] name = "bit-set" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ - "bit-vec 0.8.0", + "bit-vec", ] -[[package]] -name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" - [[package]] name = "bit-vec" version = "0.8.0" @@ -1465,7 +1450,7 @@ dependencies = [ "bytes 1.9.0", "cargo-component-core", "cargo-config2", - "cargo_metadata 0.19.1", + "cargo_metadata", "clap", "futures", "heck 0.5.0", @@ -1545,20 +1530,6 @@ dependencies = [ "serde", ] -[[package]] -name = "cargo_metadata" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" -dependencies = [ - "camino", - "cargo-platform", - "semver", - "serde", - "serde_json", - "thiserror 1.0.69", -] - [[package]] name = "cargo_metadata" version = "0.19.1" @@ -1865,7 +1836,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68578f196d2a33ff61b27fae256c3164f65e36382648e30666dde05b8cc9dfdf" dependencies = [ "async-trait", - "convert_case 0.6.0", + "convert_case", "json5", "nom", "pathdiff", @@ -1961,12 +1932,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - [[package]] name = "convert_case" version = "0.6.0" @@ -2620,19 +2585,6 @@ dependencies = [ "syn 2.0.96", ] -[[package]] -name = "derive_more" -version = "0.99.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" -dependencies = [ - "convert_case 0.4.0", - "proc-macro2", - "quote", - "rustc_version", - "syn 2.0.96", -] - [[package]] name = "derive_more" version = "1.0.0" @@ -3172,11 +3124,11 @@ checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] name = "fancy-regex" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531e46835a22af56d1e3b66f04844bed63158bc094a628bec1d321d9b4c44bf2" +checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298" dependencies = [ - "bit-set 0.5.3", + "bit-set", "regex-automata 0.4.9", "regex-syntax 0.8.5", ] @@ -3799,7 +3751,7 @@ dependencies = [ "async-trait", "bincode", "bytes 1.9.0", - "cargo_metadata 0.19.1", + "cargo_metadata", "futures-core", "golem-wasm-ast", "golem-wasm-rpc", @@ -3830,7 +3782,7 @@ dependencies = [ "clap_complete", "cli-table", "colored", - "derive_more 1.0.0", + "derive_more", "dirs 5.0.1", "env_logger 0.11.6", "futures-util", @@ -3925,7 +3877,7 @@ dependencies = [ "combine", "console-subscriber", "dashmap", - "derive_more 1.0.0", + "derive_more", "figment", "fred", "git-version", @@ -4082,20 +4034,22 @@ dependencies = [ [[package]] name = "golem-examples" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c0bcbbedbbecc9c66f2349150cbeb1b9e9704958611af624fbf11ea7202c4d9" +checksum = "f0ae4fa06a19377a53f113c47c70724416fd9cf6b9f655749250ded3f81de616" dependencies = [ "Inflector", - "cargo_metadata 0.18.1", + "cargo_metadata", "clap", "colored", "copy_dir", - "derive_more 0.99.18", + "derive_more", "dir-diff", "fancy-regex", "golem-wit", "include_dir", + "itertools 0.14.0", + "nanoid", "once_cell", "regex", "serde", @@ -4111,7 +4065,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b33e98f6cc141902ffcc13d027d0bb9a4d3310e51ff182f67236384e8dfeb3ac" dependencies = [ "clap", - "convert_case 0.6.0", + "convert_case", "fmt", "indexmap 2.7.0", "indoc", @@ -4311,7 +4265,7 @@ dependencies = [ "async-trait", "bigdecimal", "bincode", - "cargo_metadata 0.19.1", + "cargo_metadata", "git-version", "golem-wasm-ast", "poem-openapi", @@ -4433,7 +4387,7 @@ dependencies = [ "cap-fs-ext", "cap-std", "cap-time-ext", - "cargo_metadata 0.19.1", + "cargo_metadata", "chrono", "console-subscriber", "dashmap", @@ -4513,7 +4467,7 @@ dependencies = [ "bincode", "bytes 1.9.0", "console-subscriber", - "derive_more 1.0.0", + "derive_more", "figment", "futures", "futures-util", @@ -4568,7 +4522,7 @@ dependencies = [ "chrono", "conditional-trait-gen", "criterion", - "derive_more 1.0.0", + "derive_more", "fastrand", "figment", "fred", @@ -5573,6 +5527,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.14" @@ -6216,6 +6179,15 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" +[[package]] +name = "nanoid" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" +dependencies = [ + "rand", +] + [[package]] name = "nanorand" version = "0.7.0" @@ -7349,7 +7321,7 @@ dependencies = [ "base64 0.22.1", "bytes 1.9.0", "chrono", - "derive_more 1.0.0", + "derive_more", "futures-util", "humantime", "indexmap 2.7.0", @@ -7681,8 +7653,8 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50" dependencies = [ - "bit-set 0.8.0", - "bit-vec 0.8.0", + "bit-set", + "bit-vec", "bitflags 2.7.0", "lazy_static 1.5.0", "num-traits", diff --git a/golem-cli/Cargo.toml b/golem-cli/Cargo.toml index 81b143e9e..cc2c7fbd8 100644 --- a/golem-cli/Cargo.toml +++ b/golem-cli/Cargo.toml @@ -45,7 +45,7 @@ derive_more = { workspace = true } dirs = "5.0.1" futures-util = { workspace = true } glob = "0.3.1" -golem-examples = "1.1.0" +golem-examples = "1.1.1" h2 = "0.4.7" http = { workspace = true } humansize = { workspace = true } diff --git a/golem-cli/src/command.rs b/golem-cli/src/command.rs index 746c1acef..cf67bfae9 100644 --- a/golem-cli/src/command.rs +++ b/golem-cli/src/command.rs @@ -32,6 +32,8 @@ use api_deployment::ApiDeploymentSubcommand; use clap::{self, Command, Subcommand}; use component::ComponentSubCommand; use golem_common::uri::oss::uri::ComponentUri; +use golem_examples::cli::NameOrLanguage; +use golem_examples::model::{ComponentName, GuestLanguage, GuestLanguageTier, PackageName}; use golem_wasm_rpc_stubgen::App; use plugin::PluginSubcommand; use profile::{ProfileSubCommand, UniversalProfileAdd}; @@ -104,9 +106,30 @@ pub enum StaticSharedCommand { #[command(flatten)] command: diagnose::cli::Command, }, - /// Create a new Golem component from built-in examples - #[command(flatten)] - Examples(golem_examples::cli::Command), + #[command()] + New { + #[command(flatten)] + name_or_language: NameOrLanguage, + + /// The package name of the generated component (in namespace:name format) + #[arg(short, long)] + package_name: Option, + + /// The new component's name + component_name: ComponentName, + }, + + /// Lists the built-in examples available for creating new components + #[command()] + ListExamples { + /// The minimum language tier to include in the list + #[arg(short, long)] + min_tier: Option, + + /// Filter examples by a given guest language + #[arg(short, long, alias = "lang")] + language: Option, + }, } impl CliCommand for StaticSharedCommand { @@ -116,15 +139,14 @@ impl CliCommand for StaticSharedCommand { diagnose(command); Ok(GolemResult::Empty) } - StaticSharedCommand::Examples(golem_examples::cli::Command::ListExamples { - min_tier, - language, - }) => examples::process_list_examples(min_tier, language), - StaticSharedCommand::Examples(golem_examples::cli::Command::New { + StaticSharedCommand::ListExamples { min_tier, language } => { + examples::process_list_examples(min_tier, language) + } + StaticSharedCommand::New { name_or_language, package_name, component_name, - }) => examples::process_new( + } => examples::process_new( name_or_language.example_name(), component_name, package_name, @@ -133,6 +155,8 @@ impl CliCommand for StaticSharedCommand { } } + + /// Commands that are supported by both the OSS and Cloud version #[derive(Subcommand, Debug)] #[command()] diff --git a/golem-cli/src/examples.rs b/golem-cli/src/examples.rs index 39aa9ecb7..2beb76f83 100644 --- a/golem-cli/src/examples.rs +++ b/golem-cli/src/examples.rs @@ -14,24 +14,24 @@ use std::env; +use crate::model::{ExampleDescription, GolemError, GolemResult}; use golem_examples::model::{ ComponentName, ExampleName, ExampleParameters, GuestLanguage, GuestLanguageTier, PackageName, + TargetExistsResolveMode, }; use golem_examples::*; -use crate::model::{ExampleDescription, GolemError, GolemResult}; - pub fn process_new( example_name: ExampleName, component_name: ComponentName, package_name: Option, ) -> Result { - let examples = GolemExamples::list_all_examples(); + let examples = all_standalone_examples(); let example = examples.iter().find(|example| example.name == example_name); match example { Some(example) => { let cwd = env::current_dir().expect("Failed to get current working directory"); - match GolemExamples::instantiate( + match instantiate_example( example, &ExampleParameters { component_name, @@ -39,6 +39,7 @@ pub fn process_new( .unwrap_or(PackageName::from_string("golem:component").unwrap()), target_path: cwd, }, + TargetExistsResolveMode::Fail ) { Ok(instructions) => Ok(GolemResult::Str(instructions.to_string())), Err(err) => GolemResult::err(format!("Failed to instantiate component: {err}")), @@ -54,7 +55,7 @@ pub fn process_list_examples( min_tier: Option, language: Option, ) -> Result { - let examples = GolemExamples::list_all_examples() + let examples = all_standalone_examples() .iter() .filter(|example| match &language { Some(language) => example.language == *language, diff --git a/wasm-rpc-stubgen/src/commands/app.rs b/wasm-rpc-stubgen/src/commands/app.rs index 6122735f7..e2ea8185b 100644 --- a/wasm-rpc-stubgen/src/commands/app.rs +++ b/wasm-rpc-stubgen/src/commands/app.rs @@ -20,6 +20,7 @@ use crate::wit_generate::{ use crate::wit_resolve::{ResolvedWitApplication, WitDepsResolver}; use crate::{commands, naming, WasmRpcOverride}; use anyhow::{anyhow, bail, Context, Error}; +use colored::control::SHOULD_COLORIZE; use colored::Colorize; use glob::{glob_with, MatchOptions}; use golem_wasm_rpc::WASM_RPC_VERSION; @@ -559,36 +560,128 @@ pub fn clean(config: Config) -> anyhow: Ok(()) } -// TODO: collect_custom_commands is not selected_component_names aware yet -pub fn collect_custom_commands( +pub fn print_dynamic_help( config: Config, -) -> anyhow::Result>> { - set_log_output(config.log_output); +) -> anyhow::Result<()> { + static LABEL_SOURCE: &str = "Source"; + static LABEL_SELECTED: &str = "Selected"; + static LABEL_TEMPLATE: &str = "Template"; + static LABEL_PROFILES: &str = "Profiles"; + static LABEL_DEPENDENCIES: &str = "Dependencies"; + + let label_padding = { + [ + &LABEL_SOURCE, + &LABEL_SELECTED, + &LABEL_TEMPLATE, + &LABEL_PROFILES, + &LABEL_DEPENDENCIES, + ] + .map(|label| label.len()) + .into_iter() + .max() + .unwrap_or(0) + + 1 + }; - let (app, _selected_component_names) = to_anyhow( - config.log_output, - "Failed to load application manifest(s), see problems above", - load_app(&config), - )?; + let print_field = |label: &'static str, value: String| { + println!(" {:>::new(); - for profile in &all_profiles { - for command in app.all_custom_commands(profile.as_ref()) { - if !commands.contains_key(command.as_str()) { - commands.insert(command.clone(), BTreeSet::new()); + match profile { + None => { + println!("{}", "Custom commands:".log_color_help_group()) + } + Some(profile) => { + println!( + "{}{}{}", + "Custom commands for ".log_color_help_group(), + profile.as_str().log_color_help_group(), + " profile:".log_color_help_group() + ) } - profile.iter().for_each(|profile| { - commands - .get_mut(command.as_str()) - .unwrap() - .insert(profile.clone()); - }); } + for command in commands { + println!(" {}", command.bold()) + } + println!() } - Ok(commands) + Ok(()) } pub fn custom_command( diff --git a/wasm-rpc-stubgen/src/lib.rs b/wasm-rpc-stubgen/src/lib.rs index 075d75a16..2fe51bdf5 100644 --- a/wasm-rpc-stubgen/src/lib.rs +++ b/wasm-rpc-stubgen/src/lib.rs @@ -33,8 +33,6 @@ use crate::stub::{StubConfig, StubDefinition}; use crate::wit_generate::UpdateCargoToml; use anyhow::Context; use clap::Subcommand; -use colored::Colorize; -use itertools::Itertools; use std::collections::HashSet; use std::marker::PhantomData; use std::path::PathBuf; @@ -194,7 +192,7 @@ pub struct App { #[derive(Subcommand, Debug)] pub enum AppSubCommand { - /// Runs component build steps + /// Run component build steps Build(AppBuildArgs), /// Clean outputs Clean, @@ -290,7 +288,7 @@ pub async fn run_app_command( None => { clap_command.print_help()?; println!(); - print_app_custom_commands_help(config); + print_dynamic_help(config); exit(2); } } @@ -336,34 +334,10 @@ fn app_manifest_sources_to_resolve_mode( } } -fn print_app_custom_commands_help( - mut config: commands::app::Config, -) { +fn print_dynamic_help(mut config: commands::app::Config) { config.log_output = Output::None; - match commands::app::collect_custom_commands(config) { - Ok(commands) => { - if !commands.is_empty() { - println!("{}", "Custom commands:".bold().underline()); - for (command, profiles) in commands { - if profiles.is_empty() { - println!(" {}", command); - } else { - println!( - " {} ({})", - command, - profiles.iter().map(|s| s.to_string()).join(", ") - ); - } - } - println!(); - } - } - Err(err) => { - println!( - "{}\n{:?}", - "Cannot show custom commands:".log_color_warn(), - err - ); - } + + if let Some(err) = commands::app::print_dynamic_help(config).err() { + println!("{}\n{}", "Cannot show dynamic help:".log_color_warn(), err); } } diff --git a/wasm-rpc-stubgen/src/log.rs b/wasm-rpc-stubgen/src/log.rs index b9f361758..37b244b61 100644 --- a/wasm-rpc-stubgen/src/log.rs +++ b/wasm-rpc-stubgen/src/log.rs @@ -253,6 +253,10 @@ pub trait LogColorize { self.as_str().bold() } + fn log_color_help_group(&self) -> ColoredString { + self.as_str().bold().underline() + } + fn log_color_error_highlight(&self) -> ColoredString { self.as_str().bold().red().underline() } diff --git a/wasm-rpc-stubgen/src/model/app.rs b/wasm-rpc-stubgen/src/model/app.rs index a380eda3e..bb9ea0e8e 100644 --- a/wasm-rpc-stubgen/src/model/app.rs +++ b/wasm-rpc-stubgen/src/model/app.rs @@ -6,7 +6,7 @@ use crate::validation::{ValidatedResult, ValidationBuilder}; use crate::{fs, naming}; use serde::{Deserialize, Serialize}; use std::cmp::Ordering; -use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; +use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::fmt::Formatter; use std::fmt::{Debug, Display}; use std::hash::Hash; @@ -232,6 +232,10 @@ impl Application { self.components.keys() } + pub fn has_any_component(&self) -> bool { + !self.components.is_empty() + } + pub fn contains_component(&self, component_name: &ComponentName) -> bool { self.components.contains_key(component_name) } @@ -282,6 +286,36 @@ impl Application { custom_commands } + pub fn all_custom_commands_for_all_profiles( + &self, + ) -> BTreeMap, BTreeSet> { + let mut custom_commands = BTreeMap::, BTreeSet>::new(); + + custom_commands + .entry(None) + .or_default() + .extend(self.custom_commands.keys().cloned()); + + for profile in self.all_option_profiles() { + let profile_commands: &mut BTreeSet = { + if custom_commands.contains_key(&profile) { + custom_commands.get_mut(&profile).unwrap() + } else { + custom_commands.entry(profile.clone()).or_default() + } + }; + + profile_commands.extend(self.component_names().flat_map(|component_name| { + self.component_properties(component_name, profile.as_ref()) + .custom_commands + .keys() + .cloned() + })); + } + + custom_commands + } + pub fn temp_dir(&self) -> PathBuf { match self.temp_dir.as_ref() { Some(temp_dir) => temp_dir.source.as_path().join(&temp_dir.value), @@ -316,9 +350,9 @@ impl Application { .unwrap_or(&self.no_dependencies) } - fn component_profiles(&self, component_name: &ComponentName) -> HashSet { + pub fn component_profiles(&self, component_name: &ComponentName) -> BTreeSet { match &self.component(component_name).properties { - ResolvedComponentProperties::Properties { .. } => HashSet::new(), + ResolvedComponentProperties::Properties { .. } => BTreeSet::new(), ResolvedComponentProperties::Profiles { profiles, .. } => { profiles.keys().cloned().collect() }