Skip to content

Add Radius TypeScript AppHost support#18759

Draft
mitchdenny wants to merge 1 commit into
mainfrom
mitchdenny-mitchdenny-radius-ats-e2e
Draft

Add Radius TypeScript AppHost support#18759
mitchdenny wants to merge 1 commit into
mainfrom
mitchdenny-mitchdenny-radius-ats-e2e

Conversation

@mitchdenny

Copy link
Copy Markdown
Member

Description

Radius can now be fully configured from a TypeScript AppHost. This projects the cloud-provider callbacks and the generated-infrastructure customization callback into ATS, allowing TypeScript applications to configure Azure or AWS credentials and customize the Radius Bicep model without exposing Azure Provisioning types.

The infrastructure callback exposes typed construct collections plus primitive adapters for environment, application, recipe pack, resource type, container, and legacy-resource customization. A TypeScript polyglot validation fixture exercises the complete projected surface, while the Radius CLI E2E test now publishes a TypeScript AppHost and verifies the customized Bicep output.

User-facing usage

C# AppHost:

var radius = builder.AddRadiusEnvironment("radius")
    .WithNamespace("radius-system")
    .WithAzureProvider(subscriptionId, resourceGroup, azure =>
        azure.WithWorkloadIdentity(tenantId, clientId))
    .ConfigureRadiusInfrastructure(options =>
    {
        options.Environments[0].EnvironmentName = "custom-radius-environment";
    });

TypeScript AppHost:

const radius = await builder.addRadiusEnvironment('radius');
await radius.withNamespace('radius-system');
await radius.withAzureProvider(subscriptionId, resourceGroup, async (azure) => {
    await azure.withWorkloadIdentity(tenantId, clientId);
});
await radius.configureRadiusInfrastructure(async (options) => {
    const environment = await (await options.environments()).get(0);
    await environment.withEnvironmentName('custom-radius-environment');
});

Validation includes:

  • Aspire.Hosting.Radius.Tests: 181 tests passed.
  • Radius TypeScript polyglot fixture restored and type-checked successfully.
  • Radius TypeScript CLI publish E2E passed against a locally built CLI archive.

Fixes # (issue)

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

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

Copilot-Session: 2bda71df-3380-48d1-b09b-5b1fe006f77b
Copilot AI review requested due to automatic review settings July 13, 2026 06:36
@github-actions github-actions Bot added the area-integrations Issues pertaining to Aspire Integrations packages label Jul 13, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🚀 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 -- 18759

Or

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

@github-actions

Copy link
Copy Markdown
Contributor

Tests selector (audit mode)

The full test matrix and all jobs still run in audit mode. The tests and jobs below are what selective CI would run under enforcement.

2 / 100 test projects · 4 jobs, from 25 changed files.

Selected test projects (2 / 100)

Aspire.Cli.EndToEnd.Tests, Aspire.Hosting.Radius.Tests

Selected jobs (4)

cli-starter, extension-e2e, polyglot, typescript-api-compat


How these were chosen — grouped by what changed

🔧 src/Aspire.Hosting.Radius/CloudProviders/IAwsRadiusProviderBuilder.cs (changed source)
1 directly: Aspire.Hosting.Radius.Tests

🔧 src/Aspire.Hosting.Radius/CloudProviders/IAzureRadiusProviderBuilder.cs (changed source)
1 directly: Aspire.Hosting.Radius.Tests

🔧 src/Aspire.Hosting.Radius/CloudProviders/RadiusCloudProviderExtensions.cs (changed source)
1 directly: Aspire.Hosting.Radius.Tests

🔧 src/Aspire.Hosting.Radius/Publishing/Constructs/ConnectionConstruct.cs (changed source)
1 directly: Aspire.Hosting.Radius.Tests

🔧 src/Aspire.Hosting.Radius/Publishing/Constructs/ContainerEnvVarConstruct.cs (changed source)
1 directly: Aspire.Hosting.Radius.Tests

🔧 src/Aspire.Hosting.Radius/Publishing/Constructs/ContainerPortConstruct.cs (changed source)
1 directly: Aspire.Hosting.Radius.Tests

🔧 src/Aspire.Hosting.Radius/Publishing/Constructs/LegacyApplicationConstruct.cs (changed source)
1 directly: Aspire.Hosting.Radius.Tests

🔧 src/Aspire.Hosting.Radius/Publishing/Constructs/LegacyApplicationEnvironmentConstruct.cs (changed source)
1 directly: Aspire.Hosting.Radius.Tests

🔧 src/Aspire.Hosting.Radius/Publishing/Constructs/LegacyRecipeEntryConstruct.cs (changed source)
1 directly: Aspire.Hosting.Radius.Tests

🔧 src/Aspire.Hosting.Radius/Publishing/Constructs/RadiusApplicationConstruct.cs (changed source)
1 directly: Aspire.Hosting.Radius.Tests

🔧 src/Aspire.Hosting.Radius/Publishing/Constructs/RadiusContainerConstruct.cs (changed source)
1 directly: Aspire.Hosting.Radius.Tests

🔧 src/Aspire.Hosting.Radius/Publishing/Constructs/RadiusEnvironmentConstruct.cs (changed source)
1 directly: Aspire.Hosting.Radius.Tests

🔧 src/Aspire.Hosting.Radius/Publishing/Constructs/RadiusRecipePackConstruct.cs (changed source)
1 directly: Aspire.Hosting.Radius.Tests

🔧 src/Aspire.Hosting.Radius/Publishing/Constructs/RadiusResourceTypeConstruct.cs (changed source)
1 directly: Aspire.Hosting.Radius.Tests

🔧 src/Aspire.Hosting.Radius/Publishing/Constructs/RecipeEntryConstruct.cs (changed source)
1 directly: Aspire.Hosting.Radius.Tests

🔧 src/Aspire.Hosting.Radius/Publishing/RadiusInfrastructureOptions.cs (changed source)
1 directly: Aspire.Hosting.Radius.Tests

🔧 src/Aspire.Hosting.Radius/Publishing/RadiusInfrastructurePolyglotExtensions.cs (changed source)
1 directly: Aspire.Hosting.Radius.Tests

🔧 src/Aspire.Hosting.Radius/RadiusExtensions.cs (changed source)
1 directly: Aspire.Hosting.Radius.Tests

🧪 tests/Aspire.Cli.EndToEnd.Tests/RadiusPublishTests.cs (changed test)
1 directly: Aspire.Cli.EndToEnd.Tests

🧪 tests/Aspire.Hosting.Radius.Tests/Publishing/ConfigureRadiusInfrastructureTests.cs (changed test)
1 directly: Aspire.Hosting.Radius.Tests

Job reasons

Job Triggered by
cli-starter selected test Aspire.Cli.EndToEnd.Tests
extension-e2e src/Aspire.Hosting.Radius/CloudProviders/IAwsRadiusProviderBuilder.cs, src/Aspire.Hosting.Radius/CloudProviders/IAzureRadiusProviderBuilder.cs, src/Aspire.Hosting.Radius/CloudProviders/RadiusCloudProviderExtensions.cs, src/Aspire.Hosting.Radius/Publishing/Constructs/ConnectionConstruct.cs, src/Aspire.Hosting.Radius/Publishing/Constructs/ContainerEnvVarConstruct.cs, src/Aspire.Hosting.Radius/Publishing/Constructs/ContainerPortConstruct.cs, src/Aspire.Hosting.Radius/Publishing/Constructs/LegacyApplicationConstruct.cs, src/Aspire.Hosting.Radius/Publishing/Constructs/LegacyApplicationEnvironmentConstruct.cs, src/Aspire.Hosting.Radius/Publishing/Constructs/LegacyRecipeEntryConstruct.cs, src/Aspire.Hosting.Radius/Publishing/Constructs/RadiusApplicationConstruct.cs, src/Aspire.Hosting.Radius/Publishing/Constructs/RadiusContainerConstruct.cs, src/Aspire.Hosting.Radius/Publishing/Constructs/RadiusEnvironmentConstruct.cs, src/Aspire.Hosting.Radius/Publishing/Constructs/RadiusRecipePackConstruct.cs, src/Aspire.Hosting.Radius/Publishing/Constructs/RadiusResourceTypeConstruct.cs, src/Aspire.Hosting.Radius/Publishing/Constructs/RecipeEntryConstruct.cs, src/Aspire.Hosting.Radius/Publishing/RadiusInfrastructureOptions.cs, src/Aspire.Hosting.Radius/Publishing/RadiusInfrastructurePolyglotExtensions.cs, src/Aspire.Hosting.Radius/RadiusExtensions.cs, tests/Aspire.Cli.EndToEnd.Tests/RadiusPublishTests.cs
• affected project Aspire.Hosting.Radius
polyglot tests/PolyglotAppHosts/Aspire.Hosting.Radius/TypeScript/apphost.mts, tests/PolyglotAppHosts/Aspire.Hosting.Radius/TypeScript/aspire.config.json, tests/PolyglotAppHosts/Aspire.Hosting.Radius/TypeScript/package-lock.json, tests/PolyglotAppHosts/Aspire.Hosting.Radius/TypeScript/package.json, tests/PolyglotAppHosts/Aspire.Hosting.Radius/TypeScript/tsconfig.json
typescript-api-compat affected project Aspire.Hosting.Radius

Selection computed for commit 9fc511c.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds TypeScript AppHost support for Radius provider configuration and generated Bicep customization.

Changes:

  • Exports Radius callbacks, provider builders, and construct handles through ATS.
  • Adds primitive adapters for modifying Radius infrastructure.
  • Adds TypeScript validation and CLI publish coverage.
Show a summary per file
File Description
tests/PolyglotAppHosts/Aspire.Hosting.Radius/TypeScript/tsconfig.json Configures TypeScript validation.
tests/PolyglotAppHosts/Aspire.Hosting.Radius/TypeScript/package.json Defines validation dependencies.
tests/PolyglotAppHosts/Aspire.Hosting.Radius/TypeScript/package-lock.json Locks validation dependencies.
tests/PolyglotAppHosts/Aspire.Hosting.Radius/TypeScript/aspire.config.json References the Radius integration.
tests/PolyglotAppHosts/Aspire.Hosting.Radius/TypeScript/apphost.mts Exercises the projected Radius API.
tests/Aspire.Hosting.Radius.Tests/Publishing/ConfigureRadiusInfrastructureTests.cs Tests infrastructure adapters.
tests/Aspire.Cli.EndToEnd.Tests/RadiusPublishTests.cs Publishes a customized TypeScript AppHost.
src/Aspire.Hosting.Radius/RadiusExtensions.cs Exports infrastructure configuration.
src/Aspire.Hosting.Radius/Publishing/RadiusInfrastructurePolyglotExtensions.cs Implements ATS customization adapters.
src/Aspire.Hosting.Radius/Publishing/RadiusInfrastructureOptions.cs Exposes infrastructure collections.
src/Aspire.Hosting.Radius/Publishing/Constructs/RecipeEntryConstruct.cs Exports recipe handles.
src/Aspire.Hosting.Radius/Publishing/Constructs/RadiusResourceTypeConstruct.cs Exports resource-type handles.
src/Aspire.Hosting.Radius/Publishing/Constructs/RadiusRecipePackConstruct.cs Exports recipe-pack handles.
src/Aspire.Hosting.Radius/Publishing/Constructs/RadiusEnvironmentConstruct.cs Exports environment handles.
src/Aspire.Hosting.Radius/Publishing/Constructs/RadiusContainerConstruct.cs Exports container handles.
src/Aspire.Hosting.Radius/Publishing/Constructs/RadiusApplicationConstruct.cs Exports application handles.
src/Aspire.Hosting.Radius/Publishing/Constructs/LegacyRecipeEntryConstruct.cs Exports legacy recipe handles.
src/Aspire.Hosting.Radius/Publishing/Constructs/LegacyApplicationEnvironmentConstruct.cs Exports legacy environment handles.
src/Aspire.Hosting.Radius/Publishing/Constructs/LegacyApplicationConstruct.cs Exports legacy application handles.
src/Aspire.Hosting.Radius/Publishing/Constructs/ContainerPortConstruct.cs Exports container-port handles.
src/Aspire.Hosting.Radius/Publishing/Constructs/ContainerEnvVarConstruct.cs Exports environment-variable handles.
src/Aspire.Hosting.Radius/Publishing/Constructs/ConnectionConstruct.cs Exports connection handles.
src/Aspire.Hosting.Radius/CloudProviders/RadiusCloudProviderExtensions.cs Exports provider callbacks.
src/Aspire.Hosting.Radius/CloudProviders/IAzureRadiusProviderBuilder.cs Exposes Azure credential methods.
src/Aspire.Hosting.Radius/CloudProviders/IAwsRadiusProviderBuilder.cs Exposes AWS credential methods.

Review details

Files not reviewed (1)
  • tests/PolyglotAppHosts/Aspire.Hosting.Radius/TypeScript/package-lock.json: Generated file
  • Files reviewed: 24/25 changed files
  • Comments generated: 3
  • Review effort level: Medium

/// shape across releases of <c>Aspire.Hosting.Radius</c>.
/// </remarks>
[Experimental("ASPIRERADIUS004", UrlFormat = "https://aka.ms/aspire/diagnostics/{0}")]
[AspireExport(ExposeProperties = true)]
{
container.Connections[name] = new ConnectionConstruct
{
Source = new IdentifierExpression($"{sourceResourceIdentifier}.id"),
RadiusTestHelper.AttachDeploymentTargets(radiusEnv, model);
var bicep = new RadiusBicepPublishingContext(radiusEnv).GenerateBicep(model);

Assert.Contains("Custom.Resources/widgets@2025-01-01-preview", bicep);
@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

@mitchdenny mitchdenny left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

TypeScript API review findings — see the inline comments for details.

/// <param name="environmentIdentifier">The Bicep identifier of the environment.</param>
/// <returns>The same construct for chaining.</returns>
[AspireExport]
internal static RadiusResourceTypeConstruct WithResourceScope(

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

cc @mitchdenny

⚠️ [Type Design] The generated TypeScript scope/reference APIs require callers to pass raw Bicep identifier strings ('app', 'radius', 'recipepack', 'legacy_env'). Those identifiers are implementation details, are not discoverable from the TypeScript handles, and become stale if an identifier changes. Since the callback already exposes typed construct handles, can these APIs accept RadiusApplicationConstruct, RadiusEnvironmentConstruct, RadiusRecipePackConstruct, and RadiusResourceTypeConstruct directly and derive the identifier internally? The same concern applies to WithEnvironmentRecipePack, WithApplicationEnvironment, WithContainerConnection, WithContainerScope, and WithLegacyApplicationEnvironment.

Ref: Type Design Guidelines

/// <param name="value">The recipe parameter value.</param>
/// <returns>The same construct for chaining.</returns>
[AspireExport]
internal static RadiusResourceTypeConstruct WithStringRecipeParameter(

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

cc @mitchdenny

⚠️ [Consistency] The underlying RecipeParameters collection is BicepDictionary<object>, but the TypeScript projection only permits string values. Radius recipes commonly need numeric, boolean, and structured parameters, so this does not preserve the practical breadth of the C# escape hatch. Can we project the supported JSON-like value shapes, or at least add distinct string/number/boolean adapters?

Ref: Member Design Guidelines

/// <param name="name">The deployed environment name.</param>
/// <returns>The same construct for chaining.</returns>
[AspireExport]
internal static RadiusEnvironmentConstruct WithEnvironmentName(

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

cc @mitchdenny

⚠️ [Naming] These names produce redundant TypeScript such as environment.withEnvironmentName(...), application.withApplicationName(...), and resource.withResourceName(...). The receiver already provides the type context. Can we use unique capability IDs with MethodName overrides to expose withName, withEnvironment, withProvider, and similar receiver-relative names, following the withImage pattern already used below?

Ref: Naming Guidelines

/// <param name="kubernetesNamespace">The Kubernetes namespace.</param>
/// <returns>The same construct for chaining.</returns>
[AspireExport]
internal static LegacyApplicationEnvironmentConstruct WithLegacyEnvironment(

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

cc @mitchdenny

⚠️ [Surface Completeness] The legacy environment projection combines name and namespace into one operation and does not project ComputeKind, Recipes, or RecipeConfig. A TypeScript callback therefore cannot express the same legacy customization as the C# callback even though legacyEnvironments() is publicly exposed. Can we either complete this adapter surface or avoid exposing the legacy collections until they can be customized coherently?

Ref: Designing for Extensibility

@mitchdenny

Copy link
Copy Markdown
Member Author

TypeScript API Review Summary

0 errors, 4 warnings, 0 info across 1 file.

The standard api/*.cs diff is empty; this review evaluated the generated TypeScript API surface introduced by the ATS exports.

Top areas of concern:

  • Raw Bicep identifiers instead of typed construct handles
  • String-only recipe parameters
  • Redundant receiver-qualified method names
  • Incomplete legacy infrastructure customization

Each finding is posted as a separate inline comment for discussion.

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

Labels

area-integrations Issues pertaining to Aspire Integrations packages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants