File: AtsTypeScriptApiReferenceExporter.cs
Web Access
Project: src\src\Aspire.Hosting.CodeGeneration.TypeScript\Aspire.Hosting.CodeGeneration.TypeScript.csproj (Aspire.Hosting.CodeGeneration.TypeScript)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Text.Json;
using Aspire.TypeSystem;
 
namespace Aspire.Hosting.CodeGeneration.TypeScript;
 
/// <summary>
/// Exports the canonical TypeScript API reference for the surface
/// <see cref="AtsTypeScriptCodeGenerator"/> generates.
/// </summary>
/// <remarks>
/// <para>
/// This deliberately lives on its own type rather than on <see cref="AtsTypeScriptCodeGenerator"/>.
/// <c>Aspire.TypeSystem</c> is force-shared from the apphost server's default
/// <see cref="System.Runtime.Loader.AssemblyLoadContext"/> (see
/// <c>src/Aspire.Hosting.RemoteHost/IntegrationLoadContext.cs</c>) and freezes its strong-name
/// <c>AssemblyVersion</c> at a constant so an older CLI still binds a newer SDK's codegen assembly.
/// Version binding therefore succeeds, but an older CLI's bundled copy has no
/// <see cref="IApiReferenceExporter"/> in it: the interface is new. A type's interface list is
/// resolved eagerly when the type loads, so putting the interface on the code generator would make
/// the generator itself unloadable under any CLI that predates the interface, and
/// <c>CodeGeneratorResolver</c> would then find no TypeScript generator at all — TypeScript
/// generation, not just export, would stop working.
/// </para>
/// <para>
/// Keeping export on a separate type confines that loss to the feature the older CLI cannot use
/// anyway: <c>CodeGeneratorResolver</c> salvages the loadable types out of the
/// <see cref="System.Reflection.ReflectionTypeLoadException"/>, so the generator survives and only
/// this type disappears.
/// </para>
/// </remarks>
internal sealed class AtsTypeScriptApiReferenceExporter : IApiReferenceExporter
{
    /// <inheritdoc />
    public string Language => "TypeScript";
 
    /// <inheritdoc />
    public JsonElement ExportApi(
        AtsContext context,
        ApiReferenceExportOptions options,
        CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(context);
        ArgumentNullException.ThrowIfNull(options);
        cancellationToken.ThrowIfCancellationRequested();
 
        // Build the projector from the same context the generator would use, so the exported
        // documentation describes the exact signatures generation would emit rather than a
        // second, independently derived reading of the ATS context.
        var projector = new TypeScriptApiProjector(context);
        var model = projector.BuildApiModel(
            new TypeScriptApiPackageIdentity(options.PackageName, options.PackageVersion),
            options.ExportingAssemblyNames,
            cancellationToken);
 
        // JsonDocument.Parse + Clone rather than JsonSerializer, because this assembly is
        // AOT-compatible and the serializer's reflection-based overloads are not.
        using var document = JsonDocument.Parse(TypeScriptApiExportWriter.WriteToJson(model));
        return document.RootElement.Clone();
    }
}