@@ -24,7 +24,7 @@ private static Aspire.Cli.CliExecutionContext CreateExecutionContext(DirectoryIn
2424 var settingsDirectory = workingDirectory . CreateSubdirectory ( ".aspire" ) ;
2525 var hivesDirectory = settingsDirectory . CreateSubdirectory ( "hives" ) ;
2626 var cacheDirectory = new DirectoryInfo ( Path . Combine ( workingDirectory . FullName , ".aspire" , "cache" ) ) ;
27- return new CliExecutionContext ( workingDirectory , hivesDirectory , cacheDirectory ) ;
27+ return new CliExecutionContext ( workingDirectory , hivesDirectory , cacheDirectory ) ;
2828 }
2929
3030 [ Fact ]
@@ -380,7 +380,7 @@ public async Task RunAsyncSetsVersionCheckDisabledWhenUpdateNotificationsFeature
380380 provider . GetRequiredService < IInteractionService > ( ) ,
381381 provider . GetRequiredService < CliExecutionContext > ( ) ,
382382 new NullDiskCache ( ) ,
383- ( args , env , _ , _ , _ , _ ) =>
383+ ( args , env , _ , _ , _ , _ ) =>
384384 {
385385 Assert . NotNull ( env ) ;
386386 Assert . True ( env . ContainsKey ( "ASPIRE_VERSION_CHECK_DISABLED" ) ) ;
@@ -428,7 +428,7 @@ public async Task RunAsyncDoesNotSetVersionCheckDisabledWhenUpdateNotificationsF
428428 provider . GetRequiredService < IInteractionService > ( ) ,
429429 provider . GetRequiredService < CliExecutionContext > ( ) ,
430430 new NullDiskCache ( ) ,
431- ( args , env , _ , _ , _ , _ ) =>
431+ ( args , env , _ , _ , _ , _ ) =>
432432 {
433433 // When the feature is enabled (default), the version check env var should NOT be set
434434 if ( env != null )
@@ -478,7 +478,7 @@ public async Task RunAsyncDoesNotOverrideUserProvidedVersionCheckDisabledValue()
478478 provider . GetRequiredService < IInteractionService > ( ) ,
479479 provider . GetRequiredService < CliExecutionContext > ( ) ,
480480 new NullDiskCache ( ) ,
481- ( args , env , _ , _ , _ , _ ) =>
481+ ( args , env , _ , _ , _ , _ ) =>
482482 {
483483 Assert . NotNull ( env ) ;
484484 Assert . True ( env . ContainsKey ( "ASPIRE_VERSION_CHECK_DISABLED" ) ) ;
@@ -570,7 +570,10 @@ public async Task AddPackageAsyncUseFilesSwitchForSingleFileAppHost()
570570 var appHostFile = new FileInfo ( Path . Combine ( workspace . WorkspaceRoot . FullName , "apphost.cs" ) ) ;
571571 await File . WriteAllTextAsync ( appHostFile . FullName , "// Single-file AppHost" ) ;
572572
573- var services = CliTestHelper . CreateServiceCollection ( workspace , outputHelper ) ;
573+ var services = CliTestHelper . CreateServiceCollection ( workspace , outputHelper , options =>
574+ {
575+ options . EnabledFeatures = [ KnownFeatures . SingleFileAppHostEnabled ] ;
576+ } ) ;
574577 var provider = services . BuildServiceProvider ( ) ;
575578 var logger = provider . GetRequiredService < ILogger < DotNetCliRunner > > ( ) ;
576579 var interactionService = provider . GetRequiredService < IInteractionService > ( ) ;
@@ -596,14 +599,14 @@ public async Task AddPackageAsyncUseFilesSwitchForSingleFileAppHost()
596599 Assert . Contains ( appHostFile . FullName , args ) ;
597600 Assert . Contains ( "[email protected] " , args ) ; 598601 Assert . Contains ( "--no-restore" , args ) ;
599-
602+
600603 // Verify the order: add package PackageName --file FilePath --version Version --no-restore
601604 var addIndex = Array . IndexOf ( args , "add" ) ;
602605 var packageIndex = Array . IndexOf ( args , "package" ) ;
603606 var fileIndex = Array . IndexOf ( args , "--file" ) ;
604607 var filePathIndex = Array . IndexOf ( args , appHostFile . FullName ) ;
605608 var packageNameIndex = Array . IndexOf ( args , "[email protected] " ) ; 606-
609+
607610 Assert . True ( addIndex < packageIndex ) ;
608611 Assert . True ( packageIndex < fileIndex ) ;
609612 Assert . True ( fileIndex < filePathIndex ) ;
@@ -659,21 +662,21 @@ public async Task AddPackageAsyncUsesPositionalArgumentForCsprojFile()
659662 Assert . Contains ( "9.2.0" , args ) ;
660663 Assert . Contains ( "--source" , args ) ;
661664 Assert . Contains ( "https://api.nuget.org/v3/index.json" , args ) ;
662-
665+
663666 // Verify the order: add ProjectFile package PackageName --version Version --source Source
664667 var addIndex = Array . IndexOf ( args , "add" ) ;
665668 var projectIndex = Array . IndexOf ( args , projectFile . FullName ) ;
666669 var packageIndex = Array . IndexOf ( args , "package" ) ;
667670 var packageNameIndex = Array . IndexOf ( args , "Aspire.Hosting.Redis" ) ;
668671 var versionFlagIndex = Array . IndexOf ( args , "--version" ) ;
669672 var versionValueIndex = Array . IndexOf ( args , "9.2.0" ) ;
670-
673+
671674 Assert . True ( addIndex < projectIndex ) ;
672675 Assert . True ( projectIndex < packageIndex ) ;
673676 Assert . True ( packageIndex < packageNameIndex ) ;
674677 Assert . True ( packageNameIndex < versionFlagIndex ) ;
675678 Assert . True ( versionFlagIndex < versionValueIndex ) ;
676-
679+
677680 // Should NOT contain --file or the @version format
678681 Assert . DoesNotContain ( "--file" , args ) ;
679682 Assert . DoesNotContain ( "[email protected] " , args ) ; @@ -727,7 +730,7 @@ public async Task AddPackageAsyncUsesPositionalArgumentForCsprojFileWithNoRestor
727730 Assert . Contains ( "--version" , args ) ;
728731 Assert . Contains ( "9.2.0" , args ) ;
729732 Assert . Contains ( "--no-restore" , args ) ;
730-
733+
731734 // Verify the order: add ProjectFile package PackageName --version Version --no-restore
732735 var addIndex = Array . IndexOf ( args , "add" ) ;
733736 var projectIndex = Array . IndexOf ( args , projectFile . FullName ) ;
@@ -736,14 +739,14 @@ public async Task AddPackageAsyncUsesPositionalArgumentForCsprojFileWithNoRestor
736739 var versionFlagIndex = Array . IndexOf ( args , "--version" ) ;
737740 var versionValueIndex = Array . IndexOf ( args , "9.2.0" ) ;
738741 var noRestoreIndex = Array . IndexOf ( args , "--no-restore" ) ;
739-
742+
740743 Assert . True ( addIndex < projectIndex ) ;
741744 Assert . True ( projectIndex < packageIndex ) ;
742745 Assert . True ( packageIndex < packageNameIndex ) ;
743746 Assert . True ( packageNameIndex < versionFlagIndex ) ;
744747 Assert . True ( versionFlagIndex < versionValueIndex ) ;
745748 Assert . True ( versionValueIndex < noRestoreIndex ) ;
746-
749+
747750 // Should NOT contain --file, --source, or the @version format
748751 Assert . DoesNotContain ( "--file" , args ) ;
749752 Assert . DoesNotContain ( "--source" , args ) ;
@@ -763,6 +766,184 @@ public async Task AddPackageAsyncUsesPositionalArgumentForCsprojFileWithNoRestor
763766
764767 Assert . Equal ( 0 , exitCode ) ;
765768 }
769+
770+ [ Fact ]
771+ public async Task RunAsyncAppliesNoLaunchProfileForSingleFileAppHost ( )
772+ {
773+ using var workspace = TemporaryWorkspace . Create ( outputHelper ) ;
774+ var appHostFile = new FileInfo ( Path . Combine ( workspace . WorkspaceRoot . FullName , "apphost.cs" ) ) ;
775+ await File . WriteAllTextAsync ( appHostFile . FullName , "// Single-file AppHost" ) ;
776+
777+ var services = CliTestHelper . CreateServiceCollection ( workspace , outputHelper , options =>
778+ {
779+ options . EnabledFeatures = [ KnownFeatures . SingleFileAppHostEnabled ] ;
780+ } ) ;
781+ var provider = services . BuildServiceProvider ( ) ;
782+ var logger = provider . GetRequiredService < ILogger < DotNetCliRunner > > ( ) ;
783+ var interactionService = provider . GetRequiredService < IInteractionService > ( ) ;
784+
785+ var options = new DotNetCliRunnerInvocationOptions ( )
786+ {
787+ NoLaunchProfile = true
788+ } ;
789+
790+ var executionContext = CreateExecutionContext ( workspace . WorkspaceRoot ) ;
791+ var runner = new AssertingDotNetCliRunner (
792+ logger ,
793+ provider ,
794+ new AspireCliTelemetry ( ) ,
795+ provider . GetRequiredService < IConfiguration > ( ) ,
796+ provider . GetRequiredService < IFeatures > ( ) ,
797+ interactionService ,
798+ executionContext ,
799+ new NullDiskCache ( ) ,
800+ ( args , _ , _ , _ , _ , _ ) =>
801+ {
802+ // For single-file .cs files, should include --no-launch-profile
803+ Assert . Collection ( args ,
804+ arg => Assert . Equal ( "run" , arg ) ,
805+ arg => Assert . Equal ( "--no-launch-profile" , arg ) ,
806+ arg => Assert . Equal ( "--file" , arg ) ,
807+ arg => Assert . Equal ( appHostFile . FullName , arg ) ,
808+ arg => Assert . Equal ( "--" , arg )
809+ ) ;
810+ } ,
811+ 0
812+ ) ;
813+
814+ var exitCode = await runner . RunAsync (
815+ projectFile : appHostFile ,
816+ watch : false ,
817+ noBuild : false ,
818+ args : [ ] ,
819+ env : new Dictionary < string , string > ( ) ,
820+ null ,
821+ options ,
822+ CancellationToken . None
823+ ) ;
824+
825+ Assert . Equal ( 0 , exitCode ) ;
826+ }
827+
828+ [ Fact ]
829+ public async Task RunAsyncDoesNotIncludeNoLaunchProfileForSingleFileAppHostWhenNotSpecified ( )
830+ {
831+ using var workspace = TemporaryWorkspace . Create ( outputHelper ) ;
832+ var appHostFile = new FileInfo ( Path . Combine ( workspace . WorkspaceRoot . FullName , "apphost.cs" ) ) ;
833+ await File . WriteAllTextAsync ( appHostFile . FullName , "// Single-file AppHost" ) ;
834+
835+ var services = CliTestHelper . CreateServiceCollection ( workspace , outputHelper , options =>
836+ {
837+ options . EnabledFeatures = [ KnownFeatures . SingleFileAppHostEnabled ] ;
838+ } ) ;
839+ var provider = services . BuildServiceProvider ( ) ;
840+ var logger = provider . GetRequiredService < ILogger < DotNetCliRunner > > ( ) ;
841+ var interactionService = provider . GetRequiredService < IInteractionService > ( ) ;
842+
843+ var options = new DotNetCliRunnerInvocationOptions ( )
844+ {
845+ NoLaunchProfile = false
846+ } ;
847+
848+ var executionContext = CreateExecutionContext ( workspace . WorkspaceRoot ) ;
849+ var runner = new AssertingDotNetCliRunner (
850+ logger ,
851+ provider ,
852+ new AspireCliTelemetry ( ) ,
853+ provider . GetRequiredService < IConfiguration > ( ) ,
854+ provider . GetRequiredService < IFeatures > ( ) ,
855+ interactionService ,
856+ executionContext ,
857+ new NullDiskCache ( ) ,
858+ ( args , _ , _ , _ , _ , _ ) =>
859+ {
860+ // For single-file .cs files, should NOT include --no-launch-profile when false
861+ Assert . Collection ( args ,
862+ arg => Assert . Equal ( "run" , arg ) ,
863+ arg => Assert . Equal ( "--file" , arg ) ,
864+ arg => Assert . Equal ( appHostFile . FullName , arg ) ,
865+ arg => Assert . Equal ( "--" , arg )
866+ ) ;
867+ } ,
868+ 0
869+ ) ;
870+
871+ var exitCode = await runner . RunAsync (
872+ projectFile : appHostFile ,
873+ watch : false ,
874+ noBuild : false ,
875+ args : [ ] ,
876+ env : new Dictionary < string , string > ( ) ,
877+ null ,
878+ options ,
879+ CancellationToken . None
880+ ) ;
881+
882+ Assert . Equal ( 0 , exitCode ) ;
883+ }
884+
885+ [ Fact ]
886+ public async Task RunAsyncFiltersOutEmptyArgumentsForSingleFileAppHost ( )
887+ {
888+ using var workspace = TemporaryWorkspace . Create ( outputHelper ) ;
889+ var appHostFile = new FileInfo ( Path . Combine ( workspace . WorkspaceRoot . FullName , "apphost.cs" ) ) ;
890+ await File . WriteAllTextAsync ( appHostFile . FullName , "// Single-file AppHost" ) ;
891+
892+ var services = CliTestHelper . CreateServiceCollection ( workspace , outputHelper , options =>
893+ {
894+ options . EnabledFeatures = [ KnownFeatures . SingleFileAppHostEnabled ] ;
895+ } ) ;
896+ var provider = services . BuildServiceProvider ( ) ;
897+ var logger = provider . GetRequiredService < ILogger < DotNetCliRunner > > ( ) ;
898+ var interactionService = provider . GetRequiredService < IInteractionService > ( ) ;
899+
900+ var options = new DotNetCliRunnerInvocationOptions ( )
901+ {
902+ NoLaunchProfile = false // This will generate an empty string for noProfileSwitch
903+ } ;
904+
905+ var executionContext = CreateExecutionContext ( workspace . WorkspaceRoot ) ;
906+ var runner = new AssertingDotNetCliRunner (
907+ logger ,
908+ provider ,
909+ new AspireCliTelemetry ( ) ,
910+ provider . GetRequiredService < IConfiguration > ( ) ,
911+ provider . GetRequiredService < IFeatures > ( ) ,
912+ interactionService ,
913+ executionContext ,
914+ new NullDiskCache ( ) ,
915+ ( args , _ , _ , _ , _ , _ ) =>
916+ {
917+ // Verify no empty or whitespace-only arguments exist in single-file AppHost scenario
918+ foreach ( var arg in args )
919+ {
920+ Assert . False ( string . IsNullOrWhiteSpace ( arg ) , $ "Found empty or whitespace argument in args: [{ string . Join ( ", " , args ) } ]") ;
921+ }
922+
923+ // Ensure the correct arguments are present
924+ Assert . Collection ( args ,
925+ arg => Assert . Equal ( "run" , arg ) ,
926+ arg => Assert . Equal ( "--file" , arg ) ,
927+ arg => Assert . Equal ( appHostFile . FullName , arg ) ,
928+ arg => Assert . Equal ( "--" , arg )
929+ ) ;
930+ } ,
931+ 0
932+ ) ;
933+
934+ var exitCode = await runner . RunAsync (
935+ projectFile : appHostFile ,
936+ watch : false ,
937+ noBuild : false ,
938+ args : [ ] ,
939+ env : new Dictionary < string , string > ( ) ,
940+ null ,
941+ options ,
942+ CancellationToken . None
943+ ) ;
944+
945+ Assert . Equal ( 0 , exitCode ) ;
946+ }
766947}
767948
768949internal sealed class AssertingDotNetCliRunner (
0 commit comments