Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Aspire.Cli/Commands/AgentInitCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ protected override async Task<int> ExecuteAsync(ParseResult parseResult, Cancell
promptText,
sortedApplicators,
applicator => applicator.Description,
optional: true,
cancellationToken);

selectedApplicators.AddRange(selected);
Expand Down
2 changes: 2 additions & 0 deletions src/Aspire.Cli/Commands/InitCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ later as needed.
"Select projects to add to the AppHost:",
initContext.ExecutableProjects,
project => Path.GetFileNameWithoutExtension(project.ProjectFile.Name).EscapeMarkup(),
optional: true,
cancellationToken);

initContext.ExecutableProjectsToAddToAppHost = selectedProjects;
Expand Down Expand Up @@ -286,6 +287,7 @@ ServiceDefaults project contains helper code to make it easier
"Select projects to add ServiceDefaults reference to:",
initContext.ExecutableProjectsToAddToAppHost,
project => Path.GetFileNameWithoutExtension(project.ProjectFile.Name).EscapeMarkup(),
optional: true,
cancellationToken);
break;
case "none":
Expand Down
7 changes: 6 additions & 1 deletion src/Aspire.Cli/Interaction/ConsoleInteractionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ public async Task<T> PromptForSelectionAsync<T>(string promptText, IEnumerable<T
return await _outConsole.PromptAsync(prompt, cancellationToken);
}

public async Task<IReadOnlyList<T>> PromptForSelectionsAsync<T>(string promptText, IEnumerable<T> choices, Func<T, string> choiceFormatter, CancellationToken cancellationToken = default) where T : notnull
public async Task<IReadOnlyList<T>> PromptForSelectionsAsync<T>(string promptText, IEnumerable<T> choices, Func<T, string> choiceFormatter, bool optional = false, CancellationToken cancellationToken = default) where T : notnull
{
ArgumentNullException.ThrowIfNull(promptText, nameof(promptText));
ArgumentNullException.ThrowIfNull(choices, nameof(choices));
Expand All @@ -213,6 +213,11 @@ public async Task<IReadOnlyList<T>> PromptForSelectionsAsync<T>(string promptTex
.AddChoices(choices)
.PageSize(10);

if (optional)
{
prompt.NotRequired();
}

var result = await _outConsole.PromptAsync(prompt, cancellationToken);
return result;
}
Expand Down
4 changes: 2 additions & 2 deletions src/Aspire.Cli/Interaction/ExtensionInteractionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ await _extensionTaskChannel.Writer.WriteAsync(async () =>
}

public async Task<IReadOnlyList<T>> PromptForSelectionsAsync<T>(string promptText, IEnumerable<T> choices, Func<T, string> choiceFormatter,
CancellationToken cancellationToken = default) where T : notnull
bool optional = false, CancellationToken cancellationToken = default) where T : notnull
{
if (_extensionPromptEnabled)
{
Expand All @@ -290,7 +290,7 @@ await _extensionTaskChannel.Writer.WriteAsync(async () =>
}
else
{
return await _consoleInteractionService.PromptForSelectionsAsync(promptText, choices, choiceFormatter, cancellationToken);
return await _consoleInteractionService.PromptForSelectionsAsync(promptText, choices, choiceFormatter, optional, cancellationToken);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Cli/Interaction/IInteractionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ internal interface IInteractionService
Task<string> PromptForFilePathAsync(string promptText, string? defaultValue = null, Func<string, ValidationResult>? validator = null, bool directory = false, bool required = false, CancellationToken cancellationToken = default);
public Task<bool> ConfirmAsync(string promptText, bool defaultValue = true, CancellationToken cancellationToken = default);
Task<T> PromptForSelectionAsync<T>(string promptText, IEnumerable<T> choices, Func<T, string> choiceFormatter, CancellationToken cancellationToken = default) where T : notnull;
Task<IReadOnlyList<T>> PromptForSelectionsAsync<T>(string promptText, IEnumerable<T> choices, Func<T, string> choiceFormatter, CancellationToken cancellationToken = default) where T : notnull;
Task<IReadOnlyList<T>> PromptForSelectionsAsync<T>(string promptText, IEnumerable<T> choices, Func<T, string> choiceFormatter, bool optional = false, CancellationToken cancellationToken = default) where T : notnull;
int DisplayIncompatibleVersionError(AppHostIncompatibleException ex, string appHostHostingVersion);
void DisplayError(string errorMessage);
void DisplayMessage(KnownEmoji emoji, string message, bool allowMarkup = false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ private static PlaywrightCliInstaller CreatePlaywrightCliInstaller()
new FakeNpmRunner(),
new FakeNpmProvenanceChecker(),
new FakePlaywrightCliRunner(),
new TestConsoleInteractionService(),
new TestInteractionService(),
new ConfigurationBuilder().Build(),
NullLogger<PlaywrightCliInstaller>.Instance);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ private static PlaywrightCliInstaller CreatePlaywrightCliInstaller()
new FakeNpmRunner(),
new FakeNpmProvenanceChecker(),
new FakePlaywrightCliRunner(),
new TestConsoleInteractionService(),
new TestInteractionService(),
new ConfigurationBuilder().Build(),
NullLogger<PlaywrightCliInstaller>.Instance);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ private static PlaywrightCliInstaller CreatePlaywrightCliInstaller()
new FakeNpmRunner(),
new FakeNpmProvenanceChecker(),
new FakePlaywrightCliRunner(),
new TestConsoleInteractionService(),
new TestInteractionService(),
new ConfigurationBuilder().Build(),
NullLogger<PlaywrightCliInstaller>.Instance);
}
Expand Down
26 changes: 13 additions & 13 deletions tests/Aspire.Cli.Tests/Agents/PlaywrightCliInstallerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public async Task InstallAsync_WhenNpmResolveReturnsNull_ReturnsFalse()
ResolveResult = null
};
var playwrightRunner = new TestPlaywrightCliRunner();
var installer = new PlaywrightCliInstaller(npmRunner, new TestNpmProvenanceChecker(), playwrightRunner, new TestConsoleInteractionService(), new ConfigurationBuilder().Build(), NullLogger<PlaywrightCliInstaller>.Instance);
var installer = new PlaywrightCliInstaller(npmRunner, new TestNpmProvenanceChecker(), playwrightRunner, new TestInteractionService(), new ConfigurationBuilder().Build(), NullLogger<PlaywrightCliInstaller>.Instance);

var result = await installer.InstallAsync(CreateTestContext(), CancellationToken.None);

Expand All @@ -53,7 +53,7 @@ public async Task InstallAsync_WhenAlreadyInstalledAtSameVersion_SkipsInstallAnd
InstalledVersion = version,
InstallSkillsResult = true
};
var installer = new PlaywrightCliInstaller(npmRunner, new TestNpmProvenanceChecker(), playwrightRunner, new TestConsoleInteractionService(), new ConfigurationBuilder().Build(), NullLogger<PlaywrightCliInstaller>.Instance);
var installer = new PlaywrightCliInstaller(npmRunner, new TestNpmProvenanceChecker(), playwrightRunner, new TestInteractionService(), new ConfigurationBuilder().Build(), NullLogger<PlaywrightCliInstaller>.Instance);

var result = await installer.InstallAsync(CreateTestContext(), CancellationToken.None);

Expand All @@ -77,7 +77,7 @@ public async Task InstallAsync_WhenNewerVersionInstalled_SkipsInstallAndInstalls
InstalledVersion = installedVersion,
InstallSkillsResult = true
};
var installer = new PlaywrightCliInstaller(npmRunner, new TestNpmProvenanceChecker(), playwrightRunner, new TestConsoleInteractionService(), new ConfigurationBuilder().Build(), NullLogger<PlaywrightCliInstaller>.Instance);
var installer = new PlaywrightCliInstaller(npmRunner, new TestNpmProvenanceChecker(), playwrightRunner, new TestInteractionService(), new ConfigurationBuilder().Build(), NullLogger<PlaywrightCliInstaller>.Instance);

var result = await installer.InstallAsync(CreateTestContext(), CancellationToken.None);

Expand All @@ -96,7 +96,7 @@ public async Task InstallAsync_WhenPackFails_ReturnsFalse()
PackResult = null
};
var playwrightRunner = new TestPlaywrightCliRunner();
var installer = new PlaywrightCliInstaller(npmRunner, new TestNpmProvenanceChecker(), playwrightRunner, new TestConsoleInteractionService(), new ConfigurationBuilder().Build(), NullLogger<PlaywrightCliInstaller>.Instance);
var installer = new PlaywrightCliInstaller(npmRunner, new TestNpmProvenanceChecker(), playwrightRunner, new TestInteractionService(), new ConfigurationBuilder().Build(), NullLogger<PlaywrightCliInstaller>.Instance);

var result = await installer.InstallAsync(CreateTestContext(), CancellationToken.None);

Expand All @@ -122,7 +122,7 @@ public async Task InstallAsync_WhenIntegrityCheckFails_ReturnsFalse()
PackResult = tarballPath
};
var playwrightRunner = new TestPlaywrightCliRunner();
var installer = new PlaywrightCliInstaller(npmRunner, new TestNpmProvenanceChecker(), playwrightRunner, new TestConsoleInteractionService(), new ConfigurationBuilder().Build(), NullLogger<PlaywrightCliInstaller>.Instance);
var installer = new PlaywrightCliInstaller(npmRunner, new TestNpmProvenanceChecker(), playwrightRunner, new TestInteractionService(), new ConfigurationBuilder().Build(), NullLogger<PlaywrightCliInstaller>.Instance);

var result = await installer.InstallAsync(CreateTestContext(), CancellationToken.None);

Expand Down Expand Up @@ -161,7 +161,7 @@ public async Task InstallAsync_WhenIntegrityCheckPasses_InstallsGlobally()
{
InstallSkillsResult = true
};
var installer = new PlaywrightCliInstaller(npmRunner, new TestNpmProvenanceChecker(), playwrightRunner, new TestConsoleInteractionService(), new ConfigurationBuilder().Build(), NullLogger<PlaywrightCliInstaller>.Instance);
var installer = new PlaywrightCliInstaller(npmRunner, new TestNpmProvenanceChecker(), playwrightRunner, new TestInteractionService(), new ConfigurationBuilder().Build(), NullLogger<PlaywrightCliInstaller>.Instance);

var result = await installer.InstallAsync(CreateTestContext(), CancellationToken.None);

Expand Down Expand Up @@ -197,7 +197,7 @@ public async Task InstallAsync_WhenGlobalInstallFails_ReturnsFalse()
InstallGlobalResult = false
};
var playwrightRunner = new TestPlaywrightCliRunner();
var installer = new PlaywrightCliInstaller(npmRunner, new TestNpmProvenanceChecker(), playwrightRunner, new TestConsoleInteractionService(), new ConfigurationBuilder().Build(), NullLogger<PlaywrightCliInstaller>.Instance);
var installer = new PlaywrightCliInstaller(npmRunner, new TestNpmProvenanceChecker(), playwrightRunner, new TestInteractionService(), new ConfigurationBuilder().Build(), NullLogger<PlaywrightCliInstaller>.Instance);

var result = await installer.InstallAsync(CreateTestContext(), CancellationToken.None);

Expand Down Expand Up @@ -236,7 +236,7 @@ public async Task InstallAsync_WhenOlderVersionInstalled_PerformsUpgrade()
InstalledVersion = installedVersion,
InstallSkillsResult = true
};
var installer = new PlaywrightCliInstaller(npmRunner, new TestNpmProvenanceChecker(), playwrightRunner, new TestConsoleInteractionService(), new ConfigurationBuilder().Build(), NullLogger<PlaywrightCliInstaller>.Instance);
var installer = new PlaywrightCliInstaller(npmRunner, new TestNpmProvenanceChecker(), playwrightRunner, new TestInteractionService(), new ConfigurationBuilder().Build(), NullLogger<PlaywrightCliInstaller>.Instance);

var result = await installer.InstallAsync(CreateTestContext(), CancellationToken.None);

Expand Down Expand Up @@ -313,7 +313,7 @@ public async Task InstallAsync_WhenProvenanceCheckFails_ReturnsFalse()
};
var provenanceChecker = new TestNpmProvenanceChecker { ProvenanceOutcome = ProvenanceVerificationOutcome.SourceRepositoryMismatch };
var playwrightRunner = new TestPlaywrightCliRunner();
var installer = new PlaywrightCliInstaller(npmRunner, provenanceChecker, playwrightRunner, new TestConsoleInteractionService(), new ConfigurationBuilder().Build(), NullLogger<PlaywrightCliInstaller>.Instance);
var installer = new PlaywrightCliInstaller(npmRunner, provenanceChecker, playwrightRunner, new TestInteractionService(), new ConfigurationBuilder().Build(), NullLogger<PlaywrightCliInstaller>.Instance);

var result = await installer.InstallAsync(CreateTestContext(), CancellationToken.None);

Expand Down Expand Up @@ -347,7 +347,7 @@ public async Task InstallAsync_WhenValidationDisabled_SkipsAllValidationChecks()
[PlaywrightCliInstaller.DisablePackageValidationKey] = "true"
})
.Build();
var installer = new PlaywrightCliInstaller(npmRunner, provenanceChecker, playwrightRunner, new TestConsoleInteractionService(), configuration, NullLogger<PlaywrightCliInstaller>.Instance);
var installer = new PlaywrightCliInstaller(npmRunner, provenanceChecker, playwrightRunner, new TestInteractionService(), configuration, NullLogger<PlaywrightCliInstaller>.Instance);

var result = await installer.InstallAsync(CreateTestContext(), CancellationToken.None);

Expand Down Expand Up @@ -377,7 +377,7 @@ public async Task InstallAsync_WhenVersionOverrideConfigured_UsesOverrideVersion
[PlaywrightCliInstaller.VersionOverrideKey] = "0.2.0"
})
.Build();
var installer = new PlaywrightCliInstaller(npmRunner, new TestNpmProvenanceChecker(), playwrightRunner, new TestConsoleInteractionService(), configuration, NullLogger<PlaywrightCliInstaller>.Instance);
var installer = new PlaywrightCliInstaller(npmRunner, new TestNpmProvenanceChecker(), playwrightRunner, new TestInteractionService(), configuration, NullLogger<PlaywrightCliInstaller>.Instance);

await installer.InstallAsync(CreateTestContext(), CancellationToken.None);

Expand All @@ -393,7 +393,7 @@ public async Task InstallAsync_WhenNoVersionOverride_UsesDefaultRange()
ResolveResult = new NpmPackageInfo { Version = version, Integrity = "sha512-abc123" }
};
var playwrightRunner = new TestPlaywrightCliRunner();
var installer = new PlaywrightCliInstaller(npmRunner, new TestNpmProvenanceChecker(), playwrightRunner, new TestConsoleInteractionService(), new ConfigurationBuilder().Build(), NullLogger<PlaywrightCliInstaller>.Instance);
var installer = new PlaywrightCliInstaller(npmRunner, new TestNpmProvenanceChecker(), playwrightRunner, new TestInteractionService(), new ConfigurationBuilder().Build(), NullLogger<PlaywrightCliInstaller>.Instance);

await installer.InstallAsync(CreateTestContext(), CancellationToken.None);

Expand Down Expand Up @@ -428,7 +428,7 @@ public async Task InstallAsync_MirrorsSkillFilesToOtherAgentEnvironments()

var installer = new PlaywrightCliInstaller(
npmRunner, new TestNpmProvenanceChecker(), playwrightRunner,
new TestConsoleInteractionService(), new ConfigurationBuilder().Build(),
new TestInteractionService(), new ConfigurationBuilder().Build(),
NullLogger<PlaywrightCliInstaller>.Instance);

var context = new AgentEnvironmentScanContext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ private static PlaywrightCliInstaller CreatePlaywrightCliInstaller()
new FakeNpmRunner(),
new FakeNpmProvenanceChecker(),
new FakePlaywrightCliRunner(),
new TestConsoleInteractionService(),
new TestInteractionService(),
new ConfigurationBuilder().Build(),
NullLogger<PlaywrightCliInstaller>.Instance);
}
Expand Down
21 changes: 9 additions & 12 deletions tests/Aspire.Cli.Tests/Commands/AddCommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -408,18 +408,14 @@ public async Task AddCommandPreservesSourceArgumentInBothCommands()
[Fact]
public async Task AddCommand_EmptyPackageList_DisplaysErrorMessage()
{
string? displayedErrorMessage = null;
TestInteractionService? testInteractionService = null;

using var workspace = TemporaryWorkspace.Create(outputHelper);
var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options =>
{
options.InteractionServiceFactory = (sp) =>
{
var testInteractionService = new TestConsoleInteractionService();
testInteractionService.DisplayErrorCallback = (message) =>
{
displayedErrorMessage = message;
};
testInteractionService = new TestInteractionService();
return testInteractionService;
};

Expand All @@ -443,7 +439,8 @@ public async Task AddCommand_EmptyPackageList_DisplaysErrorMessage()

var exitCode = await result.InvokeAsync().DefaultTimeout();
Assert.Equal(ExitCodeConstants.FailedToAddPackage, exitCode);
Assert.Contains(AddCommandStrings.NoIntegrationPackagesFound, displayedErrorMessage);
Assert.NotNull(testInteractionService);
Assert.Contains(testInteractionService.DisplayedErrors, e => e.Contains(AddCommandStrings.NoIntegrationPackagesFound));
}

[Fact]
Expand All @@ -457,7 +454,7 @@ public async Task AddCommand_NoMatchingPackages_DisplaysNoMatchesMessage()
{
options.InteractionServiceFactory = (sp) =>
{
var testInteractionService = new TestConsoleInteractionService();
var testInteractionService = new TestInteractionService();
testInteractionService.DisplaySubtleMessageCallback = (message) =>
{
displayedSubtleMessage = message;
Expand Down Expand Up @@ -551,7 +548,7 @@ public async Task AddCommandPrompter_FiltersToHighestVersionPerPackageId()
{
options.InteractionServiceFactory = (sp) =>
{
var mockInteraction = new TestConsoleInteractionService();
var mockInteraction = new TestInteractionService();
mockInteraction.PromptForSelectionCallback = (message, choices, formatter, ct) =>
{
// Capture what the prompter passes to the interaction service
Expand Down Expand Up @@ -599,7 +596,7 @@ public async Task AddCommandPrompter_FiltersToHighestVersionPerChannel()
{
options.InteractionServiceFactory = (sp) =>
{
var mockInteraction = new TestConsoleInteractionService();
var mockInteraction = new TestInteractionService();
mockInteraction.PromptForSelectionCallback = (message, choices, formatter, ct) =>
{
// Capture what the prompter passes to the interaction service
Expand Down Expand Up @@ -647,7 +644,7 @@ public async Task AddCommandPrompter_ShowsHighestVersionPerChannelWhenMultipleCh
{
options.InteractionServiceFactory = (sp) =>
{
var mockInteraction = new TestConsoleInteractionService();
var mockInteraction = new TestInteractionService();
mockInteraction.PromptForSelectionCallback = (message, choices, formatter, ct) =>
{
// Capture what the prompter passes to the interaction service
Expand Down Expand Up @@ -700,7 +697,7 @@ public async Task AddCommand_WithoutHives_UsesImplicitChannelWithoutPrompting()
{
options.ProjectLocatorFactory = _ => new TestProjectLocator();

options.InteractionServiceFactory = _ => new TestConsoleInteractionService()
options.InteractionServiceFactory = _ => new TestInteractionService()
{
PromptForSelectionCallback = (message, choices, formatter, ct) =>
{
Expand Down
Loading
Loading