File: Projects\IAppHostProject.cs
Web Access
Project: src\src\Aspire.Cli\Aspire.Cli.Tool.csproj (aspire)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Aspire.Cli.Backchannel;
using Aspire.Cli.Utils;
 
namespace Aspire.Cli.Projects;
 
/// <summary>
/// Result of validating an AppHost file.
/// </summary>
internal record AppHostValidationResult(
    bool IsValid,
    bool IsPossiblyUnbuildable = false,
    string? Message = null);
 
/// <summary>
/// Context for updating packages in an AppHost project.
/// </summary>
internal sealed class UpdatePackagesContext
{
    /// <summary>
    /// Gets or sets the AppHost file.
    /// </summary>
    public required FileInfo AppHostFile { get; init; }
 
    /// <summary>
    /// Gets or sets the package channel to update to.
    /// </summary>
    public required Packaging.PackageChannel Channel { get; init; }
}
 
/// <summary>
/// Result of updating packages in an AppHost project.
/// </summary>
internal sealed class UpdatePackagesResult
{
    /// <summary>
    /// Gets or sets whether any updates were applied.
    /// </summary>
    public bool UpdatesApplied { get; init; }
}
 
/// <summary>
/// Context for adding a package to an AppHost project.
/// </summary>
internal sealed class AddPackageContext
{
    /// <summary>
    /// Gets or sets the AppHost file.
    /// </summary>
    public required FileInfo AppHostFile { get; init; }
 
    /// <summary>
    /// Gets or sets the package ID to add.
    /// </summary>
    public required string PackageId { get; init; }
 
    /// <summary>
    /// Gets or sets the package version to add.
    /// </summary>
    public required string PackageVersion { get; init; }
 
    /// <summary>
    /// Gets or sets the optional NuGet source.
    /// </summary>
    public string? Source { get; init; }
 
    /// <summary>
    /// Gets or sets the output collector for capturing stdout/stderr.
    /// Project implementations populate this during execution.
    /// Commands can access it for error display.
    /// </summary>
    public OutputCollector? OutputCollector { get; set; }
}
 
/// <summary>
/// Context for publishing an AppHost project.
/// </summary>
internal sealed class PublishContext
{
    /// <summary>
    /// Gets the AppHost file to publish.
    /// </summary>
    public required FileInfo AppHostFile { get; init; }
 
    /// <summary>
    /// Gets the output path for publish artifacts.
    /// </summary>
    public string? OutputPath { get; init; }
 
    /// <summary>
    /// Gets additional environment variables to pass to the AppHost.
    /// </summary>
    public IDictionary<string, string> EnvironmentVariables { get; init; } = new Dictionary<string, string>();
 
    /// <summary>
    /// Gets the arguments to pass to the AppHost for publishing.
    /// </summary>
    public string[] Arguments { get; init; } = [];
 
    /// <summary>
    /// Gets the task completion source for the backchannel connection.
    /// </summary>
    public TaskCompletionSource<IAppHostCliBackchannel>? BackchannelCompletionSource { get; init; }
 
    /// <summary>
    /// Gets the working directory for the command.
    /// </summary>
    public required DirectoryInfo WorkingDirectory { get; init; }
 
    /// <summary>
    /// Gets or sets the output collector for capturing stdout/stderr.
    /// Project implementations populate this during execution.
    /// Commands can access it for error display.
    /// </summary>
    public OutputCollector? OutputCollector { get; set; }
 
    /// <summary>
    /// Gets whether debug logging is enabled.
    /// </summary>
    public bool Debug { get; init; }
}
 
/// <summary>
/// Interface for AppHost projects of various types.
/// This is the single extension point for adding new language support.
/// </summary>
internal interface IAppHostProject
{
    /// <summary>
    /// Gets the unique identifier for this language (e.g., "csharp", "typescript").
    /// Used for configuration storage and CLI arguments.
    /// </summary>
    string LanguageId { get; }
 
    /// <summary>
    /// Gets the human-readable display name (e.g., "C# (.NET)", "TypeScript (Node.js)").
    /// </summary>
    string DisplayName { get; }
 
    /// <summary>
    /// Gets the file patterns to search for when detecting apphosts.
    /// Examples: ["*.csproj", "*.fsproj", "apphost.cs"] or ["apphost.ts"]
    /// </summary>
    string[] DetectionPatterns { get; }
 
    /// <summary>
    /// Determines if this handler can process the given file.
    /// Called after DetectionPatterns match to do deeper validation.
    /// </summary>
    /// <param name="appHostFile">The candidate apphost file.</param>
    /// <returns>True if this handler can process the file; otherwise, false.</returns>
    bool CanHandle(FileInfo appHostFile);
 
    /// <summary>
    /// Gets the default apphost filename for this language (e.g., "apphost.cs", "apphost.ts").
    /// </summary>
    string AppHostFileName { get; }
 
    /// <summary>
    /// Creates a new apphost project in the specified directory.
    /// </summary>
    /// <param name="directory">The directory to create the project in.</param>
    /// <param name="projectName">Optional project name.</param>
    /// <param name="cancellationToken">A cancellation token.</param>
    /// <returns>A task representing the scaffolding operation.</returns>
    Task ScaffoldAsync(DirectoryInfo directory, string? projectName, CancellationToken cancellationToken);
 
    /// <summary>
    /// Runs the AppHost project.
    /// </summary>
    /// <param name="context">The context containing all information needed to run the AppHost.</param>
    /// <param name="cancellationToken">A cancellation token.</param>
    /// <returns>The exit code from running the AppHost.</returns>
    Task<int> RunAsync(AppHostProjectContext context, CancellationToken cancellationToken);
 
    /// <summary>
    /// Publishes the AppHost project.
    /// </summary>
    /// <param name="context">The context containing all information needed to publish the AppHost.</param>
    /// <param name="cancellationToken">A cancellation token.</param>
    /// <returns>The exit code from publishing the AppHost.</returns>
    Task<int> PublishAsync(PublishContext context, CancellationToken cancellationToken);
 
    /// <summary>
    /// Validates that a candidate file is a valid AppHost for this project type.
    /// This does deeper validation beyond just file pattern matching.
    /// </summary>
    /// <param name="appHostFile">The candidate AppHost file to validate.</param>
    /// <param name="cancellationToken">A cancellation token.</param>
    /// <returns>A validation result indicating if the file is valid and any additional status.</returns>
    Task<AppHostValidationResult> ValidateAppHostAsync(FileInfo appHostFile, CancellationToken cancellationToken);
 
    /// <summary>
    /// Adds a package to the AppHost project.
    /// </summary>
    /// <param name="context">The context containing package information.</param>
    /// <param name="cancellationToken">A cancellation token.</param>
    /// <returns>True if the package was added successfully; otherwise, false.</returns>
    Task<bool> AddPackageAsync(AddPackageContext context, CancellationToken cancellationToken);
 
    /// <summary>
    /// Updates packages in the AppHost project to the latest versions.
    /// </summary>
    /// <param name="context">The context containing update information.</param>
    /// <param name="cancellationToken">A cancellation token.</param>
    /// <returns>The result of the update operation.</returns>
    Task<UpdatePackagesResult> UpdatePackagesAsync(UpdatePackagesContext context, CancellationToken cancellationToken);
 
    /// <summary>
    /// Checks for and handles any running instance of this AppHost.
    /// </summary>
    /// <param name="appHostFile">The AppHost file to check for running instances.</param>
    /// <param name="homeDirectory">The user's home directory for computing socket paths.</param>
    /// <param name="cancellationToken">A cancellation token.</param>
    /// <returns>True if no running instance or it was successfully stopped; otherwise, false.</returns>
    Task<bool> CheckAndHandleRunningInstanceAsync(FileInfo appHostFile, DirectoryInfo homeDirectory, CancellationToken cancellationToken);
}