Skip to content

Fix regressions in aspire update#17192

Merged
mitchdenny merged 8 commits into
mainfrom
mitchdenny/fix-aspire-update-regressions
May 18, 2026
Merged

Fix regressions in aspire update#17192
mitchdenny merged 8 commits into
mainfrom
mitchdenny/fix-aspire-update-regressions

Conversation

@mitchdenny
Copy link
Copy Markdown
Member

@mitchdenny mitchdenny commented May 18, 2026

Fixes a cluster of regressions that surface together when running aspire update against a project whose AppHost still references a since-replaced PR hive, plus the CLI-acquisition and aspire new papercuts that compound them.

Symptoms

  1. Crash: aspire update exits with No AppHosts were found (there may be AppHost project files with syntax errors/invalid SDK versions) even when an AppHost is explicitly configured in aspire.config.json.
  2. Wrong default channel: instead of prompting/picking the PR channel that matches the running CLI build, update offers daily (resolves Aspire packages from nuget.org).
  3. Restore failure after update: even when the update completes, the next dotnet restore fails with NU1301: The local source '/Users/.../.aspire/hives/pr-<old>/packages' doesn't exist.
  4. PR install pollutes shell profile: every PR install of the CLI wrote a fresh export PATH=... line into ~/.zshrc / ~/.bashrc (and the Windows user PATH), silently demoting the user's daily/stable install on every new shell until they hunted down the stale entry.
  5. aspire new does not pin the channel it scaffolded from: a project created by a PR-built or daily CLI ends up with no channel in aspire.config.json, so the next aspire update either prompts to flip to daily or silently routes through the implicit/nuget.org channel.

Root causes

A — Settings-configured AppHost path goes through MSBuild validation

UseOrFindAppHostProjectFileAsync runs DotNetAppHostProject.ValidateAppHostAsync against the AppHost path stored in aspire.config.json. Validation invokes MSBuild, which fails when Aspire.AppHost.Sdk/<pinned-version> can no longer be resolved (e.g. the user updated to a new build of the same PR and the old hive is gone). The discovery scan then falls back to PossiblyUnbuildable for the same file, producing a ProjectLocatorException. This blocks the very command whose job it is to repair the unresolvable SDK.

B — The CLI no longer falls back to its identity channel

UpdateCommand channel resolution is --channel → per-project config → global config → prompt/implicit. Previously the PR install script wrote channel: pr-<N> into the global aspire.config.json; that was removed in favor of CliExecutionContext.IdentityChannel (baked into the assembly), but UpdateCommand was never wired up to consult it. Result: a PR-built CLI sees no channel anywhere and silently falls through to the implicit/default ("daily") option.

C — Orphaned PR-hive sources linger in <packageSources>

NuGetConfigMerger removes a stale source only when its <packageSource> mapping element becomes empty during the merge. A source that has no mapping element at all (e.g. inherited from a parent config or written by a prior partial merge) survives forever. Previously the same hive directory got refreshed in-place so the dangling source still resolved; once PR builds started rotating hive directories the source points at a missing path and dotnet restore fails with NU1301.

D — PR install writes persistent PATH entries

get-aspire-cli-pr.{sh,ps1} was treating PR installs the same as release-channel installs and writing the dogfood/pr-<N>/bin path into the user's shell profile (Unix) or HKCU\Environment (Windows). PR builds are a per-session dogfood activation, not a baseline tool install; writing them persistently meant the next terminal silently picked up the PR CLI instead of the user's daily/stable install, and the entries accumulated as the user tried more PRs.

E — aspire new does not persist the resolved channel

A project scaffolded with aspire new aspire-starter (or aspire-apphost, aspire-py-starter, aspire-go-starter) using a PR-built or daily CLI did not get channel written into the scaffolded aspire.config.json. Running aspire update on that project then either prompts to switch to daily or silently routes through the Implicit/nuget.org channel, moving the project off the channel the CLI built it from. The TypeScript starter path was already pinning the channel; the .NET, Python, and Go starter paths were not.

Fix

A — UpdateCommand consults settings without MSBuild validation

ProjectLocator.GetAppHostFromSettingsAsync (existing helper, name already implied "from settings") now only does the file-exists + handler check; the MSBuild ValidateAppHostAsync call moved out. UpdateCommand.ExecuteAsync calls GetAppHostFromSettingsAsync first and only falls back to UseOrFindAppHostProjectFileAsync when there is no explicit --apphost and no entry in aspire.config.json. Other commands keep the existing strict-validation behavior of UseOrFindAppHostProjectFileAsync.

This is intentionally smaller than the original enum/overload approach we tried — update is the one command that needs to tolerate an AppHost whose SDK pin no longer resolves, because that is what it is there to fix.

B — Identity channel fallback in UpdateCommand

After the existing --channel / per-project / global lookups fail, look up ExecutionContext.IdentityChannel against allChannels (case-insensitive). If a match is found, use it silently. local is excluded so a developer-built CLI does not silently pin a shared project to a per-machine hive.

C — Sweep orphaned safe-to-remove sources after merge

New final pass in UpdateExistingPackageSourceMapping walks <packageSources> and removes any entry whose URL passes the existing IsSourceSafeToRemove heuristic (paths under .aspire/hives) and is not in sourcesInUse and is not in the new channel's RequiredSources. This is conservative — only PR-hive style paths are affected; user feeds, nuget.org, and Microsoft-controlled sources are left alone.

D — Treat PR installs as session-only

get-aspire-cli-pr.sh skips add_to_shell_profile entirely when PR_NUMBER is set; get-aspire-cli-pr.ps1 skips the persistent user-PATH write when the install target matches dogfood\pr-*\bin. Both still update the current-session PATH and print the activation hint so the user can opt into persistence manually if they choose. Non-PR routes through these scripts (release channels, explicit --install-prefix) keep the previous persistent-PATH behavior. Restarting a terminal now returns the user to the daily/stable install they had before.

E — Scaffolding persists the resolved channel across all starters

DotNetTemplateFactory.ApplyTemplateAsync, CliTemplateFactory.PythonStarterTemplate, and CliTemplateFactory.GoStarterTemplate now mirror the TypeScript starter pattern: when the resolved channel is PackageChannelType.Explicit, load the scaffolded aspire.config.json (creating one if dotnet new did not), set Channel, and save. Implicit channels (stable/nuget.org) are intentionally not persisted so aspire add / aspire restore continue to use the user's ambient NuGet config.

Note on aspire update --self and PR builds

The existing --self prompt offers { Stable, Daily, (Staging) } and routes through _cliDownloader.DownloadLatestCliAsync, which cannot fetch a PR build (PR channels have no CliDownloadBaseUrl). This is deliberately left alone: it doubles as the ejection mechanism for users who want off a PR build. Users who want to refresh the same PR re-run the acquisition script (the supported refresh path).

Tests

ProjectLocatorTests (Fix A):

  • GetAppHostFromSettings_* series — settings lookup returns the configured path even when MSBuild evaluation would fail, but still requires the file to exist and to have a registered handler.
  • UseOrFindAppHostProjectFile_RejectsUnbuildableSettingsAppHost — locks down the existing strict behavior for non-update callers.

UpdateCommandTests (Fix A + B):

  • UpdateCommand_WhenAppHostSdkVersionUnresolvable_UsesSettingsLookup — regression guard for the crash.
  • UpdateCommand_WhenIdentityChannel* — covers identity fallback, the local exclusion, the "identity has no matching channel" path, and the precedence ordering against --channel and per-project config.
  • UpdateCommand_SelfUpdate_WhenIdentityChannelIsPrAndExplicitChannelGiven_AllowsDownload — explicit --channel still wins on --self for a PR build.
  • UpdateCommand_SelfUpdate_WhenIdentityChannelIsDaily_AllowsDownload — guard that ordinary identity channels still proceed to download.

NuGetConfigMergerTests (Fix C):

  • CreateOrUpdateAsync_RemovesOldPrHive_WhenSwitchingBetweenPrHives — original config with the old hive mapped (locks down the existing empty-element path).
  • CreateOrUpdateAsync_RemovesOldPrHive_WhenItHasNoMappingElement — original config with the old hive listed but unmapped (the actual failure mode).

PRScriptFunctionTests (Fix D):

  • AddToPath_AppendsExportLine — pins the simple append contract for the lower-level helper now that PR installs bypass add_to_shell_profile entirely.

NewCommandTemplateConfigPersistenceTests (Fix E):

  • Extended theory matrices to cover KnownTemplateId.DotNetStarter, DotNetAppHost, PythonStarter, GoStarter, and the existing TS starter — asserts each starter writes channel into aspire.config.json when the resolved channel is Explicit, and writes nothing for Implicit channels.

Full Aspire.Cli.Tests (3110) and Aspire.Acquisition.Tests (179) suites pass.

Copilot AI review requested due to automatic review settings May 18, 2026 00:26
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 18, 2026

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 17192

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 17192"

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes two independent regressions in aspire update: (A) a crash when the configured AppHost's pinned Aspire.AppHost.Sdk can no longer be resolved by MSBuild, and (B) update silently defaulting to the implicit/daily channel for users whose AppHost predates the channel-bake change, even when the running CLI clearly identifies as a specific PR/daily build.

Changes:

  • Introduces AppHostSettingsValidation (Strict / TrustConfiguredPath) plus overloads on IProjectLocator; only UpdateCommand opts into trust mode so MSBuild validation is skipped for the configured path.
  • UpdateCommand now falls back to CliExecutionContext.IdentityChannel (case-insensitive match against allChannels, excluding local) before prompting/implicit selection.
  • Adds 4 locator tests and 8 update-command tests covering trust-mode contract and identity-channel resolution matrix.
Show a summary per file
File Description
src/Aspire.Cli/Projects/ProjectLocator.cs New AppHostSettingsValidation enum + interface/impl overloads; GetValidatedAppHostProjectFileFromSettingsAsync short-circuits MSBuild validation in trust mode.
src/Aspire.Cli/Commands/UpdateCommand.cs Calls locator with TrustConfiguredPath; consults ExecutionContext.IdentityChannel between per-project/global lookup and the hives prompt.
tests/Aspire.Cli.Tests/Projects/ProjectLocatorTests.cs Locks down trust-mode contract (returns unvalidated path, still requires existence + handler) and strict behavior for other commands.
tests/Aspire.Cli.Tests/Commands/UpdateCommandTests.cs Asserts UpdateCommand uses trust mode and identity-channel matrix (match wins, local skipped, stale falls through, explicit/per-project still override).
tests/Aspire.Cli.Tests/TestServices/TestProjectLocator.cs Adds validation-aware callback + overloads to allow tests to capture/route the new locator API.

Copilot's findings

  • Files reviewed: 5/5 changed files
  • Comments generated: 3

Comment thread src/Aspire.Cli/Commands/UpdateCommand.cs Outdated
Comment thread src/Aspire.Cli/Projects/ProjectLocator.cs Outdated
Comment thread tests/Aspire.Cli.Tests/Commands/UpdateCommandTests.cs Outdated
* Update no longer crashes when the AppHost's pinned Aspire.AppHost.Sdk
  version cannot be resolved (e.g. moving between PR builds after the
  hive was refreshed). UpdateCommand now calls the project locator in
  TrustConfiguredPath mode, which skips MSBuild validation of the
  configured AppHost path so ProjectUpdater can rewrite the SDK pin
  via its existing fallback parser.

* Update no longer defaults to the Implicit ("daily") channel when a
  PR-built CLI is run against an AppHost that has no per-project or
  global channel pin. UpdateCommand now consults
  ExecutionContext.IdentityChannel as a default before the prompt
  fallback, restoring the pre-#16820 behavior without re-introducing
  any global channel writes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mitchdenny mitchdenny force-pushed the mitchdenny/fix-aspire-update-regressions branch from 0f07799 to 7b47902 Compare May 18, 2026 00:36
When 'aspire update' merges a project's NuGet.config to a new channel, any
~/.aspire/hives/pr-<N>/packages entry that lives only in <packageSources>
(no corresponding <packageSourceMapping> element) used to survive the merge.
RemoveEmptyPackageSourceElements only cleans up <packageSource> mapping
entries that become empty during the merge, so a source that was never
mapped — or whose mapping was rewritten by an earlier merge — would linger
forever. As soon as the hive directory is replaced on disk (which now
happens routinely for refreshed PR builds), 'dotnet restore' fails with
NU1301: The local source '...' doesn't exist.

Add a final pass over <packageSources> that removes any safe-to-remove
source (the existing IsSourceSafeToRemove heuristic — paths under
.aspire/hives) that is not in sourcesInUse and not in the new channel's
RequiredSources.

Add two regression tests covering both shapes: original config with a
PR-hive source mapped (already cleaned by the empty-element pass; locked
down) and original config with the same PR-hive source listed but with no
mapping element (the actual failure mode).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mitchdenny mitchdenny changed the title Fix two regressions in aspire update Fix three regressions in aspire update May 18, 2026
Comment thread src/Aspire.Cli/Packaging/NuGetConfigMerger.cs
Comment thread src/Aspire.Cli/Commands/UpdateCommand.cs
@mitchdenny mitchdenny changed the title Fix three regressions in aspire update DO NOT MERGE: Fix three regressions in aspire update May 18, 2026
Two additional regressions that compounded the aspire update problems:

(D) get-aspire-cli-pr.{sh,ps1} appended a fresh PATH entry on every PR
    install because the install path embeds the PR number
    (/Users/midenn/.aspire/dogfood/pr-<N>/bin), so the literal-line dedup in
    add_to_path never matched any prior PR install. Detect any existing
    dogfood/pr-*/bin line and replace it in place when adding a new one;
    do the same for the Windows user PATH registry entry.

(E) aspire update --self prompts {Stable, Daily, Staging} and routes
    through _cliDownloader.DownloadLatestCliAsync. PR channels have no
    cliDownloadBaseUrl, so a PR-built CLI silently moved the user off
    the PR build whenever they ran --self. Refuse with a clear message
    pointing at the acquisition script (the supported refresh path for
    PR installs) when the running CLI's IdentityChannel starts with
    'pr-'. An explicit --channel still opts out.

Tests:
- AddToPath_PrInstall_ReplacesExistingDogfoodPrLine
- AddToPath_PrInstall_WithNoPriorDogfoodLine_AppendsAsBefore
- AddToPath_NonPrInstall_AppendsAndDoesNotMatchDogfoodHeuristic
- UpdateCommand_SelfUpdate_WhenIdentityChannelIsPr_RefusesWithAcquisitionScriptHint
- UpdateCommand_SelfUpdate_WhenIdentityChannelIsPrAndExplicitChannelGiven_AllowsDownload
- UpdateCommand_SelfUpdate_WhenIdentityChannelIsDaily_AllowsDownload

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mitchdenny mitchdenny changed the title DO NOT MERGE: Fix three regressions in aspire update DO NOT MERGE: Fix five regressions in aspire update May 18, 2026
Mitch Denny and others added 4 commits May 18, 2026 13:47
Per user feedback: 'aspire update --self' should remain a valid
ejection mechanism for PR builds. The hard-coded Stable/Daily/Staging
prompt already lets a PR-built CLI move to a real channel; refusing
with an acquisition-script hint was unhelpful gatekeeping.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The .NET starter and apphost templates went through DotNetTemplateFactory
without ever writing aspire.config.json#channel. The TypeScript starter
(CliTemplateFactory.TypeScriptStarterTemplate) and the empty-template /
init paths (ScaffoldingService) already mirrored the resolved channel
into the per-project config. The .NET path was an asymmetric gap.

Without a pin in the scaffolded project, `aspire update` skips its
local-config precedence step and falls through to either an interactive
prompt (when hives exist) or the Implicit/nuget.org channel — silently
moving a project scaffolded by a PR-built or daily CLI onto stable.

Mirror the TS pattern: when the resolved channel is Explicit (pr-<N>,
daily, staging, local), call AspireConfigFile.LoadOrCreate + Save with
the channel name. Implicit channels (stable/nuget.org) intentionally
stay unpinned so the user's ambient NuGet config governs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
aspire-py-starter and aspire-go-starter previously declined to persist
the resolved channel into aspire.config.json, on the rationale that
PrebuiltAppHostServer aggregates package sources from every registered
channel when no pin exists. That rationale only held for a daily CLI —
on a PR-built CLI the next 'aspire update' falls through to a daily
prompt or the Implicit/nuget.org channel because the local-config step
in the channel-resolution precedence finds nothing.

Mirror the TypeScript starter / .NET starter pattern: when NewCommand
resolves an Explicit channel, write it to aspire.config.json#channel
before the SDK generation step. Implicit channels (stable/nuget.org)
remain unpinned.

Extend NewCommandTemplateConfigPersistenceTests to cover GoStarter and
PythonStarter across all three pin scenarios (identity unregistered,
identity matches, --channel overrides), and drop the now-obsolete
NonChannelPinningStarter_NeverPersistsChannel tripwire.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PR installs land under <prefix>/dogfood/pr-<N>/bin, a per-PR path used
for session-scoped dogfooding. Writing it into ~/.zshrc / ~/.bashrc (Unix)
or HKCU\Environment (Windows) silently demoted a developer's daily/stable
install on every new shell until they hunted down the stale entry.

Treat PR installs as session-only: update the current session PATH and
print the activation hint so the user can opt into persistence manually,
but leave shell profiles and the Windows user PATH untouched. Non-PR
routes through these same scripts (release channels, --install-prefix
overrides) keep the previous persistent-PATH behavior.

The earlier dogfood/pr-* dedup pass in add_to_path / Update-PathEnvironment
is no longer needed and has been removed; the obsolete add_to_path PR
dedup tests are replaced with a single test pinning the simpler append
contract.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mitchdenny
Copy link
Copy Markdown
Member Author

/deployment-test

@github-actions
Copy link
Copy Markdown
Contributor

🚀 Deployment tests starting on PR #17192...

This will deploy to real Azure infrastructure. Results will be posted here when complete.

View workflow run

Repo convention is that committed comments describe the current contract
as seen against origin/main; they should not reference internal PR-N
identifiers or branch-evolution narrative. Rewrite the affected block
headers in UpdateCommandTests.cs (and any matching narrative in
UpdateCommand.cs / ProjectLocator.cs found by audit) to describe what
the code / test group guards, in terms of contract rather than history.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mitchdenny mitchdenny changed the title DO NOT MERGE: Fix five regressions in aspire update Fix regressions in aspire update May 18, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Deployment E2E Tests failed — 37 passed, 1 failed, 0 cancelled

View test results and recordings

View workflow run

Test Result Recording
Deployment.EndToEnd-AzureLogAnalyticsDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-TypeScriptVnetSqlServerInfraDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AzureKeyVaultDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-TypeScriptExpressDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-KubernetesGatewayTlsDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-KubernetesHelmChartDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AzureStorageDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-NspStorageKeyVaultDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AzureEventHubsDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AcaCustomRegistryDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AzureServiceBusDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-VnetKeyVaultInfraDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AksVnetInfraDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-VnetSqlServerInfraDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AksAzureKubernetesEnvironmentGatewayDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AksBlazorRedisDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AksAzureKubernetesEnvironmentCertManagerTypeScriptDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AcaExistingRegistryDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-VnetKeyVaultConnectivityDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AksStarterWithRedisHelmDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AcaDeploymentErrorOutputTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AksVnetWithAzureResourcesDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AksWithAzureResourcesDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AzureAppConfigDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AcaCompactNamingDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-VnetSqlServerConnectivityDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AksAzureKubernetesEnvironmentCertManagerDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AzureContainerRegistryDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AksWithHelmChartDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AksStarterDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-VnetStorageBlobConnectivityDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AksMultipleNodePoolsDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AcaManagedRedisDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AuthenticationTests ✅ Passed
Deployment.EndToEnd-VnetStorageBlobInfraDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AppServiceReactDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AcaStarterDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-FrontDoorDeploymentTests ❌ Failed ▶️ View Recording

@github-actions
Copy link
Copy Markdown
Contributor

CLI E2E Tests unknown — 86 passed, 0 failed, 1 unknown (commit d650017)

View all recordings
Status Test Recording
AddPackageInteractiveWhileAppHostRunningDetached ▶️ View recording
AddPackageWhileAppHostRunningDetached ▶️ View recording
AgentCommands_AllHelpOutputs_AreCorrect ▶️ View recording
AgentInitCommand_DefaultSelection_InstallsDefaultSkills ▶️ View recording
AgentInitCommand_MigratesDeprecatedConfig ▶️ View recording
AspireAddPackageVersionToDirectoryPackagesProps ▶️ View recording
AspireInitSingleFileAppHostRunsViaDotnetRunAppHost ▶️ View recording
AspireInitWithExistingAppHostDirRecreatesMissingNuGetConfigAndPreservesFiles ▶️ View recording
AspireInitWithSolutionFileGeneratesAppHostThatBuildsAgainstChannelHive ▶️ View recording
AspireUpdateRemovesAppHostPackageVersionFromDirectoryPackagesProps ▶️ View recording
AspireUpdateRemovesOrphanAppHostPackageVersionWhenSdkAlreadyCurrent ▶️ View recording
Banner_DisplayedOnFirstRun ▶️ View recording
Banner_DisplayedWithExplicitFlag ▶️ View recording
Banner_NotDisplayedWithNoLogoFlag ▶️ View recording
CertificatesClean_RemovesCertificates ▶️ View recording
CertificatesTrust_WithNoCert_CreatesAndTrustsCertificate ▶️ View recording
CertificatesTrust_WithUntrustedCert_TrustsCertificate ▶️ View recording
ConfigSetGet_CreatesNestedJsonFormat ▶️ View recording
CreateAndRunAspireStarterProject ▶️ View recording
CreateAndRunAspireStarterProjectWithBundle ▶️ View recording
CreateAndRunEmptyAppHostProject ▶️ View recording
CreateAndRunJavaEmptyAppHostProject ▶️ View recording
CreateAndRunJsReactProject ▶️ View recording
CreateAndRunPythonReactProject ▶️ View recording
CreateAndRunTypeScriptEmptyAppHostProject ▶️ View recording
CreateAndRunTypeScriptStarterProject ▶️ View recording
CreateJavaAppHostWithViteApp ▶️ View recording
CreateTypeScriptAppHostWithViteApp_UsesConfiguredToolchain ▶️ View recording
DashboardRunWithOtelTracesReturnsNoTraces ▶️ View recording
DeployK8sBasicApiService ▶️ View recording
DeployK8sWithExternalHelmChart ▶️ View recording
DeployK8sWithGarnet ▶️ View recording
DeployK8sWithMongoDB ▶️ View recording
DeployK8sWithMySql ▶️ View recording
DeployK8sWithPostgres ▶️ View recording
DeployK8sWithRabbitMQ ▶️ View recording
DeployK8sWithRedis ▶️ View recording
DeployK8sWithSqlServer ▶️ View recording
DeployK8sWithValkey ▶️ View recording
DeployTypeScriptAppToKubernetes ▶️ View recording
DescribeCommandResolvesReplicaNames ▶️ View recording
DescribeCommandShowsRunningResources ▶️ View recording
DetachFormatJsonProducesValidJson ▶️ View recording
DetachFormatJsonProducesValidJsonWhenRestartingExistingInstance ▶️ View recording
DoListStepsShowsPipelineSteps ▶️ View recording
DocsCommand_RendersInteractiveMarkdownFromLocalSource ▶️ View recording
DoctorCommand_DetectsDeprecatedAgentConfig ▶️ View recording
DoctorCommand_TypeScriptAppHostReportsMissingConfiguredToolchain ▶️ View recording
DoctorCommand_WithSslCertDir_ShowsTrusted ▶️ View recording
DoctorCommand_WithoutSslCertDir_ShowsPartiallyTrusted ▶️ View recording
GlobalMigration_HandlesCommentsAndTrailingCommas ▶️ View recording
GlobalMigration_HandlesMalformedLegacyJson ▶️ View recording
GlobalMigration_PreservesAllValueTypes ▶️ View recording
GlobalMigration_SkipsWhenNewConfigExists ▶️ View recording
GlobalSettings_MigratedFromLegacyFormat ▶️ View recording
InitTypeScriptAppHost_AugmentsExistingViteRepoAtRoot ▶️ View recording
InteractiveCSharpInitCreatesExpectedFiles ▶️ View recording
InvalidAppHostPathWithComments_IsHealedOnRun ▶️ View recording
LatestCliCanStartStableChannelAppHost ▶️ View recording
LatestCliCanStartStableChannelTypeScriptAppHost ▶️ View recording
LegacySettingsMigration_AdjustsRelativeAppHostPath ▶️ View recording
LogLevelTrace_ProducesTraceEntriesInCliLogFile ▶️ View recording
LogsCommandShowsResourceLogs ▶️ View recording
OtelLogsReturnsStructuredLogsFromStarterApp ▶️ View recording
OtelLogsReturnsStructuredLogsFromStarterAppIsolated ▶️ View recording
PsCommandListsRunningAppHost ▶️ View recording
PsFormatJsonOutputsOnlyJsonToStdout ▶️ View recording
PublishWithConfigureEnvFileUpdatesEnvOutput ▶️ View recording
PublishWithDockerComposeServiceCallbackSucceeds ▶️ View recording
PublishWithoutOutputPathUsesAppHostDirectoryDefault ▶️ View recording
ResourceCommand_FailedExecution_DisplaysAppHostLogPathAndLogContainsEntries ▶️ View recording
ResourceCommand_FailsWhenInteractionServiceIsRequired ▶️ View recording
ResourceCommand_SetAndDeleteParameterUpdatesDescribeOutput ▶️ View recording
RestoreGeneratesSdkFiles ▶️ View recording
RestoreGeneratesSdkFiles_WithConfiguredToolchain ▶️ View recording
RestoreRefreshesGeneratedSdkAfterAddingIntegration ▶️ View recording
RestoreSupportsConfigOnlyHelperPackageAndCrossPackageTypes ▶️ View recording
RunFromParentDirectory_UsesExistingConfigNearAppHost ▶️ View recording
SecretCrudOnDotNetAppHost ▶️ View recording
SecretCrudOnTypeScriptAppHost ▶️ View recording
StagingChannel_ConfigureAndVerifySettings_ThenSwitchChannels ▶️ View recording
StartAndWaitForTypeScriptSqlServerAppHostWithNativeAssets ▶️ View recording
StopAllAppHostsFromAppHostDirectory ▶️ View recording
StopNonInteractiveSingleAppHost ▶️ View recording
StopWithNoRunningAppHostExitsSuccessfully ▶️ View recording
UnAwaitedChainsCompileWithAutoResolvePromises ▶️ View recording
UpdateProjectChannelToStable_TypeScript_PicksUpStablePackages ▶️ View recording

📹 Recordings uploaded automatically from CI run #26022398534

@mitchdenny mitchdenny merged commit 3d0d7be into main May 18, 2026
587 of 591 checks passed
@microsoft-github-policy-service microsoft-github-policy-service Bot added this to the 13.4 milestone May 18, 2026
@aspire-repo-bot
Copy link
Copy Markdown
Contributor

✅ No documentation update needed.

Documentation changes were authored for aspire-update.mdx (two new sections: channel resolution order and AppHost resilience) but could not be submitted as a draft PR due to a workspace mapping issue: the safeoutputs create_pull_request tool could not locate the commits in _repos/aspire.dev. The signals that triggered this run were cli_command_file_changed (UpdateCommand.cs) and pr_body_has_cli_flag_mention (--channel resolution). A manual follow-up is needed to create the documentation PR on microsoft/aspire.dev targeting release/13.4.

nellshamrell pushed a commit to nellshamrell/aspire that referenced this pull request May 18, 2026
* Fix two regressions in `aspire update`

* Update no longer crashes when the AppHost's pinned Aspire.AppHost.Sdk
  version cannot be resolved (e.g. moving between PR builds after the
  hive was refreshed). UpdateCommand now calls the project locator in
  TrustConfiguredPath mode, which skips MSBuild validation of the
  configured AppHost path so ProjectUpdater can rewrite the SDK pin
  via its existing fallback parser.

* Update no longer defaults to the Implicit ("daily") channel when a
  PR-built CLI is run against an AppHost that has no per-project or
  global channel pin. UpdateCommand now consults
  ExecutionContext.IdentityChannel as a default before the prompt
  fallback, restoring the pre-microsoft#16820 behavior without re-introducing
  any global channel writes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Strip orphaned PR-hive sources from <packageSources> on update

When 'aspire update' merges a project's NuGet.config to a new channel, any
~/.aspire/hives/pr-<N>/packages entry that lives only in <packageSources>
(no corresponding <packageSourceMapping> element) used to survive the merge.
RemoveEmptyPackageSourceElements only cleans up <packageSource> mapping
entries that become empty during the merge, so a source that was never
mapped — or whose mapping was rewritten by an earlier merge — would linger
forever. As soon as the hive directory is replaced on disk (which now
happens routinely for refreshed PR builds), 'dotnet restore' fails with
NU1301: The local source '...' doesn't exist.

Add a final pass over <packageSources> that removes any safe-to-remove
source (the existing IsSourceSafeToRemove heuristic — paths under
.aspire/hives) that is not in sourcesInUse and not in the new channel's
RequiredSources.

Add two regression tests covering both shapes: original config with a
PR-hive source mapped (already cleaned by the empty-element pass; locked
down) and original config with the same PR-hive source listed but with no
mapping element (the actual failure mode).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix PR install PATH dedup and refuse PR-channel --self update

Two additional regressions that compounded the aspire update problems:

(D) get-aspire-cli-pr.{sh,ps1} appended a fresh PATH entry on every PR
    install because the install path embeds the PR number
    (/Users/midenn/.aspire/dogfood/pr-<N>/bin), so the literal-line dedup in
    add_to_path never matched any prior PR install. Detect any existing
    dogfood/pr-*/bin line and replace it in place when adding a new one;
    do the same for the Windows user PATH registry entry.

(E) aspire update --self prompts {Stable, Daily, Staging} and routes
    through _cliDownloader.DownloadLatestCliAsync. PR channels have no
    cliDownloadBaseUrl, so a PR-built CLI silently moved the user off
    the PR build whenever they ran --self. Refuse with a clear message
    pointing at the acquisition script (the supported refresh path for
    PR installs) when the running CLI's IdentityChannel starts with
    'pr-'. An explicit --channel still opts out.

Tests:
- AddToPath_PrInstall_ReplacesExistingDogfoodPrLine
- AddToPath_PrInstall_WithNoPriorDogfoodLine_AppendsAsBefore
- AddToPath_NonPrInstall_AppendsAndDoesNotMatchDogfoodHeuristic
- UpdateCommand_SelfUpdate_WhenIdentityChannelIsPr_RefusesWithAcquisitionScriptHint
- UpdateCommand_SelfUpdate_WhenIdentityChannelIsPrAndExplicitChannelGiven_AllowsDownload
- UpdateCommand_SelfUpdate_WhenIdentityChannelIsDaily_AllowsDownload

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Revert PR-channel refusal on aspire update --self

Per user feedback: 'aspire update --self' should remain a valid
ejection mechanism for PR builds. The hard-coded Stable/Daily/Staging
prompt already lets a PR-built CLI move to a real channel; refusing
with an acquisition-script hint was unhelpful gatekeeping.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Persist resolved channel from `aspire new` (.NET scaffold)

The .NET starter and apphost templates went through DotNetTemplateFactory
without ever writing aspire.config.json#channel. The TypeScript starter
(CliTemplateFactory.TypeScriptStarterTemplate) and the empty-template /
init paths (ScaffoldingService) already mirrored the resolved channel
into the per-project config. The .NET path was an asymmetric gap.

Without a pin in the scaffolded project, `aspire update` skips its
local-config precedence step and falls through to either an interactive
prompt (when hives exist) or the Implicit/nuget.org channel — silently
moving a project scaffolded by a PR-built or daily CLI onto stable.

Mirror the TS pattern: when the resolved channel is Explicit (pr-<N>,
daily, staging, local), call AspireConfigFile.LoadOrCreate + Save with
the channel name. Implicit channels (stable/nuget.org) intentionally
stay unpinned so the user's ambient NuGet config governs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Extend channel pin to Python and Go starters

aspire-py-starter and aspire-go-starter previously declined to persist
the resolved channel into aspire.config.json, on the rationale that
PrebuiltAppHostServer aggregates package sources from every registered
channel when no pin exists. That rationale only held for a daily CLI —
on a PR-built CLI the next 'aspire update' falls through to a daily
prompt or the Implicit/nuget.org channel because the local-config step
in the channel-resolution precedence finds nothing.

Mirror the TypeScript starter / .NET starter pattern: when NewCommand
resolves an Explicit channel, write it to aspire.config.json#channel
before the SDK generation step. Implicit channels (stable/nuget.org)
remain unpinned.

Extend NewCommandTemplateConfigPersistenceTests to cover GoStarter and
PythonStarter across all three pin scenarios (identity unregistered,
identity matches, --channel overrides), and drop the now-obsolete
NonChannelPinningStarter_NeverPersistsChannel tripwire.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Skip persistent profile/PATH writes for PR installs

PR installs land under <prefix>/dogfood/pr-<N>/bin, a per-PR path used
for session-scoped dogfooding. Writing it into ~/.zshrc / ~/.bashrc (Unix)
or HKCU\Environment (Windows) silently demoted a developer's daily/stable
install on every new shell until they hunted down the stale entry.

Treat PR installs as session-only: update the current session PATH and
print the activation hint so the user can opt into persistence manually,
but leave shell profiles and the Windows user PATH untouched. Non-PR
routes through these same scripts (release channels, --install-prefix
overrides) keep the previous persistent-PATH behavior.

The earlier dogfood/pr-* dedup pass in add_to_path / Update-PathEnvironment
is no longer needed and has been removed; the obsolete add_to_path PR
dedup tests are replaced with a single test pinning the simpler append
contract.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Drop PR-N / date narrative from committed comments

Repo convention is that committed comments describe the current contract
as seen against origin/main; they should not reference internal PR-N
identifiers or branch-evolution narrative. Rewrite the affected block
headers in UpdateCommandTests.cs (and any matching narrative in
UpdateCommand.cs / ProjectLocator.cs found by audit) to describe what
the code / test group guards, in terms of contract rather than history.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Mitch Denny <midenn@orangecake.localdomain>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: mitchdenny <mitchdenny@users.noreply.github.com>
Co-authored-by: Mitch Denny <midenn@Mac.localdomain>
radical added a commit to radical/aspire that referenced this pull request May 19, 2026
PR and localhive acquisition scripts no longer touch shell profiles,
the persistent Windows user PATH, or global channel configuration. The
success hints now name the installed Aspire binary directly and only
offer session-scoped PATH activation where that can help.

Session-scoped dogfood installs were silently demoting a developer's
daily or stable install whenever installer state survived into future
shells. Follow the microsoft#17192 pattern by keeping these routes local to the
current invocation and letting project configuration or CLI identity
resolve channels instead of installer-seeded global state.

Refs microsoft#17192

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
radical added a commit to radical/aspire that referenced this pull request May 19, 2026
PR install and localhive install are session-scoped dogfood routes. They
must not modify shell startup files or HKCU\\Environment, because a
persistent PATH entry for a PR or local build can shadow the user's daily
or stable CLI in every new shell.

Pin the script contract in tests: PR and localhive scripts must leave
persistent PATH state alone, name the direct binary path in activation
hints, and avoid global channel writes. The PR script assertions follow
the microsoft#17192 dry-run / WhatIf pattern and extend it for the stricter
session-only contract.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants