File: Layout\LayoutConfiguration.cs
Web Access
Project: src\src\Aspire.Cli\Aspire.Cli.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.Shared;
using Semver;
 
namespace Aspire.Cli.Layout;
 
/// <summary>
/// Known layout component types.
/// </summary>
public enum LayoutComponent
{
    /// <summary>CLI executable.</summary>
    Cli = 0,
    /// <summary>Developer Control Plane.</summary>
    Dcp = 1,
    /// <summary>Unified managed binary (server, NuGet, terminal host).</summary>
    Managed = 2,
    /// <summary>Dashboard executable and static assets.</summary>
    Dashboard = 3
}
 
/// <summary>
/// Configuration for the Aspire bundle layout.
/// Specifies paths to all components in a self-contained bundle.
/// </summary>
public sealed class LayoutConfiguration
{
    /// <summary>
    /// Bundle version (e.g., "13.2.0" or "dev" for local development).
    /// </summary>
    public string? Version { get; set; }
 
    /// <summary>
    /// Target platform (e.g., "linux-x64", "win-x64").
    /// </summary>
    public string? Platform { get; set; }
 
    /// <summary>
    /// Root path of the layout.
    /// </summary>
    public string? LayoutPath { get; set; }
 
    /// <summary>
    /// Component paths relative to LayoutPath.
    /// </summary>
    public LayoutComponents Components { get; set; } = new();
 
    /// <summary>
    /// List of integrations included in the bundle.
    /// </summary>
    public List<string> BuiltInIntegrations { get; set; } = [];
 
    /// <summary>
    /// Gets the absolute path to a component.
    /// </summary>
    public string? GetComponentPath(LayoutComponent component)
    {
        if (string.IsNullOrEmpty(LayoutPath))
        {
            return null;
        }
 
        var relativePath = component switch
        {
            LayoutComponent.Cli => Components.Cli,
            LayoutComponent.Dcp => Components.Dcp,
            LayoutComponent.Dashboard => Components.Dashboard,
            LayoutComponent.Managed => Components.Managed,
            _ => null
        };
 
        return relativePath is not null ? Path.Combine(LayoutPath, relativePath) : null;
    }
 
    /// <summary>
    /// Gets the path to the DCP directory.
    /// </summary>
    public string? GetDcpPath() => GetComponentPath(LayoutComponent.Dcp);
 
    /// <summary>
    /// Gets the path to the aspire-managed executable.
    /// </summary>
    /// <returns>The path to aspire-managed(.exe).</returns>
    public string? GetManagedPath()
    {
        var managedDir = GetComponentPath(LayoutComponent.Managed);
        if (managedDir is null)
        {
            return null;
        }
 
        return Path.Combine(managedDir, BundleDiscovery.GetExecutableFileName(BundleDiscovery.ManagedExecutableName));
    }
 
    /// <summary>
    /// Gets the path to the Native AOT Dashboard executable.
    /// </summary>
    /// <returns>The path to the Dashboard executable.</returns>
    public string? GetDashboardPath()
    {
        var dashboardDir = GetComponentPath(LayoutComponent.Dashboard);
        if (dashboardDir is null)
        {
            return null;
        }
 
        return Path.Combine(dashboardDir, BundleDiscovery.GetExecutableFileName(BundleDiscovery.DashboardExecutableName));
    }
}
 
/// <summary>
/// Component paths within the layout.
/// </summary>
public sealed class LayoutComponents
{
    /// <summary>
    /// Path to CLI executable (e.g., "aspire" or "aspire.exe").
    /// </summary>
    public string? Cli { get; set; } = "aspire";
 
    /// <summary>
    /// Path to Developer Control Plane.
    /// </summary>
    public string? Dcp { get; set; } = BundleDiscovery.DcpDirectoryName;
 
    /// <summary>
    /// Path to the Dashboard executable and static assets directory.
    /// </summary>
    public string? Dashboard { get; set; } = BundleDiscovery.DashboardDirectoryName;
 
    /// <summary>
    /// Path to the unified managed binary directory.
    /// </summary>
    public string? Managed { get; set; } = BundleDiscovery.ManagedDirectoryName;
}
 
/// <summary>
/// Selects a Dashboard executable compatible with the AppHost's Hosting version.
/// </summary>
internal static class DashboardLaunchHelper
{
    private static readonly SemVersion s_minimumNativeDashboardHostingVersion = SemVersion.Parse("13.6.0-0");
 
    public static bool SupportsNativeDashboard(SemVersion? hostingVersion)
    {
        // Older Hosting converts the Dashboard path to a DLL and launches it with dotnet exec.
        // Unknown versions use the managed forwarder to preserve that launch contract.
        return hostingVersion is not null &&
            hostingVersion.ComparePrecedenceTo(s_minimumNativeDashboardHostingVersion) >= 0;
    }
 
    public static string? GetDashboardPath(LayoutConfiguration layout, bool supportsNativeDashboard)
    {
        if (supportsNativeDashboard && layout.GetDashboardPath() is { } dashboardPath && File.Exists(dashboardPath))
        {
            return dashboardPath;
        }
 
        var managedPath = layout.GetManagedPath();
        return File.Exists(managedPath) ? managedPath : null;
    }
}