Skip to content

Expose command state snapshots to polyglot apphosts#17328

Merged
sebastienros merged 11 commits into
mainfrom
sebastienros/sebros-fix-typescript-command-state-cont
May 21, 2026
Merged

Expose command state snapshots to polyglot apphosts#17328
sebastienros merged 11 commits into
mainfrom
sebastienros/sebros-fix-typescript-command-state-cont

Conversation

@sebastienros
Copy link
Copy Markdown
Contributor

@sebastienros sebastienros commented May 20, 2026

Description

Polyglot AppHost command updateState callbacks need access to the resource's latest snapshot so commands can be enabled or disabled based on health and lifecycle state. Before this change, TypeScript and the other generated SDKs did not expose that data on UpdateCommandStateContext, which made health-gated command state callbacks impossible to write in polyglot AppHosts.

This adds a curated ATS resource snapshot projection for command state callbacks and exposes it as resourceSnapshot() in the generated SDKs. The projection includes health and lifecycle state data without exporting the raw .NET CustomResourceSnapshot, and the polyglot validation apphosts now cover both health-gated command state and resource command execution.

User-facing usage

TypeScript AppHosts can now gate command state directly from the resource snapshot:

await container.withCommand("noop", "Noop", async () => {
    return { success: true };
}, {
    commandOptions: {
        updateState: async (ctx) => {
            const snapshot = await ctx.resourceSnapshot();

            return snapshot.healthStatus === HealthStatus.Healthy
                ? ResourceCommandState.Enabled
                : ResourceCommandState.Disabled;
        },
    },
});

The same API shape is validated in the Python, Java, and Go polyglot apphost samples.

Fixes #16769

Validation performed:

  • Focused code generation snapshot tests for TypeScript, Python, Java, Go, and Rust
  • aspire restore for tests/PolyglotAppHosts/Aspire.Hosting in TypeScript, Python, Java, and Go
  • TypeScript npx tsc --noEmit --project tsconfig.json
  • Python syntax compilation for apphost.py and generated modules
  • Java javac --enable-preview --source 25 over generated module sources and AppHost.java
  • Go go build -buildvcs=false ./...

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

Expose a curated resource snapshot projection to polyglot command state callbacks and validate health-gated command state usage across the polyglot validation apphosts.

Co-authored-by: Copilot <[email protected]>
Copilot AI review requested due to automatic review settings May 20, 2026 23:07
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 20, 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 -- 17328

Or

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

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

This PR extends the polyglot AppHost SDK surface so CommandOptions.UpdateState callbacks can access a curated resource snapshot (health + lifecycle) via UpdateCommandStateContext.resourceSnapshot(), while keeping the raw .NET CustomResourceSnapshot unchanged for C# consumers.

Changes:

  • Added an ATS-exported resourceSnapshot() capability on UpdateCommandStateContext backed by a new curated UpdateCommandStateResourceSnapshot DTO.
  • Updated Python/Java/Go transports to recursively marshal callback-valued DTO properties so nested CommandOptions.UpdateState callbacks register correctly.
  • Refreshed codegen/API baselines and updated polyglot AppHost samples to demonstrate health-gated command state.

Reviewed changes

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

Show a summary per file
File Description
tests/PolyglotAppHosts/Aspire.Hosting/TypeScript/apphost.ts Adds a health-gated updateState command example using ctx.resourceSnapshot().
tests/PolyglotAppHosts/Aspire.Hosting/Python/apphost.py Adds a Python UpdateState command example using ctx.resource_snapshot.
tests/PolyglotAppHosts/Aspire.Hosting/Java/AppHost.java Adds a Java UpdateState example using ctx.resourceSnapshot().
tests/PolyglotAppHosts/Aspire.Hosting/Go/apphost.go Adds a Go UpdateState example using ctx.ResourceSnapshot().
tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.ts Updates TS generated snapshot with HealthStatus, snapshot DTO, and resourceSnapshot() API.
tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/AtsTypeScriptCodeGeneratorTests.cs Minor test refactor (stores aspire.ts content in a local variable).
tests/Aspire.Hosting.CodeGeneration.Rust.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.rs Updates Rust generated snapshot with HealthStatus, snapshot DTO, and resource_snapshot().
tests/Aspire.Hosting.CodeGeneration.Python.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.py Updates Python generated snapshot with HealthStatus, snapshot DTO, resource_snapshot, and arg marshalling.
tests/Aspire.Hosting.CodeGeneration.Java.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.java Updates Java generated snapshot with HealthStatus, snapshot DTO, transport marshalling, and resourceSnapshot().
tests/Aspire.Hosting.CodeGeneration.Go.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.go Updates Go generated snapshot with HealthStatus, snapshot DTO, and ResourceSnapshot().
src/Aspire.Hosting/ApplicationModel/ResourceCommandAnnotation.cs Adds the curated snapshot DTO and exports resourceSnapshot() while ignoring raw CustomResourceSnapshot for polyglot.
src/Aspire.Hosting/api/Aspire.Hosting.Capabilities.txt Updates capabilities baseline for the new snapshot DTO + HealthStatus enum.
src/Aspire.Hosting/api/Aspire.Hosting.ats.txt Updates ATS baseline for the new resourceSnapshot() capability + snapshot DTO.
src/Aspire.Hosting.CodeGeneration.Python/PythonModuleBuilder.cs Updates Python transport template to recursively marshal callback-valued args.
src/Aspire.Hosting.CodeGeneration.Java/Resources/Transport.java Updates Java transport template to recursively marshal callback-valued args.
src/Aspire.Hosting.CodeGeneration.Go/Resources/transport.go Updates Go transport template to recursively marshal callback-valued args.

Comment thread tests/PolyglotAppHosts/Aspire.Hosting/Java/AppHost.java
Comment thread src/Aspire.Hosting/api/Aspire.Hosting.Capabilities.txt Outdated
@sebastienros sebastienros marked this pull request as draft May 20, 2026 23:42
@sebastienros sebastienros marked this pull request as ready for review May 20, 2026 23:56
@sebastienros sebastienros marked this pull request as draft May 20, 2026 23:56
@github-actions
Copy link
Copy Markdown
Contributor

Re-running the failed jobs in the CI workflow for this pull request because 1 job was identified as retry-safe transient failures in the CI run attempt.
GitHub was asked to rerun all failed jobs for that attempt, and the rerun is being tracked in the rerun attempt.
The job links below point to the failed attempt jobs that matched the retry-safe transient failure rules.

sebastienros and others added 2 commits May 20, 2026 17:16
Materialize generated Java DTO capability returns from transport maps and mark nullable resource snapshot fields in the capability baseline.

Co-authored-by: Copilot <[email protected]>
Ignore IServiceProvider on resource command callback contexts and update the generated ATS baselines/snapshots to remove those polyglot capabilities.

Co-authored-by: Copilot <[email protected]>
@sebastienros sebastienros marked this pull request as ready for review May 21, 2026 01:06
sebastienros and others added 8 commits May 20, 2026 18:08
…ities

The Python AtsGeneratedAspire snapshot drifted when the polyglot transport
marshalling change introduced the _marshal_transport_value helper but only the
two-pass snapshot was refreshed. Refresh the ATS snapshot so the Python
codegen tests pass on Linux and Windows runners.

Also suppress the capability-removed TypeScript API compat diagnostics for
ExecuteCommandContext.serviceProvider and UpdateCommandStateContext.serviceProvider.
Both were intentionally hidden from ATS because polyglot apphosts cannot
consume an IServiceProvider handle and the new resourceSnapshot projection
covers the only useful use case (gating UpdateState callbacks on health).

Co-authored-by: Copilot <[email protected]>
@github-actions
Copy link
Copy Markdown
Contributor

CLI E2E Tests unknown — 95 passed, 0 failed, 5 unknown (commit b0e8a1a)

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
AgentMcpListStructuredLogsFromStarterAppCore ▶️ View recording
AllPublishMethodsBuildDockerImages ▶️ 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
DashboardRunWithAgentMcpCore ▶️ View recording
DashboardRunWithOtelTracesReturnsNoTracesCore ▶️ 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
GeneratedAspireDevScript_StartsWatchMode_WithConfiguredToolchain ▶️ 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
JavaScriptHostingApisRunFromTypeScriptAppHost ▶️ 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
PublishJavaScriptPatternsGeneratesExpectedDockerComposeArtifacts ▶️ 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 #26239952636

@sebastienros sebastienros merged commit 5c265a0 into main May 21, 2026
604 of 607 checks passed
@sebastienros sebastienros deleted the sebastienros/sebros-fix-typescript-command-state-cont branch May 21, 2026 21:04
@microsoft-github-policy-service microsoft-github-policy-service Bot added this to the 13.4 milestone May 21, 2026
aspire-repo-bot Bot added a commit to microsoft/aspire.dev that referenced this pull request May 21, 2026
… support

TypeScript AppHosts can now gate command state using ctx.resourceSnapshot()
on UpdateCommandStateContext, exposed via the new UpdateCommandStateResourceSnapshot
DTO added in microsoft/aspire#17328. Remove the outdated caution note that said
TypeScript had no resourceSnapshot equivalent, and replace it with a working
example that mirrors the C# health-gated command pattern.

Also update the Japanese (ja) localization of the same page.

Co-authored-by: Copilot <[email protected]>
@aspire-repo-bot
Copy link
Copy Markdown
Contributor

Pull request created: #1042

Generated by PR Documentation Check

@aspire-repo-bot
Copy link
Copy Markdown
Contributor

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

Updated src/frontend/src/content/docs/fundamentals/custom-resource-commands.mdx to remove the outdated caution note stating that TypeScript's UpdateCommandStateContext did not expose resourceSnapshot, and replaced it with a working example that calls ctx.resourceSnapshot() to gate a command on HealthStatus.Healthy — matching the C# pattern. Also updated the Japanese localization (ja/fundamentals/custom-resource-commands.mdx) to match. Both HealthStatus and ResourceCommandState are now documented as exported from the generated SDK.

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

Development

Successfully merging this pull request may close these issues.

TypeScript AppHost: UpdateCommandStateContext is missing ResourceSnapshot (no health-gated commands)

3 participants