Skip to content

Fix polyglot AppHost builder and callback generation#16691

Open
sebastienros wants to merge 4 commits intomainfrom
sebros/polyglot-apphosts
Open

Fix polyglot AppHost builder and callback generation#16691
sebastienros wants to merge 4 commits intomainfrom
sebros/polyglot-apphosts

Conversation

@sebastienros
Copy link
Copy Markdown
Contributor

@sebastienros sebastienros commented May 2, 2026

Summary

  • Fix polyglot AppHost builder defaults so Python and Go preserve the source AppHost path from ASPIRE_APPHOST_FILEPATH.
  • Fix Java callback transport handling for positional JSON-RPC callback params and generated DTO serialization.
  • Fix ATS callback marshaling to use declared delegate parameter types, including DTO writeback for mutable callback arguments.
  • Fix Go callback DTO decoding/writeback and preserve inferred dashboard names when CreateBuilder(nil) sends zero-value options.
  • Add focused regression coverage and updated generated snapshots.

eShop parity validation

The eShop polyglot AppHosts were validated from the external dotnet/eshop clone

Language AppHost main source file Dashboard screenshot
TypeScript src/eShop.AppHost.TypeScript/apphost.ts TypeScript eShop AppHost dashboard
Python src/eShop.AppHost.Python/apphost.py Python eShop AppHost dashboard
Java src/eShop.AppHost.Java/AppHost.java Java eShop AppHost dashboard
Go src/eShop.AppHost.Go/apphost.go Go eShop AppHost dashboard

AppHost source used for validation

src/eShop.AppHost.TypeScript/apphost.ts
// eShop Aspire TypeScript AppHost
// Translated from eShop.AppHost/Program.cs.

import { ContainerLifetime, QueryParameterMatchMode, createBuilder } from './.modules/aspire.js';

const builder = await createBuilder();

await builder.addDockerComposeEnvironment("env");

// Infrastructure
const redis = await builder.addRedis("redis");
const rabbitMq = await builder.addRabbitMQ("eventbus")
    .withLifetime(ContainerLifetime.Persistent);
const postgres = await builder.addPostgres("postgres")
    .withImage("ankane/pgvector", { tag: "latest" })
    .withLifetime(ContainerLifetime.Persistent);

const catalogDb = await postgres.addDatabase("catalogdb");
const identityDb = await postgres.addDatabase("identitydb");
const orderDb = await postgres.addDatabase("orderingdb");
const webhooksDb = await postgres.addDatabase("webhooksdb");

// For test use only.
// Looks for an environment variable that forces the use of HTTP for all the endpoints.
// We are doing this for ease of running the Playwright tests in CI.
function shouldUseHttpForEndpoints(): boolean {
    const envValue = process.env["ESHOP_USE_HTTP_ENDPOINTS"];
    return envValue === "1";
}

const launchProfileName = shouldUseHttpForEndpoints() ? "http" : "https";

// Services
const identityApi = await builder.addProject("identity-api", "../Identity.API/Identity.API.csproj", { launchProfileOrOptions: launchProfileName })
    .withExternalHttpEndpoints()
    .withReference(identityDb)
    .withEnvironment("ASPNETCORE_FORWARDEDHEADERS_ENABLED", "true");

const identityEndpoint = await identityApi.getEndpoint(launchProfileName);

const basketApi = await builder.addCSharpApp("basket-api", "../Basket.API/Basket.API.csproj")
    .withReference(redis)
    .withReference(rabbitMq).waitFor(rabbitMq)
    .withEnvironment("Identity__Url", identityEndpoint)
    .withEnvironment("ASPNETCORE_FORWARDEDHEADERS_ENABLED", "true");
await redis.withParentRelationship(basketApi);

const catalogApi = await builder.addCSharpApp("catalog-api", "../Catalog.API/Catalog.API.csproj")
    .withReference(rabbitMq).waitFor(rabbitMq)
    .withReference(catalogDb)
    .withEnvironment("ASPNETCORE_FORWARDEDHEADERS_ENABLED", "true");

const orderingApi = await builder.addCSharpApp("ordering-api", "../Ordering.API/Ordering.API.csproj")
    .withReference(rabbitMq).waitFor(rabbitMq)
    .withReference(orderDb).waitFor(orderDb)
    .withHttpHealthCheck({ path: "/health" })
    .withEnvironment("Identity__Url", identityEndpoint)
    .withEnvironment("ASPNETCORE_FORWARDEDHEADERS_ENABLED", "true");

await builder.addCSharpApp("order-processor", "../OrderProcessor/OrderProcessor.csproj")
    .withReference(rabbitMq).waitFor(rabbitMq)
    .withReference(orderDb)
    .waitFor(orderingApi)
    .withEnvironment("ASPNETCORE_FORWARDEDHEADERS_ENABLED", "true");

await builder.addCSharpApp("payment-processor", "../PaymentProcessor/PaymentProcessor.csproj")
    .withReference(rabbitMq).waitFor(rabbitMq)
    .withEnvironment("ASPNETCORE_FORWARDEDHEADERS_ENABLED", "true");

const webHooksApi = await builder.addCSharpApp("webhooks-api", "../Webhooks.API/Webhooks.API.csproj")
    .withReference(rabbitMq).waitFor(rabbitMq)
    .withReference(webhooksDb)
    .withEnvironment("Identity__Url", identityEndpoint)
    .withEnvironment("ASPNETCORE_FORWARDEDHEADERS_ENABLED", "true");

function apiVersion(values: string[]) {
    return [{ name: "api-version", values, mode: QueryParameterMatchMode.Exact }];
}

// Reverse proxies
await builder.addYarp("mobile-bff")
    .withExternalHttpEndpoints()
    .withConfiguration(async (yarp) => {
        const catalogCluster = await yarp.addClusterFromResource(catalogApi);

        await yarp.addRoute("/catalog-api/api/catalog/items", catalogCluster)
            .withMatchRouteQueryParameter(apiVersion(["1.0", "1", "2.0"]))
            .withTransformPathRemovePrefix("/catalog-api");

        await yarp.addRoute("/catalog-api/api/catalog/items/by", catalogCluster)
            .withMatchRouteQueryParameter(apiVersion(["1.0", "1", "2.0"]))
            .withTransformPathRemovePrefix("/catalog-api");

        await yarp.addRoute("/catalog-api/api/catalog/items/{id}", catalogCluster)
            .withMatchRouteQueryParameter(apiVersion(["1.0", "1", "2.0"]))
            .withTransformPathRemovePrefix("/catalog-api");

        await yarp.addRoute("/catalog-api/api/catalog/items/by/{name}", catalogCluster)
            .withMatchRouteQueryParameter(apiVersion(["1.0", "1"]))
            .withTransformPathRemovePrefix("/catalog-api");

        await yarp.addRoute("/catalog-api/api/catalog/items/withsemanticrelevance/{text}", catalogCluster)
            .withMatchRouteQueryParameter(apiVersion(["1.0", "1"]))
            .withTransformPathRemovePrefix("/catalog-api");

        await yarp.addRoute("/catalog-api/api/catalog/items/withsemanticrelevance", catalogCluster)
            .withMatchRouteQueryParameter(apiVersion(["2.0"]))
            .withTransformPathRemovePrefix("/catalog-api");

        await yarp.addRoute("/catalog-api/api/catalog/items/type/{typeId}/brand/{brandId?}", catalogCluster)
            .withMatchRouteQueryParameter(apiVersion(["1.0", "1"]))
            .withTransformPathRemovePrefix("/catalog-api");

        await yarp.addRoute("/catalog-api/api/catalog/items/type/all/brand/{brandId?}", catalogCluster)
            .withMatchRouteQueryParameter(apiVersion(["1.0", "1"]))
            .withTransformPathRemovePrefix("/catalog-api");

        await yarp.addRoute("/catalog-api/api/catalog/catalogTypes", catalogCluster)
            .withMatchRouteQueryParameter(apiVersion(["1.0", "1", "2.0"]))
            .withTransformPathRemovePrefix("/catalog-api");

        await yarp.addRoute("/catalog-api/api/catalog/catalogBrands", catalogCluster)
            .withMatchRouteQueryParameter(apiVersion(["1.0", "1", "2.0"]))
            .withTransformPathRemovePrefix("/catalog-api");

        await yarp.addRoute("/catalog-api/api/catalog/items/{id}/pic", catalogCluster)
            .withMatchRouteQueryParameter(apiVersion(["1.0", "1", "2.0"]))
            .withTransformPathRemovePrefix("/catalog-api");

        await yarp.addRoute("/api/catalog/{*any}", catalogCluster)
            .withMatchRouteQueryParameter(apiVersion(["1.0", "1", "2.0"]));

        const orderingEndpoint = await orderingApi.getEndpoint("http");
        await yarp.addRoute("/api/orders/{*any}", orderingEndpoint)
            .withMatchRouteQueryParameter(apiVersion(["1.0", "1"]));

        const identityHttpEndpoint = await identityApi.getEndpoint("http");
        await yarp.addRoute("/identity/{*any}", identityHttpEndpoint)
            .withTransformPathRemovePrefix("/identity");
    });

// Apps
const webhooksClient = await builder.addProject("webhooksclient", "../WebhookClient/WebhookClient.csproj", { launchProfileOrOptions: launchProfileName })
    .withReference(webHooksApi)
    .withEnvironment("IdentityUrl", identityEndpoint)
    .withEnvironment("ASPNETCORE_FORWARDEDHEADERS_ENABLED", "true");

let webAppBuilder = builder.addProject("webapp", "../WebApp/WebApp.csproj", { launchProfileOrOptions: launchProfileName })
    .withExternalHttpEndpoints()
    .withUrlForEndpoint("http", async (url) => { url.displayText = "Online Store (http)"; });

if (launchProfileName === "https") {
    webAppBuilder = webAppBuilder.withUrlForEndpoint("https", async (url) => { url.displayText = "Online Store (https)"; });
}

const webApp = await webAppBuilder
    .withReference(basketApi)
    .withReference(catalogApi)
    .withReference(orderingApi)
    .withReference(rabbitMq).waitFor(rabbitMq)
    .withEnvironment("IdentityUrl", identityEndpoint)
    .withEnvironment("ASPNETCORE_FORWARDEDHEADERS_ENABLED", "true");

const useOpenAI = false;
if (useOpenAI) {
    const openAI = await builder.addAzureOpenAI("openai");

    const chat = await openAI.addDeployment("chatModel", "gpt-4.1-mini", "2025-04-14")
        .withProperties(async (d) => {
            d.deploymentName.set("gpt-4.1-mini");
            d.skuName.set("GlobalStandard");
            d.skuCapacity.set(50);
        });
    const textEmbedding = await openAI.addDeployment("textEmbeddingModel", "text-embedding-3-small", "1")
        .withProperties(async (d) => {
            d.deploymentName.set("text-embedding-3-small");
            d.skuCapacity.set(20);
        });

    await catalogApi.withReference(textEmbedding);
    await webApp.withReference(chat);
}

const useOllama = false;
if (useOllama) {
    // CommunityToolkit Ollama integration is not available in the TypeScript SDK yet.
}

// Wire up the callback urls (self referencing)
const webAppEndpoint = await webApp.getEndpoint(launchProfileName);
const webhooksClientEndpoint = await webhooksClient.getEndpoint(launchProfileName);
await webApp.withEnvironment("CallBackUrl", webAppEndpoint);
await webhooksClient.withEnvironment("CallBackUrl", webhooksClientEndpoint);

// Identity has a reference to all of the apps for callback urls, this is a cyclic reference
const basketHttpEndpoint = await basketApi.getEndpoint("http");
const orderingHttpEndpoint = await orderingApi.getEndpoint("http");
const webHooksHttpEndpoint = await webHooksApi.getEndpoint("http");
await identityApi
    .withEnvironment("BasketApiClient", basketHttpEndpoint)
    .withEnvironment("OrderingApiClient", orderingHttpEndpoint)
    .withEnvironment("WebhooksApiClient", webHooksHttpEndpoint)
    .withEnvironment("WebhooksWebClient", webhooksClientEndpoint)
    .withEnvironment("WebAppClient", webAppEndpoint);

await builder.build().run();
src/eShop.AppHost.Python/apphost.py
# eShop Aspire Python AppHost
# Translated from the issue 16645 TypeScript AppHost.

import os

from aspire_app import create_builder


def should_use_http_for_endpoints() -> bool:
    return os.environ.get("ESHOP_USE_HTTP_ENDPOINTS") == "1"


with create_builder() as builder:
    builder.add_docker_compose_env("env")

    # Infrastructure
    redis = builder.add_redis("redis")
    rabbit_mq = builder.add_rabbit_m_q("eventbus")
    rabbit_mq.with_lifetime("Persistent")
    postgres = builder.add_postgres("postgres")
    postgres.with_image("ankane/pgvector", tag="latest")
    postgres.with_lifetime("Persistent")

    catalog_db = postgres.add_database("catalogdb")
    identity_db = postgres.add_database("identitydb")
    order_db = postgres.add_database("orderingdb")
    webhooks_db = postgres.add_database("webhooksdb")

    launch_profile_name = "http" if should_use_http_for_endpoints() else "https"

    # Services
    identity_api = builder.add_project(
        "identity-api",
        "../Identity.API/Identity.API.csproj",
        launch_profile_or_options=launch_profile_name)
    identity_api.with_external_http_endpoints()
    identity_api.with_reference(identity_db)
    identity_api.with_env("ASPNETCORE_FORWARDEDHEADERS_ENABLED", "true")

    identity_endpoint = identity_api.get_endpoint(launch_profile_name)

    basket_api = builder.add_c_sharp_app("basket-api", "../Basket.API/Basket.API.csproj")
    basket_api.with_reference(redis)
    basket_api.with_reference(rabbit_mq).wait_for(rabbit_mq)
    basket_api.with_env("Identity__Url", identity_endpoint)
    basket_api.with_env("ASPNETCORE_FORWARDEDHEADERS_ENABLED", "true")
    redis.with_parent_relationship(basket_api)

    catalog_api = builder.add_c_sharp_app("catalog-api", "../Catalog.API/Catalog.API.csproj")
    catalog_api.with_reference(rabbit_mq).wait_for(rabbit_mq)
    catalog_api.with_reference(catalog_db)
    catalog_api.with_env("ASPNETCORE_FORWARDEDHEADERS_ENABLED", "true")

    ordering_api = builder.add_c_sharp_app("ordering-api", "../Ordering.API/Ordering.API.csproj")
    ordering_api.with_reference(rabbit_mq).wait_for(rabbit_mq)
    ordering_api.with_reference(order_db).wait_for(order_db)
    ordering_api.with_http_health_check(path="/health")
    ordering_api.with_env("Identity__Url", identity_endpoint)
    ordering_api.with_env("ASPNETCORE_FORWARDEDHEADERS_ENABLED", "true")

    order_processor = builder.add_c_sharp_app("order-processor", "../OrderProcessor/OrderProcessor.csproj")
    order_processor.with_reference(rabbit_mq).wait_for(rabbit_mq)
    order_processor.with_reference(order_db)
    order_processor.wait_for(ordering_api)
    order_processor.with_env("ASPNETCORE_FORWARDEDHEADERS_ENABLED", "true")

    payment_processor = builder.add_c_sharp_app("payment-processor", "../PaymentProcessor/PaymentProcessor.csproj")
    payment_processor.with_reference(rabbit_mq).wait_for(rabbit_mq)
    payment_processor.with_env("ASPNETCORE_FORWARDEDHEADERS_ENABLED", "true")

    webhooks_api = builder.add_c_sharp_app("webhooks-api", "../Webhooks.API/Webhooks.API.csproj")
    webhooks_api.with_reference(rabbit_mq).wait_for(rabbit_mq)
    webhooks_api.with_reference(webhooks_db)
    webhooks_api.with_env("Identity__Url", identity_endpoint)
    webhooks_api.with_env("ASPNETCORE_FORWARDEDHEADERS_ENABLED", "true")

    def api_version(*values: str):
        return [{
            "Name": "api-version",
            "Values": list(values),
            "Mode": "Exact",
        }]

    def configure_mobile_bff(yarp):
        catalog_cluster = yarp.add_cluster_from_resource(catalog_api)

        yarp.add_route("/catalog-api/api/catalog/items", catalog_cluster) \
            .with_match_route_query_parameter(api_version("1.0", "1", "2.0")) \
            .with_transform_path_remove_prefix("/catalog-api")

        yarp.add_route("/catalog-api/api/catalog/items/by", catalog_cluster) \
            .with_match_route_query_parameter(api_version("1.0", "1", "2.0")) \
            .with_transform_path_remove_prefix("/catalog-api")

        yarp.add_route("/catalog-api/api/catalog/items/{id}", catalog_cluster) \
            .with_match_route_query_parameter(api_version("1.0", "1", "2.0")) \
            .with_transform_path_remove_prefix("/catalog-api")

        yarp.add_route("/catalog-api/api/catalog/items/by/{name}", catalog_cluster) \
            .with_match_route_query_parameter(api_version("1.0", "1")) \
            .with_transform_path_remove_prefix("/catalog-api")

        yarp.add_route("/catalog-api/api/catalog/items/withsemanticrelevance/{text}", catalog_cluster) \
            .with_match_route_query_parameter(api_version("1.0", "1")) \
            .with_transform_path_remove_prefix("/catalog-api")

        yarp.add_route("/catalog-api/api/catalog/items/withsemanticrelevance", catalog_cluster) \
            .with_match_route_query_parameter(api_version("2.0")) \
            .with_transform_path_remove_prefix("/catalog-api")

        yarp.add_route("/catalog-api/api/catalog/items/type/{typeId}/brand/{brandId?}", catalog_cluster) \
            .with_match_route_query_parameter(api_version("1.0", "1")) \
            .with_transform_path_remove_prefix("/catalog-api")

        yarp.add_route("/catalog-api/api/catalog/items/type/all/brand/{brandId?}", catalog_cluster) \
            .with_match_route_query_parameter(api_version("1.0", "1")) \
            .with_transform_path_remove_prefix("/catalog-api")

        yarp.add_route("/catalog-api/api/catalog/catalogTypes", catalog_cluster) \
            .with_match_route_query_parameter(api_version("1.0", "1", "2.0")) \
            .with_transform_path_remove_prefix("/catalog-api")

        yarp.add_route("/catalog-api/api/catalog/catalogBrands", catalog_cluster) \
            .with_match_route_query_parameter(api_version("1.0", "1", "2.0")) \
            .with_transform_path_remove_prefix("/catalog-api")

        yarp.add_route("/catalog-api/api/catalog/items/{id}/pic", catalog_cluster) \
            .with_match_route_query_parameter(api_version("1.0", "1", "2.0")) \
            .with_transform_path_remove_prefix("/catalog-api")

        yarp.add_route("/api/catalog/{*any}", catalog_cluster) \
            .with_match_route_query_parameter(api_version("1.0", "1", "2.0"))

        ordering_endpoint = ordering_api.get_endpoint("http")
        yarp.add_route("/api/orders/{*any}", ordering_endpoint) \
            .with_match_route_query_parameter(api_version("1.0", "1"))

        identity_http_endpoint = identity_api.get_endpoint("http")
        yarp.add_route("/identity/{*any}", identity_http_endpoint) \
            .with_transform_path_remove_prefix("/identity")

    # Reverse proxies
    builder.add_yarp("mobile-bff") \
        .with_external_http_endpoints() \
        .with_config(configure_mobile_bff)

    # Apps
    webhooks_client = builder.add_project(
        "webhooksclient",
        "../WebhookClient/WebhookClient.csproj",
        launch_profile_or_options=launch_profile_name)
    webhooks_client.with_reference(webhooks_api)
    webhooks_client.with_env("IdentityUrl", identity_endpoint)
    webhooks_client.with_env("ASPNETCORE_FORWARDEDHEADERS_ENABLED", "true")

    web_app = builder.add_project(
        "webapp",
        "../WebApp/WebApp.csproj",
        launch_profile_or_options=launch_profile_name)

    def set_online_store_http(url):
        url["DisplayText"] = "Online Store (http)"

    def set_online_store_https(url):
        url["DisplayText"] = "Online Store (https)"

    web_app.with_external_http_endpoints()
    web_app.with_url_for_endpoint("http", set_online_store_http)
    if launch_profile_name == "https":
        web_app.with_url_for_endpoint("https", set_online_store_https)

    web_app.with_reference(basket_api)
    web_app.with_reference(catalog_api)
    web_app.with_reference(ordering_api)
    web_app.with_reference(rabbit_mq).wait_for(rabbit_mq)
    web_app.with_env("IdentityUrl", identity_endpoint)
    web_app.with_env("ASPNETCORE_FORWARDEDHEADERS_ENABLED", "true")

    use_openai = False
    if use_openai:
        openai = builder.add_azure_open_a_i("openai")

        def configure_chat_deployment(deployment):
            deployment.deployment_name = "gpt-4.1-mini"
            deployment.sku_name = "GlobalStandard"
            deployment.sku_capacity = 50

        def configure_embedding_deployment(deployment):
            deployment.deployment_name = "text-embedding-3-small"
            deployment.sku_capacity = 20

        chat = openai.add_deployment("chatModel", "gpt-4.1-mini", "2025-04-14")
        chat.with_properties(configure_chat_deployment)
        text_embedding = openai.add_deployment("textEmbeddingModel", "text-embedding-3-small", "1")
        text_embedding.with_properties(configure_embedding_deployment)

        catalog_api.with_reference(text_embedding)
        web_app.with_reference(chat)

    use_ollama = False
    if use_ollama:
        # CommunityToolkit Ollama integration is not available in the Python SDK yet.
        pass

    # Wire up the callback urls (self referencing)
    web_app_endpoint = web_app.get_endpoint(launch_profile_name)
    webhooks_client_endpoint = webhooks_client.get_endpoint(launch_profile_name)
    web_app.with_env("CallBackUrl", web_app_endpoint)
    webhooks_client.with_env("CallBackUrl", webhooks_client_endpoint)

    # Identity has a reference to all of the apps for callback urls, this is a cyclic reference.
    basket_http_endpoint = basket_api.get_endpoint("http")
    ordering_http_endpoint = ordering_api.get_endpoint("http")
    webhooks_http_endpoint = webhooks_api.get_endpoint("http")
    identity_api.with_env("BasketApiClient", basket_http_endpoint)
    identity_api.with_env("OrderingApiClient", ordering_http_endpoint)
    identity_api.with_env("WebhooksApiClient", webhooks_http_endpoint)
    identity_api.with_env("WebhooksWebClient", webhooks_client_endpoint)
    identity_api.with_env("WebAppClient", web_app_endpoint)

    builder.run()
src/eShop.AppHost.Java/AppHost.java
import aspire.*;

void main() throws Exception {
    var builder = DistributedApplication.CreateBuilder();

    builder.addDockerComposeEnvironment("env");

    // Infrastructure
    var redis = builder.addRedis("redis");
    var rabbitMq = builder.addRabbitMQ("eventbus")
        .withLifetime(ContainerLifetime.PERSISTENT);
    var postgres = builder.addPostgres("postgres")
        .withImage("ankane/pgvector", "latest")
        .withLifetime(ContainerLifetime.PERSISTENT);

    var catalogDb = postgres.addDatabase("catalogdb");
    var identityDb = postgres.addDatabase("identitydb");
    var orderDb = postgres.addDatabase("orderingdb");
    var webhooksDb = postgres.addDatabase("webhooksdb");

    var launchProfileName = shouldUseHttpForEndpoints() ? "http" : "https";

    // Services
    var identityApi = builder.addProject("identity-api", "../Identity.API/Identity.API.csproj", launchProfileName)
        .withExternalHttpEndpoints()
        .withReference(identityDb, null)
        .withEnvironment("ASPNETCORE_FORWARDEDHEADERS_ENABLED", "true");

    var identityEndpoint = identityApi.getEndpoint(launchProfileName);

    var basketApi = builder.addCSharpApp("basket-api", "../Basket.API/Basket.API.csproj")
        .withReference(redis, null)
        .withReference(rabbitMq, null).waitFor(rabbitMq)
        .withEnvironment("Identity__Url", identityEndpoint)
        .withEnvironment("ASPNETCORE_FORWARDEDHEADERS_ENABLED", "true");
    redis.withParentRelationship(basketApi);

    var catalogApi = builder.addCSharpApp("catalog-api", "../Catalog.API/Catalog.API.csproj")
        .withReference(rabbitMq, null).waitFor(rabbitMq)
        .withReference(catalogDb, null)
        .withEnvironment("ASPNETCORE_FORWARDEDHEADERS_ENABLED", "true");

    var orderingApi = builder.addCSharpApp("ordering-api", "../Ordering.API/Ordering.API.csproj")
        .withReference(rabbitMq, null).waitFor(rabbitMq)
        .withReference(orderDb, null).waitFor(orderDb)
        .withHttpHealthCheck(new WithHttpHealthCheckOptions().path("/health"))
        .withEnvironment("Identity__Url", identityEndpoint)
        .withEnvironment("ASPNETCORE_FORWARDEDHEADERS_ENABLED", "true");

    builder.addCSharpApp("order-processor", "../OrderProcessor/OrderProcessor.csproj")
        .withReference(rabbitMq, null).waitFor(rabbitMq)
        .withReference(orderDb, null)
        .waitFor(orderingApi)
        .withEnvironment("ASPNETCORE_FORWARDEDHEADERS_ENABLED", "true");

    builder.addCSharpApp("payment-processor", "../PaymentProcessor/PaymentProcessor.csproj")
        .withReference(rabbitMq, null).waitFor(rabbitMq)
        .withEnvironment("ASPNETCORE_FORWARDEDHEADERS_ENABLED", "true");

    var webHooksApi = builder.addCSharpApp("webhooks-api", "../Webhooks.API/Webhooks.API.csproj")
        .withReference(rabbitMq, null).waitFor(rabbitMq)
        .withReference(webhooksDb, null)
        .withEnvironment("Identity__Url", identityEndpoint)
        .withEnvironment("ASPNETCORE_FORWARDEDHEADERS_ENABLED", "true");

    // Reverse proxies
    builder.addYarp("mobile-bff")
        .withExternalHttpEndpoints()
        .withConfiguration((yarp) -> {
            var catalogCluster = yarp.addClusterFromResource(catalogApi);

            yarp.addRoute("/catalog-api/api/catalog/items", catalogCluster)
                .withMatchRouteQueryParameter(apiVersion("1.0", "1", "2.0"))
                .withTransformPathRemovePrefix("/catalog-api");

            yarp.addRoute("/catalog-api/api/catalog/items/by", catalogCluster)
                .withMatchRouteQueryParameter(apiVersion("1.0", "1", "2.0"))
                .withTransformPathRemovePrefix("/catalog-api");

            yarp.addRoute("/catalog-api/api/catalog/items/{id}", catalogCluster)
                .withMatchRouteQueryParameter(apiVersion("1.0", "1", "2.0"))
                .withTransformPathRemovePrefix("/catalog-api");

            yarp.addRoute("/catalog-api/api/catalog/items/by/{name}", catalogCluster)
                .withMatchRouteQueryParameter(apiVersion("1.0", "1"))
                .withTransformPathRemovePrefix("/catalog-api");

            yarp.addRoute("/catalog-api/api/catalog/items/withsemanticrelevance/{text}", catalogCluster)
                .withMatchRouteQueryParameter(apiVersion("1.0", "1"))
                .withTransformPathRemovePrefix("/catalog-api");

            yarp.addRoute("/catalog-api/api/catalog/items/withsemanticrelevance", catalogCluster)
                .withMatchRouteQueryParameter(apiVersion("2.0"))
                .withTransformPathRemovePrefix("/catalog-api");

            yarp.addRoute("/catalog-api/api/catalog/items/type/{typeId}/brand/{brandId?}", catalogCluster)
                .withMatchRouteQueryParameter(apiVersion("1.0", "1"))
                .withTransformPathRemovePrefix("/catalog-api");

            yarp.addRoute("/catalog-api/api/catalog/items/type/all/brand/{brandId?}", catalogCluster)
                .withMatchRouteQueryParameter(apiVersion("1.0", "1"))
                .withTransformPathRemovePrefix("/catalog-api");

            yarp.addRoute("/catalog-api/api/catalog/catalogTypes", catalogCluster)
                .withMatchRouteQueryParameter(apiVersion("1.0", "1", "2.0"))
                .withTransformPathRemovePrefix("/catalog-api");

            yarp.addRoute("/catalog-api/api/catalog/catalogBrands", catalogCluster)
                .withMatchRouteQueryParameter(apiVersion("1.0", "1", "2.0"))
                .withTransformPathRemovePrefix("/catalog-api");

            yarp.addRoute("/catalog-api/api/catalog/items/{id}/pic", catalogCluster)
                .withMatchRouteQueryParameter(apiVersion("1.0", "1", "2.0"))
                .withTransformPathRemovePrefix("/catalog-api");

            yarp.addRoute("/api/catalog/{*any}", catalogCluster)
                .withMatchRouteQueryParameter(apiVersion("1.0", "1", "2.0"));

            var orderingEndpoint = orderingApi.getEndpoint("http");
            yarp.addRoute("/api/orders/{*any}", orderingEndpoint)
                .withMatchRouteQueryParameter(apiVersion("1.0", "1"));

            var identityHttpEndpoint = identityApi.getEndpoint("http");
            yarp.addRoute("/identity/{*any}", identityHttpEndpoint)
                .withTransformPathRemovePrefix("/identity");
        });

    // Apps
    var webhooksClient = builder.addProject("webhooksclient", "../WebhookClient/WebhookClient.csproj", launchProfileName)
        .withReference(webHooksApi, null)
        .withEnvironment("IdentityUrl", identityEndpoint)
        .withEnvironment("ASPNETCORE_FORWARDEDHEADERS_ENABLED", "true");

    var webApp = builder.addProject("webapp", "../WebApp/WebApp.csproj", launchProfileName)
        .withExternalHttpEndpoints()
        .withUrlForEndpoint("http", (url) -> {
            url.setDisplayText("Online Store (http)");
        });

    if (launchProfileName.equals("https")) {
        webApp.withUrlForEndpoint("https", (url) -> {
            url.setDisplayText("Online Store (https)");
        });
    }

    webApp
        .withReference(basketApi, null)
        .withReference(catalogApi, null)
        .withReference(orderingApi, null)
        .withReference(rabbitMq, null).waitFor(rabbitMq)
        .withEnvironment("IdentityUrl", identityEndpoint)
        .withEnvironment("ASPNETCORE_FORWARDEDHEADERS_ENABLED", "true");

    var useOpenAI = false;
    if (useOpenAI) {
        var openAI = builder.addAzureOpenAI("openai");

        var chat = openAI.addDeployment("chatModel", "gpt-4.1-mini", "2025-04-14")
            .withProperties((d) -> {
                d.setDeploymentName("gpt-4.1-mini");
                d.setSkuName("GlobalStandard");
                d.setSkuCapacity(50);
            });
        var textEmbedding = openAI.addDeployment("textEmbeddingModel", "text-embedding-3-small", "1")
            .withProperties((d) -> {
                d.setDeploymentName("text-embedding-3-small");
                d.setSkuCapacity(20);
            });

        catalogApi.withReference(textEmbedding, null);
        webApp.withReference(chat, null);
    }

    var useOllama = false;
    if (useOllama) {
        // CommunityToolkit Ollama integration is not available in the Java SDK yet.
    }

    // Wire up the callback urls (self referencing)
    var webAppEndpoint = webApp.getEndpoint(launchProfileName);
    var webhooksClientEndpoint = webhooksClient.getEndpoint(launchProfileName);
    webApp.withEnvironment("CallBackUrl", webAppEndpoint);
    webhooksClient.withEnvironment("CallBackUrl", webhooksClientEndpoint);

    // Identity has a reference to all of the apps for callback urls, this is a cyclic reference.
    var basketHttpEndpoint = basketApi.getEndpoint("http");
    var orderingHttpEndpoint = orderingApi.getEndpoint("http");
    var webHooksHttpEndpoint = webHooksApi.getEndpoint("http");
    identityApi
        .withEnvironment("BasketApiClient", basketHttpEndpoint)
        .withEnvironment("OrderingApiClient", orderingHttpEndpoint)
        .withEnvironment("WebhooksApiClient", webHooksHttpEndpoint)
        .withEnvironment("WebhooksWebClient", webhooksClientEndpoint)
        .withEnvironment("WebAppClient", webAppEndpoint);

    builder.build().run();
}

boolean shouldUseHttpForEndpoints() {
    return "1".equals(System.getenv("ESHOP_USE_HTTP_ENDPOINTS"));
}

YarpRouteQueryParameterMatch[] apiVersion(String... values) {
    var queryParameter = new YarpRouteQueryParameterMatch();
    queryParameter.setName("api-version");
    queryParameter.setValues(values);
    queryParameter.setMode(QueryParameterMatchMode.EXACT);

    return new YarpRouteQueryParameterMatch[] {
        queryParameter
    };
}
src/eShop.AppHost.Go/apphost.go
package main

import (
	"log"
	"os"

	"apphost/modules/aspire"
)

func main() {
	builder, err := aspire.CreateBuilder()
	if err != nil {
		log.Fatalf(aspire.FormatError(err))
	}

	builder.AddDockerComposeEnvironment("env")

	// Infrastructure
	redis := builder.AddRedis("redis")
	rabbitMq := builder.AddRabbitMQ("eventbus").
		WithLifetime(aspire.ContainerLifetimePersistent)
	postgres := builder.AddPostgres("postgres").
		WithImage("ankane/pgvector", &aspire.WithImageOptions{Tag: aspire.StringPtr("latest")}).
		WithLifetime(aspire.ContainerLifetimePersistent)

	catalogDb := postgres.AddDatabase("catalogdb")
	identityDb := postgres.AddDatabase("identitydb")
	orderDb := postgres.AddDatabase("orderingdb")
	webhooksDb := postgres.AddDatabase("webhooksdb")

	launchProfileName := "https"
	if shouldUseHttpForEndpoints() {
		launchProfileName = "http"
	}

	// Services
	identityApi := builder.AddProject("identity-api", "../Identity.API/Identity.API.csproj", &aspire.AddProjectOptions{LaunchProfileOrOptions: launchProfileName}).
		WithExternalHttpEndpoints().
		WithReference(identityDb).
		WithEnvironment("ASPNETCORE_FORWARDEDHEADERS_ENABLED", "true")

	identityEndpoint := identityApi.GetEndpoint(launchProfileName)

	basketApi := builder.AddCSharpApp("basket-api", "../Basket.API/Basket.API.csproj").
		WithReference(redis).
		WithReference(rabbitMq).WaitFor(rabbitMq).
		WithEnvironment("Identity__Url", identityEndpoint).
		WithEnvironment("ASPNETCORE_FORWARDEDHEADERS_ENABLED", "true")
	redis.WithParentRelationship(basketApi)

	catalogApi := builder.AddCSharpApp("catalog-api", "../Catalog.API/Catalog.API.csproj").
		WithReference(rabbitMq).WaitFor(rabbitMq).
		WithReference(catalogDb).
		WithEnvironment("ASPNETCORE_FORWARDEDHEADERS_ENABLED", "true")

	orderingApi := builder.AddCSharpApp("ordering-api", "../Ordering.API/Ordering.API.csproj").
		WithReference(rabbitMq).WaitFor(rabbitMq).
		WithReference(orderDb).WaitFor(orderDb).
		WithHttpHealthCheck(&aspire.WithHttpHealthCheckOptions{Path: aspire.StringPtr("/health")}).
		WithEnvironment("Identity__Url", identityEndpoint).
		WithEnvironment("ASPNETCORE_FORWARDEDHEADERS_ENABLED", "true")

	builder.AddCSharpApp("order-processor", "../OrderProcessor/OrderProcessor.csproj").
		WithReference(rabbitMq).WaitFor(rabbitMq).
		WithReference(orderDb).
		WaitFor(orderingApi).
		WithEnvironment("ASPNETCORE_FORWARDEDHEADERS_ENABLED", "true")

	builder.AddCSharpApp("payment-processor", "../PaymentProcessor/PaymentProcessor.csproj").
		WithReference(rabbitMq).WaitFor(rabbitMq).
		WithEnvironment("ASPNETCORE_FORWARDEDHEADERS_ENABLED", "true")

	webHooksApi := builder.AddCSharpApp("webhooks-api", "../Webhooks.API/Webhooks.API.csproj").
		WithReference(rabbitMq).WaitFor(rabbitMq).
		WithReference(webhooksDb).
		WithEnvironment("Identity__Url", identityEndpoint).
		WithEnvironment("ASPNETCORE_FORWARDEDHEADERS_ENABLED", "true")

	// Reverse proxies
	builder.AddYarp("mobile-bff").
		WithExternalHttpEndpoints().
		WithConfiguration(func(yarp aspire.YarpConfigurationBuilder) {
			catalogCluster := yarp.AddClusterFromResource(catalogApi)

			yarp.AddRoute("/catalog-api/api/catalog/items", catalogCluster).
				WithMatchRouteQueryParameter(apiVersion("1.0", "1", "2.0")).
				WithTransformPathRemovePrefix("/catalog-api")

			yarp.AddRoute("/catalog-api/api/catalog/items/by", catalogCluster).
				WithMatchRouteQueryParameter(apiVersion("1.0", "1", "2.0")).
				WithTransformPathRemovePrefix("/catalog-api")

			yarp.AddRoute("/catalog-api/api/catalog/items/{id}", catalogCluster).
				WithMatchRouteQueryParameter(apiVersion("1.0", "1", "2.0")).
				WithTransformPathRemovePrefix("/catalog-api")

			yarp.AddRoute("/catalog-api/api/catalog/items/by/{name}", catalogCluster).
				WithMatchRouteQueryParameter(apiVersion("1.0", "1")).
				WithTransformPathRemovePrefix("/catalog-api")

			yarp.AddRoute("/catalog-api/api/catalog/items/withsemanticrelevance/{text}", catalogCluster).
				WithMatchRouteQueryParameter(apiVersion("1.0", "1")).
				WithTransformPathRemovePrefix("/catalog-api")

			yarp.AddRoute("/catalog-api/api/catalog/items/withsemanticrelevance", catalogCluster).
				WithMatchRouteQueryParameter(apiVersion("2.0")).
				WithTransformPathRemovePrefix("/catalog-api")

			yarp.AddRoute("/catalog-api/api/catalog/items/type/{typeId}/brand/{brandId?}", catalogCluster).
				WithMatchRouteQueryParameter(apiVersion("1.0", "1")).
				WithTransformPathRemovePrefix("/catalog-api")

			yarp.AddRoute("/catalog-api/api/catalog/items/type/all/brand/{brandId?}", catalogCluster).
				WithMatchRouteQueryParameter(apiVersion("1.0", "1")).
				WithTransformPathRemovePrefix("/catalog-api")

			yarp.AddRoute("/catalog-api/api/catalog/catalogTypes", catalogCluster).
				WithMatchRouteQueryParameter(apiVersion("1.0", "1", "2.0")).
				WithTransformPathRemovePrefix("/catalog-api")

			yarp.AddRoute("/catalog-api/api/catalog/catalogBrands", catalogCluster).
				WithMatchRouteQueryParameter(apiVersion("1.0", "1", "2.0")).
				WithTransformPathRemovePrefix("/catalog-api")

			yarp.AddRoute("/catalog-api/api/catalog/items/{id}/pic", catalogCluster).
				WithMatchRouteQueryParameter(apiVersion("1.0", "1", "2.0")).
				WithTransformPathRemovePrefix("/catalog-api")

			yarp.AddRoute("/api/catalog/{*any}", catalogCluster).
				WithMatchRouteQueryParameter(apiVersion("1.0", "1", "2.0"))

			orderingEndpoint := orderingApi.GetEndpoint("http")
			yarp.AddRoute("/api/orders/{*any}", orderingEndpoint).
				WithMatchRouteQueryParameter(apiVersion("1.0", "1"))

			identityHttpEndpoint := identityApi.GetEndpoint("http")
			yarp.AddRoute("/identity/{*any}", identityHttpEndpoint).
				WithTransformPathRemovePrefix("/identity")
		})

	// Apps
	webhooksClient := builder.AddProject("webhooksclient", "../WebhookClient/WebhookClient.csproj", &aspire.AddProjectOptions{LaunchProfileOrOptions: launchProfileName}).
		WithReference(webHooksApi).
		WithEnvironment("IdentityUrl", identityEndpoint).
		WithEnvironment("ASPNETCORE_FORWARDEDHEADERS_ENABLED", "true")

	webApp := builder.AddProject("webapp", "../WebApp/WebApp.csproj", &aspire.AddProjectOptions{LaunchProfileOrOptions: launchProfileName}).
		WithExternalHttpEndpoints().
		WithUrlForEndpoint("http", func(url *aspire.ResourceUrlAnnotation) {
			url.DisplayText = "Online Store (http)"
		})

	if launchProfileName == "https" {
		webApp.WithUrlForEndpoint("https", func(url *aspire.ResourceUrlAnnotation) {
			url.DisplayText = "Online Store (https)"
		})
	}

	webApp.
		WithReference(basketApi).
		WithReference(catalogApi).
		WithReference(orderingApi).
		WithReference(rabbitMq).WaitFor(rabbitMq).
		WithEnvironment("IdentityUrl", identityEndpoint).
		WithEnvironment("ASPNETCORE_FORWARDEDHEADERS_ENABLED", "true")

	useOpenAI := false
	if useOpenAI {
		openAI := builder.AddAzureOpenAI("openai")

		chat := openAI.AddDeployment("chatModel", "gpt-4.1-mini", "2025-04-14").
			WithProperties(func(d aspire.AzureOpenAIDeploymentResource) {
				d.SetDeploymentName("gpt-4.1-mini")
				d.SetSkuName("GlobalStandard")
				d.SetSkuCapacity(50)
			})
		textEmbedding := openAI.AddDeployment("textEmbeddingModel", "text-embedding-3-small", "1").
			WithProperties(func(d aspire.AzureOpenAIDeploymentResource) {
				d.SetDeploymentName("text-embedding-3-small")
				d.SetSkuCapacity(20)
			})

		catalogApi.WithReference(textEmbedding)
		webApp.WithReference(chat)
	}

	useOllama := false
	if useOllama {
		// CommunityToolkit Ollama integration is not available in the Go SDK yet.
	}

	// Wire up the callback urls (self referencing)
	webAppEndpoint := webApp.GetEndpoint(launchProfileName)
	webhooksClientEndpoint := webhooksClient.GetEndpoint(launchProfileName)
	webApp.WithEnvironment("CallBackUrl", webAppEndpoint)
	webhooksClient.WithEnvironment("CallBackUrl", webhooksClientEndpoint)

	// Identity has a reference to all of the apps for callback urls, this is a cyclic reference.
	basketHttpEndpoint := basketApi.GetEndpoint("http")
	orderingHttpEndpoint := orderingApi.GetEndpoint("http")
	webHooksHttpEndpoint := webHooksApi.GetEndpoint("http")
	identityApi.
		WithEnvironment("BasketApiClient", basketHttpEndpoint).
		WithEnvironment("OrderingApiClient", orderingHttpEndpoint).
		WithEnvironment("WebhooksApiClient", webHooksHttpEndpoint).
		WithEnvironment("WebhooksWebClient", webhooksClientEndpoint).
		WithEnvironment("WebAppClient", webAppEndpoint)

	app, err := builder.Build()
	if err != nil {
		log.Fatalf(aspire.FormatError(err))
	}
	if err := app.Run(nil); err != nil {
		log.Fatalf(aspire.FormatError(err))
	}
}

func shouldUseHttpForEndpoints() bool {
	return os.Getenv("ESHOP_USE_HTTP_ENDPOINTS") == "1"
}

func apiVersion(values ...string) []*aspire.YarpRouteQueryParameterMatch {
	return []*aspire.YarpRouteQueryParameterMatch{
		{
			Name:   "api-version",
			Values: values,
			Mode:   aspire.QueryParameterMatchModeExact,
		},
	}
}

Validation

  • dotnet test --project tests/Aspire.Hosting.CodeGeneration.Go.Tests/Aspire.Hosting.CodeGeneration.Go.Tests.csproj --no-launch-profile -- --filter-class "*.AtsGoCodeGeneratorTests" --filter-not-trait "quarantined=true" --filter-not-trait "outerloop=true"
  • Previously validated focused Java, Python, and RemoteHost regression tests in this worktree.
  • Validated TypeScript, Python, Java, and Go eShop AppHosts with the local Aspire CLI via dotnet run --project src/Aspire.Cli/Aspire.Cli.csproj -- start --isolated --debug --no-build --non-interactive --format Json --apphost <file>.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 2, 2026 01:00
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 2, 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 -- 16691

Or

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

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 several polyglot AppHost codegen/transport edge cases so generated Python/Java/Go AppHosts preserve the correct AppHost source path and callback behavior (positional JSON-RPC params, DTO/handle marshaling, and DTO writeback) matches the declared signatures.

Changes:

  • Update ATS callback argument marshaling to use the declared CLR parameter type (not runtime type) for handles/arrays, with a new regression test.
  • Improve Java JSON-RPC transport callback handling (array-style params + positional pN args) and ensure DTO values serialize as maps via a JsonSerializable contract.
  • Adjust Python/Go builder defaults to respect ASPIRE_APPHOST_FILEPATH (and fix Go callback DTO writeback), with focused generator regression tests + snapshot updates.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/Aspire.Hosting.RemoteHost.Tests/CallbackProxyTests.cs Adds regression coverage for handle marshaling using declared callback parameter types.
src/Aspire.Hosting.RemoteHost/Ats/AtsMarshaller.cs Introduces marshaling overload that honors declared CLR types when producing JSON.
src/Aspire.Hosting.RemoteHost/Ats/AtsCallbackProxyFactory.cs Passes declared parameter types into marshalling during proxy invocation argument packing.
src/Aspire.Hosting.CodeGeneration.Python/PythonModuleBuilder.cs Updates generated create_builder to accept/apply app_host_file_path and default it from ASPIRE_APPHOST_FILEPATH.
tests/Aspire.Hosting.CodeGeneration.Python.Tests/AtsPythonCodeGeneratorTests.cs Adds regression assertions for Python create_builder defaults.
tests/Aspire.Hosting.CodeGeneration.Python.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.py Snapshot update reflecting Python builder option defaults.
tests/Aspire.Hosting.CodeGeneration.Python.Tests/Snapshots/AtsGeneratedAspire.verified.py Snapshot update reflecting Python builder option defaults.
src/Aspire.Hosting.CodeGeneration.Java/Resources/Transport.java Enhances callback request parsing (array/object params) and DTO serialization hook.
src/Aspire.Hosting.CodeGeneration.Java/AtsJavaCodeGenerator.cs Emits DTOs implementing JsonSerializable so they serialize as maps.
tests/Aspire.Hosting.CodeGeneration.Java.Tests/AtsJavaCodeGeneratorTests.cs Adds regression tests for array callback params + DTO map serialization in generated Java.
tests/Aspire.Hosting.CodeGeneration.Java.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.java Snapshot update for transport changes + DTO serialization behavior.
tests/Aspire.Hosting.CodeGeneration.Java.Tests/Snapshots/AtsGeneratedAspire.verified.java Snapshot update for transport changes + DTO serialization behavior.
src/Aspire.Hosting.CodeGeneration.Go/Resources/base.go Adds fallback struct-field decoding when full JSON unmarshal fails (used for DTO callback args).
src/Aspire.Hosting.CodeGeneration.Go/AtsGoCodeGenerator.cs Emits Go callback shims that return mutated DTO args + fixes CreateBuilder defaults/omissions.
tests/Aspire.Hosting.CodeGeneration.Go.Tests/AtsGoCodeGeneratorTests.cs Adds regression tests for Go CreateBuilder defaults and DTO callback writeback behavior.
tests/Aspire.Hosting.CodeGeneration.Go.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.go Snapshot update for Go callback writeback + CreateBuilder defaults.
tests/Aspire.Hosting.CodeGeneration.Go.Tests/Snapshots/AtsGeneratedAspire.verified.go Snapshot update for Go CreateBuilder defaults.

Comment thread src/Aspire.Hosting.CodeGeneration.Go/Resources/base.go Outdated
Comment thread src/Aspire.Hosting.CodeGeneration.Java/Resources/Transport.java Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 4, 2026

🎬 CLI E2E Test Recordings — 69 recordings uploaded (commit 54cc0b2)

View all recordings
Status Test Recording
AddPackageInteractiveWhileAppHostRunningDetached ▶️ View Recording
AddPackageWhileAppHostRunningDetached ▶️ View Recording
AgentCommands_AllHelpOutputs_AreCorrect ▶️ View Recording
AgentInitCommand_DefaultSelection_InstallsSkillOnly ▶️ View Recording
AgentInitCommand_MigratesDeprecatedConfig ▶️ View Recording
AspireUpdateRemovesAppHostPackageVersionFromDirectoryPackagesProps ▶️ 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
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
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_PreservesAllValueTypes ▶️ View Recording
InteractiveCSharpInitCreatesExpectedFiles ▶️ View Recording
InvalidAppHostPathWithComments_IsHealedOnRun ▶️ View Recording
LatestCliCanStartStableChannelAppHost ▶️ View Recording
LatestCliCanStartStableChannelTypeScriptAppHost ▶️ View Recording
LegacySettingsMigration_AdjustsRelativeAppHostPath ▶️ View Recording
LogsCommandShowsResourceLogs ▶️ View Recording
OtelLogsReturnsStructuredLogsFromStarterAppCore ▶️ View Recording
PsCommandListsRunningAppHost ▶️ View Recording
PsFormatJsonOutputsOnlyJsonToStdout ▶️ View Recording
PublishWithConfigureEnvFileUpdatesEnvOutput ▶️ View Recording
PublishWithDockerComposeServiceCallbackSucceeds ▶️ View Recording
PublishWithoutOutputPathUsesAppHostDirectoryDefault ▶️ 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

📹 Recordings uploaded automatically from CI run #25325202639

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.

2 participants