55using System . CommandLine . Parsing ;
66using Microsoft . Extensions . DependencyInjection ;
77using Microsoft . Extensions . Hosting ;
8+ using Microsoft . Extensions . Logging ;
89
910namespace Aspire . Cli ;
1011
1112public class Program
1213{
13- private static IHost BuildApplication ( )
14+ private static IHost BuildApplication ( ParseResult parseResult )
1415 {
1516 var builder = Host . CreateApplicationBuilder ( ) ;
17+
18+ var debugOption = parseResult . GetValue < bool > ( "--debug" ) ;
19+
20+ if ( ! debugOption )
21+ {
22+ builder . Logging . ClearProviders ( ) ;
23+ }
24+
1625 builder . Services . AddTransient < AppHostRunner > ( ) ;
26+ builder . Services . AddTransient < DotNetCliRunner > ( ) ;
1727 var app = builder . Build ( ) ;
1828 return app ;
1929 }
2030
2131 private static RootCommand GetRootCommand ( )
2232 {
2333 var rootCommand = new RootCommand ( ".NET Aspire CLI" ) ;
34+ var debugOption = new Option < bool > ( "--debug" , "-d" ) ;
35+ debugOption . Recursive = true ;
36+ rootCommand . Options . Add ( debugOption ) ;
2437 ConfigureDevCommand ( rootCommand ) ;
2538 ConfigurePackCommand ( rootCommand ) ;
39+ ConfigureNewCommand ( rootCommand ) ;
2640 return rootCommand ;
2741 }
2842
@@ -73,7 +87,7 @@ private static void ConfigureDevCommand(Command parentCommand)
7387 command . Options . Add ( projectOption ) ;
7488
7589 command . SetAction ( async ( parseResult , ct ) => {
76- using var app = BuildApplication ( ) ;
90+ using var app = BuildApplication ( parseResult ) ;
7791 _ = app . RunAsync ( ct ) . ConfigureAwait ( false ) ;
7892
7993 var runner = app . Services . GetRequiredService < AppHostRunner > ( ) ;
@@ -116,7 +130,7 @@ private static void ConfigurePackCommand(Command parentCommand)
116130 command . Options . Add ( outputPath ) ;
117131
118132 command . SetAction ( async ( parseResult , ct ) => {
119- using var app = BuildApplication ( ) ;
133+ using var app = BuildApplication ( parseResult ) ;
120134 _ = app . RunAsync ( ct ) . ConfigureAwait ( false ) ;
121135
122136 var runner = app . Services . GetRequiredService < AppHostRunner > ( ) ;
@@ -133,6 +147,103 @@ private static void ConfigurePackCommand(Command parentCommand)
133147 parentCommand . Subcommands . Add ( command ) ;
134148 }
135149
150+ private static void ValidateProjectTemplate ( ArgumentResult result )
151+ {
152+ // TODO: We need to integrate with the template engine to interrogate
153+ // the list of available templates. For now we will just hard-code
154+ // the acceptable options.
155+ //
156+ // Once we integrate with template engine we will also be able to
157+ // interrogate the various options and add them. For now we will
158+ // keep it simple.
159+ string [ ] validTemplates = [
160+ "aspire-starter" ,
161+ "aspire" ,
162+ "aspire-apphost" ,
163+ "aspire-servicedefaults" ,
164+ "aspire-mstest" ,
165+ "aspire-nunit" ,
166+ "aspire-xunit"
167+ ] ;
168+
169+ var value = result . GetValueOrDefault < string > ( ) ;
170+
171+ if ( value is null )
172+ {
173+ // This is OK, for now we will use the default
174+ // template of aspire-starter, but we might
175+ // be able to do more intelligent selection in the
176+ // future based on what is already in the working directory.
177+ return ;
178+ }
179+
180+ if ( value is { } templateName && ! validTemplates . Contains ( templateName ) )
181+ {
182+ result . AddError ( $ "The specified template '{ templateName } ' is not valid. Valid templates are [{ string . Join ( ", " , validTemplates ) } ].") ;
183+ return ;
184+ }
185+ }
186+
187+ private static void ConfigureNewCommand ( Command parentCommand )
188+ {
189+ var command = new Command ( "new" , "Create a new .NET Aspire-related project." ) ;
190+ var templateArgument = new Argument < string > ( "template" ) ;
191+ templateArgument . Validators . Add ( ValidateProjectTemplate ) ;
192+ templateArgument . Arity = ArgumentArity . ZeroOrOne ;
193+ command . Arguments . Add ( templateArgument ) ;
194+
195+ var nameOption = new Option < string > ( "--name" , "-n" ) ;
196+ command . Options . Add ( nameOption ) ;
197+
198+ var outputOption = new Option < string ? > ( "--output" , "-o" ) ;
199+ command . Options . Add ( outputOption ) ;
200+
201+ command . SetAction ( async ( parseResult , ct ) => {
202+ using var app = BuildApplication ( parseResult ) ;
203+ _ = app . RunAsync ( ct ) . ConfigureAwait ( false ) ;
204+
205+ var cliRunner = app . Services . GetRequiredService < DotNetCliRunner > ( ) ;
206+ var templateInstallExitCode = await cliRunner . InstallTemplateAsync ( "Aspire.ProjectTemplates" , "*-*" , true , ct ) . ConfigureAwait ( false ) ;
207+
208+ if ( templateInstallExitCode != 0 )
209+ {
210+ return ExitCodeConstants . FailedToInstallTemplates ;
211+ }
212+
213+ var templateName = parseResult . GetValue < string > ( "template" ) ?? "aspire-starter" ;
214+
215+ if ( parseResult . GetValue < string > ( "--output" ) is not { } outputPath )
216+ {
217+ outputPath = Environment . CurrentDirectory ;
218+ }
219+ else
220+ {
221+ outputPath = Path . GetFullPath ( outputPath ) ;
222+ }
223+
224+ if ( parseResult . GetValue < string > ( "--name" ) is not { } name )
225+ {
226+ var outputPathDirectoryInfo = new DirectoryInfo ( outputPath ) ;
227+ name = outputPathDirectoryInfo . Name ;
228+ }
229+
230+ var newProjectExitCode = await cliRunner . NewProjectAsync (
231+ templateName ,
232+ name ,
233+ outputPath ,
234+ ct ) . ConfigureAwait ( false ) ;
235+
236+ if ( newProjectExitCode != 0 )
237+ {
238+ return ExitCodeConstants . FailedToCreateNewProject ;
239+ }
240+
241+ return 0 ;
242+ } ) ;
243+
244+ parentCommand . Subcommands . Add ( command ) ;
245+ }
246+
136247 public static async Task < int > Main ( string [ ] args )
137248 {
138249 var rootCommand = GetRootCommand ( ) ;
0 commit comments