Skip to content

Fix guest AppHost update with older CLI#17100

Merged
sebastienros merged 3 commits into
mainfrom
sebros/fix-typescript-apphost-update
May 19, 2026
Merged

Fix guest AppHost update with older CLI#17100
sebastienros merged 3 commits into
mainfrom
sebros/fix-typescript-apphost-update

Conversation

@sebastienros
Copy link
Copy Markdown
Contributor

Description

aspire update could advance aspire.config.json for guest AppHost projects before SDK regeneration completed. With older CLIs updating TypeScript AppHosts to a newer SDK, regeneration could fail because the bundled CLI lacked the matching code generator, leaving the project broken.

This change preflights guest AppHost updates against the target channel SDK version and prompts for a CLI update before mutating the project when the target SDK is newer than the running CLI. It also stages guest config updates in memory and writes aspire.config.json only after SDK regeneration succeeds, while threading the requested channel into AppHost server preparation so regeneration uses the selected SDK before persistence.

Validated with the focused Aspire CLI UpdateCommandTests and GuestAppHostProjectTests run.

Fixes: #17077

Checklist

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
    • No
  • Did you add public API?
    • Yes
      • If yes, did you have an API Review for it?
        • Yes
        • No
      • Did you add <remarks /> and <code /> elements on your triple slash comments?
        • Yes
        • No
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
      • If yes, have you done a threat model and had a security review?
        • Yes
        • No
    • No

Preflight guest AppHost package updates when the target SDK is newer than the running CLI so users can update the CLI first. Defer saving aspire.config.json until guest SDK regeneration succeeds.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 14, 2026 22:04
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 #17077 where aspire update on a guest AppHost (e.g. TypeScript) project would mutate aspire.config.json to newer package versions before SDK regeneration ran, leaving the project in a broken state when the bundled CLI/code-generator lagged the new packages.

Changes:

  • GuestAppHostProject.UpdatePackagesAsync now keeps the updated config in memory, regenerates the SDK first, and only persists aspire.config.json after regeneration succeeds.
  • IAppHostServerProject.PrepareAsync (and DotNetBasedAppHostServerProject.CreateProjectFilesAsync / PrebuiltAppHostServer.PrepareAsync) accept an optional requestedChannel, so regeneration uses the channel matching the in-memory config instead of re-reading from disk.
  • UpdateCommand adds a TryUpdateCliBeforeGuestProjectUpdateAsync preflight that compares the target Aspire.Hosting version against the running CLI and prompts the user to update the CLI first (skipping the project update if they accept).

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/Aspire.Cli/Projects/GuestAppHostProject.cs Stage config in memory; save only after regen succeeds; thread config.Channel into PrepareAsync calls.
src/Aspire.Cli/Projects/IAppHostServerProject.cs Add optional requestedChannel parameter to PrepareAsync.
src/Aspire.Cli/Projects/PrebuiltAppHostServer.cs Honor caller-supplied requestedChannel, falling back to project resolution.
src/Aspire.Cli/Projects/DotNetBasedAppHostServerProject.cs Thread requestedChannel through CreateProjectFilesAsync / PrepareAsync.
src/Aspire.Cli/Commands/UpdateCommand.cs Preflight CLI-vs-target version check for guest projects with a prompt to update CLI first.
tests/Aspire.Cli.Tests/Commands/UpdateCommandTests.cs Three new tests covering accept/decline/no-download-url branches of the new preflight.
tests/Aspire.Cli.Tests/Projects/GuestAppHostProjectTests.cs Rewritten regression test asserts config is not advanced when regeneration fails.
tests/Aspire.Cli.Tests/Projects/AppHostServerSessionTests.cs Updated test double for new PrepareAsync signature.
tests/Aspire.Cli.Tests/TestServices/TestAppHostProjectFactory.cs Made LanguageId/DisplayName configurable and added UpdatePackagesAsyncCallback to support guest-language tests.

Comment thread src/Aspire.Cli/Commands/UpdateCommand.cs Outdated
Comment thread src/Aspire.Cli/Commands/UpdateCommand.cs
Comment thread src/Aspire.Cli/Commands/UpdateCommand.cs
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 14, 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 -- 17100

Or

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

Clarify the pre-update CLI prompt, explain that project update is skipped after accepting CLI update, and share guest AppHost SDK version resolution between the preflight and updater.

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

@maddymontaquila @nmontaquila15 would appreciate if you can confirm this build fixes your issue

@davidfowl
Copy link
Copy Markdown
Collaborator

davidfowl commented May 15, 2026

PR testing summary

Tested this PR with the repo-local Aspire PR container runner. I also checked the linked issue #17077 and added a closer variant of that repro to the scenario set.

Result: ? Verified

CLI version: 13.4.0-pr.17100.gdc11ddd2
Expected PR head: dc11ddd25efaa8bf0b30c389201e9752be3a2b86

Scenarios run:

  1. ? Dogfood install + version verification

    • Installed via the PR dogfood command.
    • Verified the installed CLI version contains dc11ddd.
  2. ? TypeScript starter creation

    • Created aspire-ts-starter from the PR hive.
    • Project files were generated successfully.
    • Note: the container image does not include npm, so the CLI reported the expected JavaScript tooling warning and skipped automatic npm install.
  3. ? Guest AppHost no-op update

    • Ran aspire update --apphost <apphost.ts> --non-interactive --yes on the generated guest AppHost.
    • Update reported the project was already up to date and left aspire.config.json unchanged.
  4. ? Downgraded guest AppHost update

    • Manually downgraded aspire.config.json from 13.4.0-pr.17100.gdc11ddd2 to 13.3.0.
    • Ran aspire update --apphost <apphost.ts> --non-interactive --yes.
    • Update advanced both the SDK version and Aspire.Hosting.JavaScript package back to 13.4.0-pr.17100.gdc11ddd2 and completed successfully.
  5. ? Issue aspire update for TypeScript AppHost fails with "No code generator found for language: TypeScript" when CLI is older than target package version; leaves project in broken state #17077 variant: TypeScript guest AppHost with MongoDB package

    • Based on the linked issue repro: TypeScript AppHost update where SDK, Aspire.Hosting.JavaScript, and Aspire.Hosting.MongoDB are bumped together.
    • Started with aspire.config.json pinned to 13.3.0 for SDK, Aspire.Hosting.JavaScript, and Aspire.Hosting.MongoDB.
    • Ran aspire update --apphost <apphost.ts> --non-interactive --yes against the PR hive.
    • Update advanced all three entries to 13.4.0-pr.17100.gdc11ddd2, regenerated SDK code, and completed successfully.

No PR-blocking issues found in these scenarios.

@IEvangelist
Copy link
Copy Markdown
Member

Took this one for a real dogfood spin — it captures the bug nicely.

CLI / install

PR build 13.4.0-pr.17100.gdc11ddd2 (matches head dc11ddd2).

Scenarios (TypeScript guest AppHost)

S1 — same-channel sanity (downgrade pr-17100stable):aspire.config.json moves cleanly to 13.3.3/stable, regen succeeds.

S2 — THE BUG (running CLI < target SDK): project pinned to 13.3.3/stable, run aspire update --apphost <proj> --channel daily -y --non-interactive. With the PR build I observed:

  • Daily SDK resolved to 13.4.0-preview.1.26269.1+e4f641e1 (newer than the running CLI).
  • Preflight prompt fired (auto-confirmed by -y).
  • CLI self-updated → Updated to version: 13.4.0-preview.1.26269.1.
  • Project update skipped. Update the Aspire CLI, then re-run aspire update.
  • aspire.config.json was NOT touched — still 13.3.3/stable. This is the transactional/in-memory staging fix doing exactly what it should. Without this PR the config would have been advanced to the newer SDK pin before regen even ran, leaving a broken project if regen failed on the old codegen.

S3 — second leg with the newly self-updated CLI: re-ran the same aspire update --channel daily -y. ✅ Config advanced cleanly to 13.4.0-preview.1.26269.1/daily, regen succeeded.

Code review

One finding (verified against pr-17100-head HEAD):

src/Aspire.Cli/Projects/GuestAppHostProject.csAddPackageAsync still has the pre-fix pattern. It calls SaveConfiguration(config, directory) before BuildAndGenerateSdkAsync, so aspire add <package> reproduces the exact data-corruption bug this PR fixes for aspire update — if regen fails (bad version, restore failure, transient network), aspire.config.json is left with a package entry the project can't actually consume. The PR correctly moves SaveConfiguration after a successful BuildAndGenerateSdkAsync in UpdatePackagesAsync (see the new code around line 1262); the same swap is needed in AddPackageAsync (~line 1129).

Repro is the same shape as S2 but driven by aspire add instead of aspire update. Would you mind extending the fix to that path before merge? It's a small move + a probably-matching test in GuestAppHostProjectTests.

Everything else I walked through looked correct:

  • TryUpdateCliBeforeGuestProjectUpdateAsync runs before project.UpdatePackagesAsync — right ordering.
  • requestedChannel plumbed through IAppHostServerProject.PrepareAsync so regen uses the target SDK, not the (still-old) on-disk config.
  • pendingConfigUpdate only persists on regenerateSuccess.

LGTM modulo the AddPackageAsync follow-through.

…ix-typescript-apphost-update

# Conflicts:
#	src/Aspire.Cli/Commands/UpdateCommand.cs
#	src/Aspire.Cli/Projects/DotNetBasedAppHostServerProject.cs
#	tests/Aspire.Cli.Tests/Projects/GuestAppHostProjectTests.cs
@github-actions
Copy link
Copy Markdown
Contributor

CLI E2E Tests unknown — 92 passed, 0 failed, 2 unknown (commit 54101d4)

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
RunPublishFailureScenarioAsync ▶️ View recording
RunReportsSyntaxErrorsForDotNetAppHost ▶️ View recording
RunReportsSyntaxErrorsForTypeScriptAppHost ▶️ View recording
SecretCrudOnDotNetAppHost ▶️ View recording
SecretCrudOnTypeScriptAppHost ▶️ View recording
StagingChannel_ConfigureAndVerifySettings_ThenSwitchChannels ▶️ View recording
StartAndWaitForTypeScriptSqlServerAppHostWithNativeAssets ▶️ View recording
StartReportsSyntaxErrorsForDotNetAppHost ▶️ View recording
StartReportsSyntaxErrorsForTypeScriptAppHost ▶️ View recording
StopAllAppHostsFromAppHostDirectory ▶️ View recording
StopJavaPolyglotAppHostUsingApphostDirectory ▶️ View recording
StopNonInteractiveSingleAppHost ▶️ View recording
StopTypeScriptPolyglotAppHostUsingApphostDirectory ▶️ View recording
StopWithNoRunningAppHostExitsSuccessfully ▶️ View recording
UnAwaitedChainsCompileWithAutoResolvePromises ▶️ View recording
UpdateProjectChannelToStable_TypeScript_PicksUpStablePackages ▶️ View recording

📹 Recordings uploaded automatically from CI run #26106919016

@sebastienros sebastienros merged commit f8e3cb8 into main May 19, 2026
591 of 601 checks passed
@sebastienros sebastienros deleted the sebros/fix-typescript-apphost-update branch May 19, 2026 17:19
@microsoft-github-policy-service microsoft-github-policy-service Bot added this to the 13.4 milestone May 19, 2026
aspire-repo-bot Bot added a commit to microsoft/aspire.dev that referenced this pull request May 19, 2026
When aspire update targets a non-C# (guest) AppHost and the selected SDK
is newer than the running CLI, the command now prompts the user to update
the CLI first and skips the project update until they do.

Documents changes from microsoft/aspire#17100.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@aspire-repo-bot
Copy link
Copy Markdown
Contributor

Pull request created: #1013

Generated by PR Documentation Check

@aspire-repo-bot
Copy link
Copy Markdown
Contributor

📝 Documentation has been drafted in microsoft/aspire.dev#1013 targeting release/13.4.

Updated src/frontend/src/content/docs/reference/cli/commands/aspire-update.mdx to document the new CLI version preflight check introduced for guest (non-C#) AppHost projects. When aspire update targets a TypeScript or other non-C# AppHost and the selected Aspire SDK is newer than the running CLI, users are now prompted to update the CLI first — the doc addition covers both user-visible prompts and guidance to re-run aspire update afterward.

  • src/frontend/src/content/docs/reference/cli/commands/aspire-update.mdx — new "Updating guest (non-C#) AppHost projects" section added

Note

This draft PR needs human review before merging.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

5 participants