Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
{
"appHostPath": "../apphost.ts",
"language": "typescript/nodejs",
"channel": "pr-13970",
"sdkVersion": "13.2.0-pr.13970.g9fb24263",
"packages": {
"Aspire.Hosting.Azure.Storage": "13.2.0-pr.13970.g9fb24263"
"Aspire.Hosting.Azure.Storage": ""
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
{
"appHostPath": "../apphost.ts",
"language": "typescript/nodejs",
"channel": "local",
"sdkVersion": "13.2.0-preview.1.26081.1",
"packages": {
"Aspire.Hosting.RabbitMQ": "13.2.0-preview.1.26081.1"
"Aspire.Hosting.RabbitMQ": ""
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
{
"appHostPath": "../apphost.ts",
"language": "typescript/nodejs",
"channel": "pr-13970",
"sdkVersion": "13.1.0",
"packages": {
"Aspire.Hosting.SqlServer": "13.2.0-pr.13970.g0575147c"
"Aspire.Hosting.SqlServer": ""
}
}
}
9 changes: 7 additions & 2 deletions src/Aspire.Cli/Commands/AddCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,13 @@ protected override async Task<int> ExecuteAsync(ParseResult parseResult, Cancell
string? configuredChannel = null;
if (project.LanguageId != KnownLanguageId.CSharp)
{
var settings = AspireJsonConfiguration.Load(effectiveAppHostProjectFile.Directory!.FullName);
configuredChannel = settings?.Channel;
var appHostDirectory = effectiveAppHostProjectFile.Directory!.FullName;
var isProjectReferenceMode = AspireRepositoryDetector.DetectRepositoryRoot(appHostDirectory) is not null;
if (!isProjectReferenceMode)
{
var settings = AspireJsonConfiguration.Load(appHostDirectory);
configuredChannel = settings?.Channel;
}
}

var packagesWithChannels = await InteractionService.ShowStatusAsync(
Expand Down
16 changes: 12 additions & 4 deletions src/Aspire.Cli/Commands/UpdateCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,13 +146,22 @@ protected override async Task<int> ExecuteAsync(ParseResult parseResult, Cancell
return ExitCodeConstants.FailedToFindProject;
}

var allChannels = await _packagingService.GetChannelsAsync(cancellationToken);
var project = _projectFactory.GetProject(projectFile);
var isGuestProject = !project.LanguageId.Equals(KnownLanguageId.CSharp, StringComparison.OrdinalIgnoreCase);
var isProjectReferenceMode = isGuestProject && AspireRepositoryDetector.DetectRepositoryRoot(projectFile.Directory?.FullName) is not null;

// Check if channel or quality option was provided (channel takes precedence)
var channelName = parseResult.GetValue(_channelOption) ?? parseResult.GetValue(_qualityOption);
PackageChannel channel;

if (!string.IsNullOrEmpty(channelName))
var allChannels = await _packagingService.GetChannelsAsync(cancellationToken);

if (isProjectReferenceMode)
{
channel = allChannels.FirstOrDefault(c => c.Type is PackageChannelType.Implicit)
?? allChannels.First();
}
else if (!string.IsNullOrEmpty(channelName))
Comment thread
sebastienros marked this conversation as resolved.
Outdated
{
// Try to find a channel matching the provided channel/quality
channel = allChannels.FirstOrDefault(c => string.Equals(c.Name, channelName, StringComparison.OrdinalIgnoreCase))
Expand Down Expand Up @@ -181,8 +190,7 @@ protected override async Task<int> ExecuteAsync(ParseResult parseResult, Cancell
}
}

// Get the appropriate project handler and update packages
var project = _projectFactory.GetProject(projectFile);
// Update packages using the appropriate project handler
var updateContext = new UpdatePackagesContext
{
AppHostFile = projectFile,
Expand Down
58 changes: 41 additions & 17 deletions src/Aspire.Cli/Configuration/AspireJsonConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -163,32 +163,56 @@ public bool RemovePackage(string packageId)
}

/// <summary>
/// Gets all package references including the base Aspire.Hosting packages.
/// Uses the SdkVersion for base packages.
/// Note: Aspire.Hosting.AppHost is an SDK package (not a runtime DLL) and is excluded.
/// Gets the effective SDK version for package-based AppHost preparation.
/// Falls back to <paramref name="defaultSdkVersion"/> when no SDK version is configured.
/// </summary>
public string GetEffectiveSdkVersion(string defaultSdkVersion)
{
return string.IsNullOrWhiteSpace(SdkVersion) ? defaultSdkVersion : SdkVersion;
}

/// <summary>
/// Gets all package references including the base Aspire.Hosting package.
/// Empty package versions in settings are resolved to the effective SDK version.
/// </summary>
/// <param name="defaultSdkVersion">Default SDK version to use when not configured.</param>
/// <param name="useProjectReferences">Unused compatibility parameter.</param>
/// <returns>Enumerable of (PackageName, Version) tuples.</returns>
public IEnumerable<(string Name, string Version)> GetAllPackages()
public IEnumerable<(string Name, string Version)> GetAllPackages(string defaultSdkVersion, bool useProjectReferences)
{
var sdkVersion = SdkVersion ?? throw new InvalidOperationException("SdkVersion must be set before calling GetAllPackages. Use LoadOrCreate to ensure it's set.");
_ = useProjectReferences;
var sdkVersion = GetEffectiveSdkVersion(defaultSdkVersion);
Comment thread
sebastienros marked this conversation as resolved.

// Base packages always included (Aspire.Hosting.AppHost is an SDK, not a runtime DLL)
// Base package always included (Aspire.Hosting.AppHost is an SDK, not a runtime DLL)
yield return ("Aspire.Hosting", sdkVersion);

// Additional packages from settings
if (Packages is not null)
if (Packages is null)
{
yield break;
}

foreach (var (packageName, version) in Packages)
{
foreach (var (packageName, version) in Packages)
// Skip base packages and SDK-only packages
if (string.Equals(packageName, "Aspire.Hosting", StringComparison.OrdinalIgnoreCase) ||
string.Equals(packageName, "Aspire.Hosting.AppHost", StringComparison.OrdinalIgnoreCase))
{
// Skip base packages and SDK-only packages
if (string.Equals(packageName, "Aspire.Hosting", StringComparison.OrdinalIgnoreCase) ||
string.Equals(packageName, "Aspire.Hosting.AppHost", StringComparison.OrdinalIgnoreCase))
{
continue;
}

yield return (packageName, version);
continue;
}

yield return (packageName, string.IsNullOrWhiteSpace(version) ? sdkVersion : version);
}
}

/// <summary>
/// Gets all package references including the base Aspire.Hosting packages.
/// Uses the SdkVersion for base packages.
/// Note: Aspire.Hosting.AppHost is an SDK package (not a runtime DLL) and is excluded.
/// </summary>
/// <returns>Enumerable of (PackageName, Version) tuples.</returns>
public IEnumerable<(string Name, string Version)> GetAllPackages()
{
var sdkVersion = SdkVersion ?? throw new InvalidOperationException("SdkVersion must be set before calling GetAllPackages. Use LoadOrCreate to ensure it's set.");
Comment thread
sebastienros marked this conversation as resolved.
Outdated
return GetAllPackages(sdkVersion, useProjectReferences: false);
}
}
39 changes: 2 additions & 37 deletions src/Aspire.Cli/Projects/AppHostServerProject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Aspire.Cli.DotNet;
using Aspire.Cli.NuGet;
using Aspire.Cli.Packaging;
using Aspire.Cli.Utils;
using Microsoft.Extensions.Logging;

namespace Aspire.Cli.Projects;
Expand Down Expand Up @@ -58,7 +59,7 @@ public async Task<IAppHostServerProject> CreateAsync(string appPath, Cancellatio
}

// Priority 1: Check for dev mode (ASPIRE_REPO_ROOT or running from Aspire source repo)
var repoRoot = DetectAspireRepoRoot();
var repoRoot = AspireRepositoryDetector.DetectRepositoryRoot(appPath);
if (repoRoot is not null)
{
return new DotNetBasedAppHostServerProject(
Expand Down Expand Up @@ -91,40 +92,4 @@ public async Task<IAppHostServerProject> CreateAsync(string appPath, Cancellatio
"No Aspire AppHost server is available. Ensure the Aspire CLI is installed " +
"with a valid bundle layout, or reinstall using 'aspire setup --force'.");
}

/// <summary>
/// Detects the Aspire repository root for dev mode.
/// Checks ASPIRE_REPO_ROOT env var first, then walks up from the CLI executable
/// looking for a git repo containing Aspire.slnx.
/// </summary>
private static string? DetectAspireRepoRoot()
{
// Check explicit environment variable
var envRoot = Environment.GetEnvironmentVariable("ASPIRE_REPO_ROOT");
if (!string.IsNullOrEmpty(envRoot) && Directory.Exists(envRoot))
{
return envRoot;
}

// Auto-detect: walk up from the CLI executable looking for .git + Aspire.slnx
var cliPath = Environment.ProcessPath;
if (string.IsNullOrEmpty(cliPath))
{
return null;
}

var dir = Path.GetDirectoryName(cliPath);
while (dir is not null)
{
if (Directory.Exists(Path.Combine(dir, ".git")) &&
File.Exists(Path.Combine(dir, "Aspire.slnx")))
{
return dir;
}

dir = Path.GetDirectoryName(dir);
}

return null;
}
}
52 changes: 31 additions & 21 deletions src/Aspire.Cli/Projects/GuestAppHostProject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,15 +135,28 @@ public bool CanHandle(FileInfo appHostFile)
AspireJsonConfiguration config,
CancellationToken cancellationToken)
{
var packages = config.GetAllPackages().ToList();
var defaultSdkVersion = GetEffectiveSdkVersion();
var packages = config.GetAllPackages(defaultSdkVersion, useProjectReferences: false).ToList();
var codeGenPackage = await _languageDiscovery.GetPackageForLanguageAsync(_resolvedLanguage.LanguageId, cancellationToken);
if (codeGenPackage is not null)
{
packages.Add((codeGenPackage, config.SdkVersion!));
var codeGenVersion = config.GetEffectiveSdkVersion(defaultSdkVersion);
packages.Add((codeGenPackage, codeGenVersion));
}
return packages;
}

private AspireJsonConfiguration LoadConfiguration(DirectoryInfo directory)
{
var effectiveSdkVersion = GetEffectiveSdkVersion();
return AspireJsonConfiguration.LoadOrCreate(directory.FullName, effectiveSdkVersion);
}

private string GetPrepareSdkVersion(AspireJsonConfiguration config)
{
return config.GetEffectiveSdkVersion(GetEffectiveSdkVersion());
}

/// <summary>
/// Prepares the AppHost server (creates files and builds for dev mode, restores packages for prebuilt mode).
/// </summary>
Expand All @@ -162,14 +175,14 @@ public bool CanHandle(FileInfo appHostFile)
/// </summary>
private async Task BuildAndGenerateSdkAsync(DirectoryInfo directory, CancellationToken cancellationToken)
{
var appHostServerProject = await _appHostServerProjectFactory.CreateAsync(directory.FullName, cancellationToken);

// Step 1: Load config - source of truth for SDK version and packages
var effectiveSdkVersion = GetEffectiveSdkVersion();
var config = AspireJsonConfiguration.LoadOrCreate(directory.FullName, effectiveSdkVersion);
var config = LoadConfiguration(directory);
var packages = await GetAllPackagesAsync(config, cancellationToken);
var sdkVersion = GetPrepareSdkVersion(config);

var appHostServerProject = await _appHostServerProjectFactory.CreateAsync(directory.FullName, cancellationToken);

var (buildSuccess, buildOutput, _, _) = await PrepareAppHostServerAsync(appHostServerProject, config.SdkVersion!, packages, cancellationToken);
var (buildSuccess, buildOutput, _, _) = await PrepareAppHostServerAsync(appHostServerProject, sdkVersion, packages, cancellationToken);
if (!buildSuccess)
{
if (buildOutput is not null)
Expand Down Expand Up @@ -269,19 +282,19 @@ public async Task<int> RunAsync(AppHostProjectContext context, CancellationToken
}

// Build phase: build AppHost server (dependency install happens after server starts)
var appHostServerProject = await _appHostServerProjectFactory.CreateAsync(directory.FullName, cancellationToken);

// Load config - source of truth for SDK version and packages
var effectiveSdkVersion = GetEffectiveSdkVersion();
var config = AspireJsonConfiguration.LoadOrCreate(directory.FullName, effectiveSdkVersion);
var config = LoadConfiguration(directory);
var packages = await GetAllPackagesAsync(config, cancellationToken);

var appHostServerProject = await _appHostServerProjectFactory.CreateAsync(directory.FullName, cancellationToken);
var sdkVersion = GetPrepareSdkVersion(config);

var buildResult = await _interactionService.ShowStatusAsync(
":gear: Preparing Aspire server...",
async () =>
{
// Prepare the AppHost server (build for dev mode, restore for prebuilt)
var (prepareSuccess, prepareOutput, channelName, needsCodeGen) = await PrepareAppHostServerAsync(appHostServerProject, config.SdkVersion!, packages, cancellationToken);
var (prepareSuccess, prepareOutput, channelName, needsCodeGen) = await PrepareAppHostServerAsync(appHostServerProject, sdkVersion, packages, cancellationToken);
if (!prepareSuccess)
{
return (Success: false, Output: prepareOutput, Error: "Failed to prepare app host.", ChannelName: (string?)null, NeedsCodeGen: false);
Expand Down Expand Up @@ -557,14 +570,13 @@ public async Task<int> PublishAsync(PublishContext context, CancellationToken ca
try
{
// Step 1: Load config - source of truth for SDK version and packages
var effectiveSdkVersion = GetEffectiveSdkVersion();
var config = AspireJsonConfiguration.LoadOrCreate(directory.FullName, effectiveSdkVersion);
var packages = await GetAllPackagesAsync(config, cancellationToken);

var appHostServerProject = await _appHostServerProjectFactory.CreateAsync(directory.FullName, cancellationToken);
var config = LoadConfiguration(directory);
var packages = await GetAllPackagesAsync(config, cancellationToken);
var sdkVersion = GetPrepareSdkVersion(config);

// Prepare the AppHost server (build for dev mode, restore for prebuilt)
var (prepareSuccess, prepareOutput, _, needsCodeGen) = await PrepareAppHostServerAsync(appHostServerProject, config.SdkVersion!, packages, cancellationToken);
var (prepareSuccess, prepareOutput, _, needsCodeGen) = await PrepareAppHostServerAsync(appHostServerProject, sdkVersion, packages, cancellationToken);
if (!prepareSuccess)
{
// Set OutputCollector so PipelineCommandBase can display errors
Expand Down Expand Up @@ -802,8 +814,7 @@ public async Task<bool> AddPackageAsync(AddPackageContext context, CancellationT
}

// Load config - source of truth for SDK version and packages
var effectiveSdkVersion = GetEffectiveSdkVersion();
var config = AspireJsonConfiguration.LoadOrCreate(directory.FullName, effectiveSdkVersion);
var config = LoadConfiguration(directory);

// Update .aspire/settings.json with the new package
config.AddOrUpdatePackage(context.PackageId, context.PackageVersion);
Expand All @@ -825,8 +836,7 @@ public async Task<UpdatePackagesResult> UpdatePackagesAsync(UpdatePackagesContex
}

// Load config - source of truth for SDK version and packages
var effectiveSdkVersion = GetEffectiveSdkVersion();
var config = AspireJsonConfiguration.LoadOrCreate(directory.FullName, effectiveSdkVersion);
var config = LoadConfiguration(directory);

// Find updates for SDK version and packages
string? newSdkVersion = null;
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Cli/Projects/ProjectLocator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ private async Task CreateSettingsFileAsync(FileInfo projectFile, CancellationTok
if (language is not null && !language.LanguageId.Value.Equals(KnownLanguageId.CSharp, StringComparison.OrdinalIgnoreCase))
{
await configurationService.SetConfigurationAsync("language", language.LanguageId.Value, isGlobal: false, cancellationToken);

// Inherit SDK version from parent/global config if available
var inheritedSdkVersion = await configurationService.GetConfigurationAsync("sdkVersion", cancellationToken);
if (!string.IsNullOrEmpty(inheritedSdkVersion))
Expand Down
13 changes: 7 additions & 6 deletions src/Aspire.Cli/Scaffolding/ScaffoldingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,25 +55,25 @@ private async Task ScaffoldGuestLanguageAsync(ScaffoldContext context, Cancellat
var directory = context.TargetDirectory;
var language = context.Language;

// Step 1: Resolve SDK version from channel (if configured) or use default
// Step 1: Resolve SDK and package strategy
var sdkVersion = await ResolveSdkVersionAsync(cancellationToken);

// Load or create config with resolved SDK version
var config = AspireJsonConfiguration.LoadOrCreate(directory.FullName, sdkVersion);

// Include the code generation package for scaffolding and code gen
var codeGenPackage = await _languageDiscovery.GetPackageForLanguageAsync(language.LanguageId, cancellationToken);
var packages = config.GetAllPackages().ToList();
var packages = config.GetAllPackages(sdkVersion, useProjectReferences: false).ToList();
if (codeGenPackage is not null)
{
packages.Add((codeGenPackage, config.SdkVersion!));
var codeGenVersion = config.GetEffectiveSdkVersion(sdkVersion);
packages.Add((codeGenPackage, codeGenVersion));
}

var appHostServerProject = await _appHostServerProjectFactory.CreateAsync(directory.FullName, cancellationToken);
var prepareSdkVersion = config.GetEffectiveSdkVersion(sdkVersion);

var prepareResult = await _interactionService.ShowStatusAsync(
":gear: Preparing Aspire server...",
() => appHostServerProject.PrepareAsync(config.SdkVersion!, packages, cancellationToken));
() => appHostServerProject.PrepareAsync(prepareSdkVersion, packages, cancellationToken));
if (!prepareResult.Success)
{
if (prepareResult.Output is not null)
Expand Down Expand Up @@ -134,6 +134,7 @@ await GenerateCodeViaRpcAsync(
{
config.Channel = prepareResult.ChannelName;
}

config.Language = language.LanguageId;
config.Save(directory.FullName);
}
Expand Down
Loading
Loading