From a335e9d5c9692bad1da6d1e962f0edea8d62ead4 Mon Sep 17 00:00:00 2001 From: Adam Ratzman Date: Tue, 24 Mar 2026 09:20:34 -0700 Subject: [PATCH 1/4] Add .vscode workspace files to aspire new templates After successful template creation, aspire new now generates .vscode/extensions.json (recommending the Aspire extension) and .vscode/launch.json (default Aspire debug configuration). Existing files are not overwritten. Fixes #14802 Fixes #14391 --- src/Aspire.Cli/Commands/NewCommand.cs | 46 ++++++++++ .../Commands/NewCommandTests.cs | 91 +++++++++++++++++++ 2 files changed, 137 insertions(+) diff --git a/src/Aspire.Cli/Commands/NewCommand.cs b/src/Aspire.Cli/Commands/NewCommand.cs index cc8d4f48c4a..aef7d1d8713 100644 --- a/src/Aspire.Cli/Commands/NewCommand.cs +++ b/src/Aspire.Cli/Commands/NewCommand.cs @@ -3,6 +3,7 @@ using System.CommandLine; using System.Diagnostics.CodeAnalysis; +using System.Text; using System.Text.RegularExpressions; using Aspire.Cli.Configuration; using Aspire.Cli.Interaction; @@ -270,6 +271,11 @@ protected override async Task ExecuteAsync(ParseResult parseResult, Cancell Language = template.LanguageId }; var templateResult = await template.ApplyTemplateAsync(inputs, parseResult, cancellationToken); + if (templateResult.ExitCode == ExitCodeConstants.Success && templateResult.OutputPath is not null) + { + WriteVsCodeWorkspaceFiles(templateResult.OutputPath); + } + if (templateResult.OutputPath is not null && ExtensionHelper.IsExtensionHost(InteractionService, out var extensionInteractionService, out _)) { extensionInteractionService.OpenEditor(templateResult.OutputPath); @@ -283,6 +289,46 @@ private static bool ShouldResolveCliTemplateVersion(ITemplate template) { return template.Runtime is TemplateRuntime.Cli; } + + private static void WriteVsCodeWorkspaceFiles(string outputPath) + { + var vsCodeDir = Path.Combine(outputPath, ".vscode"); + Directory.CreateDirectory(vsCodeDir); + + var extensionsJsonPath = Path.Combine(vsCodeDir, "extensions.json"); + if (!File.Exists(extensionsJsonPath)) + { + File.WriteAllText(extensionsJsonPath, + """ + { + "recommendations": [ + "microsoft-aspire.aspire-vscode" + ] + } + """, + new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); + } + + var launchJsonPath = Path.Combine(vsCodeDir, "launch.json"); + if (!File.Exists(launchJsonPath)) + { + File.WriteAllText(launchJsonPath, + """ + { + "version": "0.2.0", + "configurations": [ + { + "type": "aspire", + "request": "launch", + "name": "Aspire: Launch default apphost", + "program": "${workspaceFolder}" + } + ] + } + """, + new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); + } + } } internal interface INewCommandPrompter diff --git a/tests/Aspire.Cli.Tests/Commands/NewCommandTests.cs b/tests/Aspire.Cli.Tests/Commands/NewCommandTests.cs index d671a48055d..ac55e13c5d0 100644 --- a/tests/Aspire.Cli.Tests/Commands/NewCommandTests.cs +++ b/tests/Aspire.Cli.Tests/Commands/NewCommandTests.cs @@ -1471,6 +1471,97 @@ public async Task NewCommandNonInteractiveDoesNotPrompt() var exitCode = await result.InvokeAsync().DefaultTimeout(); Assert.Equal(0, exitCode); } + + [Fact] + public async Task NewCommandCreatesVsCodeWorkspaceFiles() + { + using var workspace = TemporaryWorkspace.Create(outputHelper); + var outputDir = workspace.CreateDirectory("MyApp"); + var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options => + { + options.DotNetCliRunnerFactory = (sp) => + { + var runner = new TestDotNetCliRunner(); + runner.SearchPackagesAsyncCallback = (dir, query, prerelease, take, skip, nugetSource, useCache, options, cancellationToken) => + { + var package = new NuGetPackage() + { + Id = "Aspire.ProjectTemplates", + Source = "nuget", + Version = "9.2.0" + }; + + return (0, new NuGetPackage[] { package }); + }; + + return runner; + }; + }); + var provider = services.BuildServiceProvider(); + + var command = provider.GetRequiredService(); + var result = command.Parse($"new aspire-starter --name TestApp --output {outputDir.FullName} --use-redis-cache --test-framework None"); + + var exitCode = await result.InvokeAsync().DefaultTimeout(); + Assert.Equal(0, exitCode); + + var extensionsJsonPath = Path.Combine(outputDir.FullName, ".vscode", "extensions.json"); + Assert.True(File.Exists(extensionsJsonPath)); + var extensionsContent = File.ReadAllText(extensionsJsonPath); + Assert.Contains("microsoft-aspire.aspire-vscode", extensionsContent); + + var launchJsonPath = Path.Combine(outputDir.FullName, ".vscode", "launch.json"); + Assert.True(File.Exists(launchJsonPath)); + var launchContent = File.ReadAllText(launchJsonPath); + Assert.Contains("\"type\": \"aspire\"", launchContent); + } + + [Fact] + public async Task NewCommandDoesNotOverwriteExistingVsCodeFiles() + { + using var workspace = TemporaryWorkspace.Create(outputHelper); + var outputDir = workspace.CreateDirectory("MyApp2"); + + var vsCodeDir = Path.Combine(outputDir.FullName, ".vscode"); + Directory.CreateDirectory(vsCodeDir); + File.WriteAllText(Path.Combine(vsCodeDir, "extensions.json"), """{"recommendations":["existing.extension"]}"""); + File.WriteAllText(Path.Combine(vsCodeDir, "launch.json"), """{"version":"0.2.0","configurations":[]}"""); + + var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options => + { + options.DotNetCliRunnerFactory = (sp) => + { + var runner = new TestDotNetCliRunner(); + runner.SearchPackagesAsyncCallback = (dir, query, prerelease, take, skip, nugetSource, useCache, options, cancellationToken) => + { + var package = new NuGetPackage() + { + Id = "Aspire.ProjectTemplates", + Source = "nuget", + Version = "9.2.0" + }; + + return (0, new NuGetPackage[] { package }); + }; + + return runner; + }; + }); + var provider = services.BuildServiceProvider(); + + var command = provider.GetRequiredService(); + var result = command.Parse($"new aspire-starter --name TestApp --output {outputDir.FullName} --use-redis-cache --test-framework None"); + + var exitCode = await result.InvokeAsync().DefaultTimeout(); + Assert.Equal(0, exitCode); + + var extensionsContent = File.ReadAllText(Path.Combine(vsCodeDir, "extensions.json")); + Assert.Contains("existing.extension", extensionsContent); + Assert.DoesNotContain("microsoft-aspire.aspire-vscode", extensionsContent); + + var launchContent = File.ReadAllText(Path.Combine(vsCodeDir, "launch.json")); + Assert.DoesNotContain("\"type\": \"aspire\"", launchContent); + } } internal sealed class TestNewCommandPrompter(IInteractionService interactionService) : NewCommandPrompter(interactionService) From 768f40f551e2d6108c083acc5743561327fabd37 Mon Sep 17 00:00:00 2001 From: Adam Ratzman Date: Tue, 24 Mar 2026 14:37:23 -0700 Subject: [PATCH 2/4] Address PR feedback: try/catch for WriteVsCodeWorkspaceFiles, add mixed-case test --- src/Aspire.Cli/Commands/NewCommand.cs | 9 +++- .../Commands/NewCommandTests.cs | 48 +++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/Aspire.Cli/Commands/NewCommand.cs b/src/Aspire.Cli/Commands/NewCommand.cs index aef7d1d8713..26aac762840 100644 --- a/src/Aspire.Cli/Commands/NewCommand.cs +++ b/src/Aspire.Cli/Commands/NewCommand.cs @@ -273,7 +273,14 @@ protected override async Task ExecuteAsync(ParseResult parseResult, Cancell var templateResult = await template.ApplyTemplateAsync(inputs, parseResult, cancellationToken); if (templateResult.ExitCode == ExitCodeConstants.Success && templateResult.OutputPath is not null) { - WriteVsCodeWorkspaceFiles(templateResult.OutputPath); + try + { + WriteVsCodeWorkspaceFiles(templateResult.OutputPath); + } + catch (Exception ex) + { + InteractionService.DisplayMessage(KnownEmojis.Warning, $"Failed to generate VS Code workspace files: {ex.Message}"); + } } if (templateResult.OutputPath is not null && ExtensionHelper.IsExtensionHost(InteractionService, out var extensionInteractionService, out _)) diff --git a/tests/Aspire.Cli.Tests/Commands/NewCommandTests.cs b/tests/Aspire.Cli.Tests/Commands/NewCommandTests.cs index ac55e13c5d0..b5185317f55 100644 --- a/tests/Aspire.Cli.Tests/Commands/NewCommandTests.cs +++ b/tests/Aspire.Cli.Tests/Commands/NewCommandTests.cs @@ -1562,6 +1562,54 @@ public async Task NewCommandDoesNotOverwriteExistingVsCodeFiles() var launchContent = File.ReadAllText(Path.Combine(vsCodeDir, "launch.json")); Assert.DoesNotContain("\"type\": \"aspire\"", launchContent); } + + [Fact] + public async Task NewCommandCreatesOnlyMissingVsCodeFiles() + { + using var workspace = TemporaryWorkspace.Create(outputHelper); + var outputDir = workspace.CreateDirectory("MyApp3"); + + var vsCodeDir = Path.Combine(outputDir.FullName, ".vscode"); + Directory.CreateDirectory(vsCodeDir); + File.WriteAllText(Path.Combine(vsCodeDir, "extensions.json"), """{"recommendations":["existing.extension"]}"""); + + var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options => + { + options.DotNetCliRunnerFactory = (sp) => + { + var runner = new TestDotNetCliRunner(); + runner.SearchPackagesAsyncCallback = (dir, query, prerelease, take, skip, nugetSource, useCache, options, cancellationToken) => + { + var package = new NuGetPackage() + { + Id = "Aspire.ProjectTemplates", + Source = "nuget", + Version = "9.2.0" + }; + + return (0, new NuGetPackage[] { package }); + }; + + return runner; + }; + }); + var provider = services.BuildServiceProvider(); + + var command = provider.GetRequiredService(); + var result = command.Parse($"new aspire-starter --name TestApp --output {outputDir.FullName} --use-redis-cache --test-framework None"); + + var exitCode = await result.InvokeAsync().DefaultTimeout(); + Assert.Equal(0, exitCode); + + var extensionsContent = File.ReadAllText(Path.Combine(vsCodeDir, "extensions.json")); + Assert.Contains("existing.extension", extensionsContent); + Assert.DoesNotContain("microsoft-aspire.aspire-vscode", extensionsContent); + + var launchJsonPath = Path.Combine(vsCodeDir, "launch.json"); + Assert.True(File.Exists(launchJsonPath)); + var launchContent = File.ReadAllText(launchJsonPath); + Assert.Contains("\"type\": \"aspire\"", launchContent); + } } internal sealed class TestNewCommandPrompter(IInteractionService interactionService) : NewCommandPrompter(interactionService) From e372b992335674d129f84c67f68968684b25b314 Mon Sep 17 00:00:00 2001 From: Adam Ratzman Date: Tue, 24 Mar 2026 17:59:58 -0700 Subject: [PATCH 3/4] add comment --- src/Aspire.Cli/Commands/NewCommand.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Aspire.Cli/Commands/NewCommand.cs b/src/Aspire.Cli/Commands/NewCommand.cs index c7fe0835bb2..78b4b4b0119 100644 --- a/src/Aspire.Cli/Commands/NewCommand.cs +++ b/src/Aspire.Cli/Commands/NewCommand.cs @@ -288,6 +288,7 @@ protected override async Task ExecuteAsync(ParseResult parseResult, Cancell var workspaceRoot = new DirectoryInfo(templateResult.OutputPath ?? ExecutionContext.WorkingDirectory.FullName); var exitCode = await _agentInitCommand.PromptAndChainAsync(_hostEnvironment, InteractionService, templateResult.ExitCode, workspaceRoot, cancellationToken); + // Editor is opened AFTER the agent init prompt since OpenEditor may trigger a workspace change, which would terminate the CLI process if (templateResult.OutputPath is not null && ExtensionHelper.IsExtensionHost(InteractionService, out var extensionInteractionService, out _)) { extensionInteractionService.OpenEditor(templateResult.OutputPath); From a78024d22cbaeb71adecda49b7d09b0bf02a8f17 Mon Sep 17 00:00:00 2001 From: Adam Ratzman Date: Tue, 24 Mar 2026 18:18:08 -0700 Subject: [PATCH 4/4] Move .vscode workspace files into templates instead of generating in NewCommand Add .vscode/extensions.json (recommending Aspire extension) and .vscode/launch.json (with Aspire debug config) directly to the four solution templates that include an AppHost: - aspire-empty - aspire-starter - aspire-ts-cs-starter - aspire-py-starter This is better than programmatic generation because it works with all template instantiation paths (dotnet new, aspire new, Visual Studio), not just the Aspire CLI. Remove WriteVsCodeWorkspaceFiles from NewCommand.cs and the three related tests. --- src/Aspire.Cli/Commands/NewCommand.cs | 52 ------- .../aspire-empty/.vscode/extensions.json | 5 + .../aspire-empty/.vscode/launch.json | 11 ++ .../aspire-py-starter/.vscode/extensions.json | 5 + .../aspire-py-starter/.vscode/launch.json | 11 ++ .../aspire-starter/.vscode/extensions.json | 5 + .../aspire-starter/.vscode/launch.json | 11 ++ .../.vscode/extensions.json | 5 + .../aspire-ts-cs-starter/.vscode/launch.json | 11 ++ .../Commands/NewCommandTests.cs | 139 ------------------ 10 files changed, 64 insertions(+), 191 deletions(-) create mode 100644 src/Aspire.ProjectTemplates/templates/aspire-empty/.vscode/extensions.json create mode 100644 src/Aspire.ProjectTemplates/templates/aspire-empty/.vscode/launch.json create mode 100644 src/Aspire.ProjectTemplates/templates/aspire-py-starter/.vscode/extensions.json create mode 100644 src/Aspire.ProjectTemplates/templates/aspire-py-starter/.vscode/launch.json create mode 100644 src/Aspire.ProjectTemplates/templates/aspire-starter/.vscode/extensions.json create mode 100644 src/Aspire.ProjectTemplates/templates/aspire-starter/.vscode/launch.json create mode 100644 src/Aspire.ProjectTemplates/templates/aspire-ts-cs-starter/.vscode/extensions.json create mode 100644 src/Aspire.ProjectTemplates/templates/aspire-ts-cs-starter/.vscode/launch.json diff --git a/src/Aspire.Cli/Commands/NewCommand.cs b/src/Aspire.Cli/Commands/NewCommand.cs index 78b4b4b0119..3e06c963d23 100644 --- a/src/Aspire.Cli/Commands/NewCommand.cs +++ b/src/Aspire.Cli/Commands/NewCommand.cs @@ -3,7 +3,6 @@ using System.CommandLine; using System.Diagnostics.CodeAnalysis; -using System.Text; using System.Text.RegularExpressions; using Aspire.Cli.Configuration; using Aspire.Cli.Interaction; @@ -273,17 +272,6 @@ protected override async Task ExecuteAsync(ParseResult parseResult, Cancell Language = template.LanguageId }; var templateResult = await template.ApplyTemplateAsync(inputs, parseResult, cancellationToken); - if (templateResult.ExitCode == ExitCodeConstants.Success && templateResult.OutputPath is not null) - { - try - { - WriteVsCodeWorkspaceFiles(templateResult.OutputPath); - } - catch (Exception ex) - { - InteractionService.DisplayMessage(KnownEmojis.Warning, $"Failed to generate VS Code workspace files: {ex.Message}"); - } - } var workspaceRoot = new DirectoryInfo(templateResult.OutputPath ?? ExecutionContext.WorkingDirectory.FullName); var exitCode = await _agentInitCommand.PromptAndChainAsync(_hostEnvironment, InteractionService, templateResult.ExitCode, workspaceRoot, cancellationToken); @@ -301,46 +289,6 @@ private static bool ShouldResolveCliTemplateVersion(ITemplate template) { return template.Runtime is TemplateRuntime.Cli; } - - private static void WriteVsCodeWorkspaceFiles(string outputPath) - { - var vsCodeDir = Path.Combine(outputPath, ".vscode"); - Directory.CreateDirectory(vsCodeDir); - - var extensionsJsonPath = Path.Combine(vsCodeDir, "extensions.json"); - if (!File.Exists(extensionsJsonPath)) - { - File.WriteAllText(extensionsJsonPath, - """ - { - "recommendations": [ - "microsoft-aspire.aspire-vscode" - ] - } - """, - new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); - } - - var launchJsonPath = Path.Combine(vsCodeDir, "launch.json"); - if (!File.Exists(launchJsonPath)) - { - File.WriteAllText(launchJsonPath, - """ - { - "version": "0.2.0", - "configurations": [ - { - "type": "aspire", - "request": "launch", - "name": "Aspire: Launch default apphost", - "program": "${workspaceFolder}" - } - ] - } - """, - new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); - } - } } internal interface INewCommandPrompter diff --git a/src/Aspire.ProjectTemplates/templates/aspire-empty/.vscode/extensions.json b/src/Aspire.ProjectTemplates/templates/aspire-empty/.vscode/extensions.json new file mode 100644 index 00000000000..3c534f6592a --- /dev/null +++ b/src/Aspire.ProjectTemplates/templates/aspire-empty/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "microsoft-aspire.aspire-vscode" + ] +} diff --git a/src/Aspire.ProjectTemplates/templates/aspire-empty/.vscode/launch.json b/src/Aspire.ProjectTemplates/templates/aspire-empty/.vscode/launch.json new file mode 100644 index 00000000000..3b101e93559 --- /dev/null +++ b/src/Aspire.ProjectTemplates/templates/aspire-empty/.vscode/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "aspire", + "request": "launch", + "name": "Aspire: Launch default apphost", + "program": "${workspaceFolder}" + } + ] +} diff --git a/src/Aspire.ProjectTemplates/templates/aspire-py-starter/.vscode/extensions.json b/src/Aspire.ProjectTemplates/templates/aspire-py-starter/.vscode/extensions.json new file mode 100644 index 00000000000..3c534f6592a --- /dev/null +++ b/src/Aspire.ProjectTemplates/templates/aspire-py-starter/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "microsoft-aspire.aspire-vscode" + ] +} diff --git a/src/Aspire.ProjectTemplates/templates/aspire-py-starter/.vscode/launch.json b/src/Aspire.ProjectTemplates/templates/aspire-py-starter/.vscode/launch.json new file mode 100644 index 00000000000..3b101e93559 --- /dev/null +++ b/src/Aspire.ProjectTemplates/templates/aspire-py-starter/.vscode/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "aspire", + "request": "launch", + "name": "Aspire: Launch default apphost", + "program": "${workspaceFolder}" + } + ] +} diff --git a/src/Aspire.ProjectTemplates/templates/aspire-starter/.vscode/extensions.json b/src/Aspire.ProjectTemplates/templates/aspire-starter/.vscode/extensions.json new file mode 100644 index 00000000000..3c534f6592a --- /dev/null +++ b/src/Aspire.ProjectTemplates/templates/aspire-starter/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "microsoft-aspire.aspire-vscode" + ] +} diff --git a/src/Aspire.ProjectTemplates/templates/aspire-starter/.vscode/launch.json b/src/Aspire.ProjectTemplates/templates/aspire-starter/.vscode/launch.json new file mode 100644 index 00000000000..3b101e93559 --- /dev/null +++ b/src/Aspire.ProjectTemplates/templates/aspire-starter/.vscode/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "aspire", + "request": "launch", + "name": "Aspire: Launch default apphost", + "program": "${workspaceFolder}" + } + ] +} diff --git a/src/Aspire.ProjectTemplates/templates/aspire-ts-cs-starter/.vscode/extensions.json b/src/Aspire.ProjectTemplates/templates/aspire-ts-cs-starter/.vscode/extensions.json new file mode 100644 index 00000000000..3c534f6592a --- /dev/null +++ b/src/Aspire.ProjectTemplates/templates/aspire-ts-cs-starter/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "microsoft-aspire.aspire-vscode" + ] +} diff --git a/src/Aspire.ProjectTemplates/templates/aspire-ts-cs-starter/.vscode/launch.json b/src/Aspire.ProjectTemplates/templates/aspire-ts-cs-starter/.vscode/launch.json new file mode 100644 index 00000000000..3b101e93559 --- /dev/null +++ b/src/Aspire.ProjectTemplates/templates/aspire-ts-cs-starter/.vscode/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "aspire", + "request": "launch", + "name": "Aspire: Launch default apphost", + "program": "${workspaceFolder}" + } + ] +} diff --git a/tests/Aspire.Cli.Tests/Commands/NewCommandTests.cs b/tests/Aspire.Cli.Tests/Commands/NewCommandTests.cs index b5185317f55..d671a48055d 100644 --- a/tests/Aspire.Cli.Tests/Commands/NewCommandTests.cs +++ b/tests/Aspire.Cli.Tests/Commands/NewCommandTests.cs @@ -1471,145 +1471,6 @@ public async Task NewCommandNonInteractiveDoesNotPrompt() var exitCode = await result.InvokeAsync().DefaultTimeout(); Assert.Equal(0, exitCode); } - - [Fact] - public async Task NewCommandCreatesVsCodeWorkspaceFiles() - { - using var workspace = TemporaryWorkspace.Create(outputHelper); - var outputDir = workspace.CreateDirectory("MyApp"); - var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options => - { - options.DotNetCliRunnerFactory = (sp) => - { - var runner = new TestDotNetCliRunner(); - runner.SearchPackagesAsyncCallback = (dir, query, prerelease, take, skip, nugetSource, useCache, options, cancellationToken) => - { - var package = new NuGetPackage() - { - Id = "Aspire.ProjectTemplates", - Source = "nuget", - Version = "9.2.0" - }; - - return (0, new NuGetPackage[] { package }); - }; - - return runner; - }; - }); - var provider = services.BuildServiceProvider(); - - var command = provider.GetRequiredService(); - var result = command.Parse($"new aspire-starter --name TestApp --output {outputDir.FullName} --use-redis-cache --test-framework None"); - - var exitCode = await result.InvokeAsync().DefaultTimeout(); - Assert.Equal(0, exitCode); - - var extensionsJsonPath = Path.Combine(outputDir.FullName, ".vscode", "extensions.json"); - Assert.True(File.Exists(extensionsJsonPath)); - var extensionsContent = File.ReadAllText(extensionsJsonPath); - Assert.Contains("microsoft-aspire.aspire-vscode", extensionsContent); - - var launchJsonPath = Path.Combine(outputDir.FullName, ".vscode", "launch.json"); - Assert.True(File.Exists(launchJsonPath)); - var launchContent = File.ReadAllText(launchJsonPath); - Assert.Contains("\"type\": \"aspire\"", launchContent); - } - - [Fact] - public async Task NewCommandDoesNotOverwriteExistingVsCodeFiles() - { - using var workspace = TemporaryWorkspace.Create(outputHelper); - var outputDir = workspace.CreateDirectory("MyApp2"); - - var vsCodeDir = Path.Combine(outputDir.FullName, ".vscode"); - Directory.CreateDirectory(vsCodeDir); - File.WriteAllText(Path.Combine(vsCodeDir, "extensions.json"), """{"recommendations":["existing.extension"]}"""); - File.WriteAllText(Path.Combine(vsCodeDir, "launch.json"), """{"version":"0.2.0","configurations":[]}"""); - - var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options => - { - options.DotNetCliRunnerFactory = (sp) => - { - var runner = new TestDotNetCliRunner(); - runner.SearchPackagesAsyncCallback = (dir, query, prerelease, take, skip, nugetSource, useCache, options, cancellationToken) => - { - var package = new NuGetPackage() - { - Id = "Aspire.ProjectTemplates", - Source = "nuget", - Version = "9.2.0" - }; - - return (0, new NuGetPackage[] { package }); - }; - - return runner; - }; - }); - var provider = services.BuildServiceProvider(); - - var command = provider.GetRequiredService(); - var result = command.Parse($"new aspire-starter --name TestApp --output {outputDir.FullName} --use-redis-cache --test-framework None"); - - var exitCode = await result.InvokeAsync().DefaultTimeout(); - Assert.Equal(0, exitCode); - - var extensionsContent = File.ReadAllText(Path.Combine(vsCodeDir, "extensions.json")); - Assert.Contains("existing.extension", extensionsContent); - Assert.DoesNotContain("microsoft-aspire.aspire-vscode", extensionsContent); - - var launchContent = File.ReadAllText(Path.Combine(vsCodeDir, "launch.json")); - Assert.DoesNotContain("\"type\": \"aspire\"", launchContent); - } - - [Fact] - public async Task NewCommandCreatesOnlyMissingVsCodeFiles() - { - using var workspace = TemporaryWorkspace.Create(outputHelper); - var outputDir = workspace.CreateDirectory("MyApp3"); - - var vsCodeDir = Path.Combine(outputDir.FullName, ".vscode"); - Directory.CreateDirectory(vsCodeDir); - File.WriteAllText(Path.Combine(vsCodeDir, "extensions.json"), """{"recommendations":["existing.extension"]}"""); - - var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options => - { - options.DotNetCliRunnerFactory = (sp) => - { - var runner = new TestDotNetCliRunner(); - runner.SearchPackagesAsyncCallback = (dir, query, prerelease, take, skip, nugetSource, useCache, options, cancellationToken) => - { - var package = new NuGetPackage() - { - Id = "Aspire.ProjectTemplates", - Source = "nuget", - Version = "9.2.0" - }; - - return (0, new NuGetPackage[] { package }); - }; - - return runner; - }; - }); - var provider = services.BuildServiceProvider(); - - var command = provider.GetRequiredService(); - var result = command.Parse($"new aspire-starter --name TestApp --output {outputDir.FullName} --use-redis-cache --test-framework None"); - - var exitCode = await result.InvokeAsync().DefaultTimeout(); - Assert.Equal(0, exitCode); - - var extensionsContent = File.ReadAllText(Path.Combine(vsCodeDir, "extensions.json")); - Assert.Contains("existing.extension", extensionsContent); - Assert.DoesNotContain("microsoft-aspire.aspire-vscode", extensionsContent); - - var launchJsonPath = Path.Combine(vsCodeDir, "launch.json"); - Assert.True(File.Exists(launchJsonPath)); - var launchContent = File.ReadAllText(launchJsonPath); - Assert.Contains("\"type\": \"aspire\"", launchContent); - } } internal sealed class TestNewCommandPrompter(IInteractionService interactionService) : NewCommandPrompter(interactionService)