diff --git a/golem-cli/src/command.rs b/golem-cli/src/command.rs index cf67bfae9..9023371f7 100644 --- a/golem-cli/src/command.rs +++ b/golem-cli/src/command.rs @@ -106,6 +106,8 @@ pub enum StaticSharedCommand { #[command(flatten)] command: diagnose::cli::Command, }, + + /// Create a new Golem standalone component example project from built-in examples #[command()] New { #[command(flatten)] @@ -119,6 +121,17 @@ pub enum StaticSharedCommand { component_name: ComponentName, }, + /// Add a new Golem component to a project using Golem Application Manifest + #[command()] + NewAppComponent { + /// The component name (and package name) of the generated component (in namespace:name format) + component_name: PackageName, + + /// Component language + #[arg(short, long, alias = "lang")] + language: GuestLanguage, + }, + /// Lists the built-in examples available for creating new components #[command()] ListExamples { @@ -140,23 +153,25 @@ impl CliCommand for StaticSharedCommand { Ok(GolemResult::Empty) } StaticSharedCommand::ListExamples { min_tier, language } => { - examples::process_list_examples(min_tier, language) + examples::list_standalone_examples(min_tier, language) } StaticSharedCommand::New { name_or_language, package_name, component_name, - } => examples::process_new( + } => examples::new( name_or_language.example_name(), component_name, package_name, ), + StaticSharedCommand::NewAppComponent { + component_name, + language, + } => examples::new_app_component(component_name, language), } } } - - /// 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 2beb76f83..94a27af43 100644 --- a/golem-cli/src/examples.rs +++ b/golem-cli/src/examples.rs @@ -16,12 +16,13 @@ use std::env; use crate::model::{ExampleDescription, GolemError, GolemResult}; use golem_examples::model::{ - ComponentName, ExampleName, ExampleParameters, GuestLanguage, GuestLanguageTier, PackageName, - TargetExistsResolveMode, + ComponentName, ComposableAppGroupName, ExampleName, ExampleParameters, GuestLanguage, + GuestLanguageTier, PackageName, TargetExistsResolveMode, }; use golem_examples::*; +use itertools::Itertools; -pub fn process_new( +pub fn new( example_name: ExampleName, component_name: ComponentName, package_name: Option, @@ -39,7 +40,7 @@ pub fn process_new( .unwrap_or(PackageName::from_string("golem:component").unwrap()), target_path: cwd, }, - TargetExistsResolveMode::Fail + TargetExistsResolveMode::Fail, ) { Ok(instructions) => Ok(GolemResult::Str(instructions.to_string())), Err(err) => GolemResult::err(format!("Failed to instantiate component: {err}")), @@ -51,7 +52,7 @@ pub fn process_new( } } -pub fn process_list_examples( +pub fn list_standalone_examples( min_tier: Option, language: Option, ) -> Result { @@ -70,3 +71,43 @@ pub fn process_list_examples( Ok(GolemResult::Ok(Box::new(examples))) } + +pub fn new_app_component( + component_name: PackageName, + language: GuestLanguage, +) -> Result { + let all_examples = all_composable_app_examples(); + + let Some(language_examples) = all_examples.get(&language) else { + return Err(GolemError(format!( + "No template found for {}, currently supported languages: {}", + language, + all_examples.keys().join(", ") + ))); + }; + + let default_examples = language_examples + .get(&ComposableAppGroupName::default()) + .expect("No default template found for the selected language"); + + assert_eq!( + default_examples.components.len(), + 1, + "Expected exactly one default component template" + ); + + let default_component_example = &default_examples.components[0]; + + match add_component_by_example( + default_examples.common.as_ref(), + default_component_example, + &env::current_dir().expect("Failed to get current working directory"), + &component_name, + ) { + Ok(_) => Ok(GolemResult::Str(format!( + "Added new app component {}", + component_name.to_string_with_colon() + ))), + Err(err) => Err(GolemError(format!("Failed to add component: {err}"))), + } +}