Skip to content

Commit bfff46d

Browse files
Copilotdavidfowl
andcommitted
Apply PR #11951 changes: Add noProfileSwitch to single-file run command and filter empty arguments
Co-authored-by: davidfowl <[email protected]>
1 parent 38c3df6 commit bfff46d

File tree

2 files changed

+198
-15
lines changed

2 files changed

+198
-15
lines changed

src/Aspire.Cli/DotNet/DotNetCliRunner.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,9 +240,11 @@ public async Task<int> RunAsync(FileInfo projectFile, bool watch, bool noBuild,
240240
string[] cliArgs = isSingleFile switch
241241
{
242242
false => [watchOrRunCommand, noBuildSwitch, noProfileSwitch, "--project", projectFile.FullName, "--", .. args],
243-
true => ["run", projectFile.FullName, "--", ..args]
243+
true => ["run", noProfileSwitch, "--file", projectFile.FullName, "--", .. args]
244244
};
245-
245+
246+
cliArgs = [.. cliArgs.Where(arg => !string.IsNullOrWhiteSpace(arg))];
247+
246248
// Inject DOTNET_CLI_USE_MSBUILD_SERVER when noBuild == false - we copy the
247249
// dictionary here because we don't want to mutate the input.
248250
IDictionary<string, string>? finalEnv = env;

tests/Aspire.Cli.Tests/DotNet/DotNetCliRunnerTests.cs

Lines changed: 194 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -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

768949
internal sealed class AssertingDotNetCliRunner(

0 commit comments

Comments
 (0)