Skip to content

Export canonical TypeScript API data from the CLI - #19032

Merged
Adam Ratzman (adamint) merged 77 commits into
microsoft:mainfrom
adamint:adamint/versioned-ts-api-docs
Aug 25, 2026
Merged

Export canonical TypeScript API data from the CLI#19032
Adam Ratzman (adamint) merged 77 commits into
microsoft:mainfrom
adamint:adamint/versioned-ts-api-docs

Conversation

@adamint

@adamint Adam Ratzman (adamint) commented Aug 5, 2026

Copy link
Copy Markdown
Member

Description

Adds aspire sdk export, which emits a deterministic TypeScript API reference as JSON. The command exports Aspire.Hosting at the running CLI SDK version by default, or an exact PackageName@Version supplied with --package.

This is the producer side of fixing #17608: generated TypeScript signatures and the exported documentation model now share the same projector, so optional-parameter shaping, promise wrappers, entry points, DTOs, enums, and declaration fragments cannot drift independently.

The focused implementation includes:

  • TypeScript API projection, schema serialization, and a discovered IApiReferenceExporter.
  • An authenticated RemoteHost RPC and CLI command with JSON on stdout and diagnostics on stderr.
  • Export-only package ownership resolution from restored lib, ref, and runtimes/<rid>/lib assets.
  • Exact requested-package restoration without changing normal AppHost build behavior.

The reduction intentionally excludes source selection, output-file handling, checkout provenance/substitution machinery, compatibility shims, and normal-build manifest expansion.

Contributes to #17608.

Validation

  • Affected TypeScript, RemoteHost, and CLI projects build with zero errors.
  • Focused tests: 15 TypeScript, 27 RemoteHost, 19 CLI command, and 1 exact-package test passed.
  • Direct NuGet-backed scanner proof is blocked in the current execution sandbox because access to /etc/resolv.conf is denied.

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
  • 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

Adam Ratzman (adamint) and others added 6 commits August 5, 2026 16:51
Extract every TypeScript-specific resolution decision out of
AtsTypeScriptCodeGenerator into TypeScriptApiProjector: type mapping,
options flattening, callback shaping, promise wrapping, and fluent return
selection. The generator consumes the resolved model instead of
recomputing those decisions inline, and TypeScriptApiExportWriter
serializes the same model into a schema version 1 canonical export.

Documentation that reconstructs signatures from raw ATS drifts from the
SDK that actually ships (microsoft#17608). Sharing one projection
makes that drift impossible: the generated aspire.mts snapshots are
byte-identical, and a new test asserts every exported declaration appears
verbatim in the corresponding generated public interface.

Ownership is resolved per capability rather than per type, mirroring
AtsContextFilter, so a package that extends another package's resource
documents its own members without republishing the referenced type.
Referenced types contribute opaque declaration fragments keyed by their
real owner, which lets a manifest concatenate packages and type-check
without site-authored shims.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 819baaf4-91c3-44ce-8004-3dbc9110f810
Adds IApiReferenceExporter as an optional companion to ICodeGenerator, so a
language provider can describe the surface it generates without every provider
being forced to. AtsTypeScriptCodeGenerator implements it by building the same
TypeScriptApiProjector code generation uses, which is what keeps documentation
from drifting from emitted source.

RemoteHost exposes it as an authenticated exportApi RPC that resolves the
existing code generator and then requires the optional interface, rather than
introducing a second discovery mechanism. The provider's JSON document is
returned verbatim; the payload schema belongs to the language provider.

Also fixes a reference-closure bug this uncovered. Types owned by the selected
assembly were seeded into AtsContextFilter's included sets directly, so the
"was this newly added?" guard refused to walk their own members. An owned DTO
exposing an enum from a non-Aspire dependency kept the DTO and dropped the
enum, and code generation then failed on the dangling reference -- generateCode
for Aspire.Hosting hit this too.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 819baaf4-91c3-44ce-8004-3dbc9110f810
Adds a hidden `aspire sdk export` that asks a scanner AppHost for the canonical
API reference of one package in one language and writes it to stdout. Because
documentation pipelines consume it, stdout carries exactly one JSON document and
every status message goes to stderr, so `aspire sdk export ... > api.json`
produces a usable file.

The package version must be exact. A document published under a range would
describe a different SDK after the next restore, so floating versions are
rejected before restore rather than resolved. With no --package, the command
defaults to Aspire.Hosting at this CLI's own identity version, which is the
whole point: the docs describe the SDK this CLI generates against.

SdkCommandPreparation holds only what dump and export genuinely share --
argument parsing, scanner AppHost setup, exporting-assembly discovery. Dump keeps
its own serialization and is deliberately not routed through the canonical
exporter; a test asserts its payload still has the capabilities shape and no
schemaVersion.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 819baaf4-91c3-44ce-8004-3dbc9110f810
The export contract promises that concatenating a manifest's declaration
fragments type-checks without site-authored shims. Running TypeScript over
real Aspire.Hosting and Aspire.Hosting.Redis exports showed it did not.

Handle types with no generated wrapper class surface in signatures under
their raw handle alias name, but the fragment pass derived a class name
instead, so it declared a symbol nothing referenced and left the referenced
alias undefined. Emit the same alias the generator emits.

The runtime fragment was also missing Handle, InputType and AbortSignal,
and MarshalledHandle was missing $type.

Keep sdk export's own --help reachable: Hidden on a subcommand suppresses
its help output, and the parent sdk command is already hidden.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 819baaf4-91c3-44ce-8004-3dbc9110f810
The scanner AppHost does not reference the language's code generation
package by default, so the server loaded no generators and every export
failed with "No code generator found for language: typescript". sdk
generate already adds the package for exactly this reason.

Verified end to end against the repo-local AppHost server: exporting
Aspire.Hosting and Aspire.Hosting.Redis now writes schema version 1
documents to stdout with nothing on stderr, and TypeScript type-checks
their combined declaration fragments with no errors.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 819baaf4-91c3-44ce-8004-3dbc9110f810
A package that extends a type another package owns emitted its
contribution as a normal interface item: same "interface:{name}" stable
ID as the owning package's item, and owningAssembly pointing at the
extending package. Across a manifest that collides with the real page and
misattributes the type, which the export contract explicitly forbids.

Give these items an augmentation kind, an "augmentation:{name}" ID, and
the type's real owning assembly. The declaration fragments already used
this split; the items now match.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 819baaf4-91c3-44ce-8004-3dbc9110f810
@github-actions

github-actions Bot commented Aug 5, 2026

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 -- 19032

Or

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

@github-actions github-actions Bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Aug 5, 2026

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 canonical TypeScript API export through aspire sdk export, sharing projection logic with generated SDK output.

Changes:

  • Adds the export model, serializer, RPC, and CLI command.
  • Fixes ATS reference-closure handling.
  • Adds schema, CLI, RPC, and regression tests.
Show a summary per file
File Description
src/Aspire.Hosting.CodeGeneration.TypeScript/AtsTypeScriptCodeGenerator.cs Uses shared projection and implements export.
src/Aspire.Hosting.CodeGeneration.TypeScript/TypeScriptApiProjector.cs Centralizes TypeScript API projection.
src/Aspire.Hosting.CodeGeneration.TypeScript/TypeScriptApiModel.cs Defines the export model.
src/Aspire.Hosting.CodeGeneration.TypeScript/TypeScriptApiExportWriter.cs Serializes schema v1.
src/Aspire.TypeSystem/IApiReferenceExporter.cs Adds the exporter contract.
src/Aspire.TypeSystem/ApiReferenceExportOptions.cs Adds package export options.
src/Aspire.TypeSystem/api/Aspire.TypeSystem.cs Updates the generated API baseline.
src/Aspire.Hosting.RemoteHost/CodeGeneration/CodeGenerationService.cs Exposes the export RPC.
src/Aspire.Hosting.RemoteHost/CodeGeneration/CodeGeneratorResolver.cs Resolves exporters.
src/Aspire.Hosting.RemoteHost/Diagnostics/RemoteHostProfilingTelemetry.cs Adds export telemetry.
src/Aspire.Hosting.RemoteHost/AtsContextFilter.cs Expands referenced type closure.
src/Aspire.Hosting.RemoteHost/Aspire.Hosting.RemoteHost.csproj Grants test internals access.
src/Aspire.Cli/Commands/Sdk/SdkExportCommand.cs Implements sdk export.
src/Aspire.Cli/Commands/Sdk/SdkCommandPreparation.cs Shares scanner preparation.
src/Aspire.Cli/Commands/Sdk/SdkDumpCommand.cs Uses shared preparation.
src/Aspire.Cli/Commands/Sdk/SdkCommand.cs Registers the subcommand.
src/Aspire.Cli/Projects/IAppHostRpcClient.cs Adds the client contract.
src/Aspire.Cli/Projects/AppHostRpcClient.cs Implements export RPC invocation.
src/Aspire.Cli/Program.cs Registers command services.
tests/Aspire.Hosting.RemoteHost.Tests/CodeGeneration/ApiReferenceExportTests.cs Tests RPC behavior.
tests/Aspire.Hosting.RemoteHost.Tests/AtsContextFilterTests.cs Covers closure expansion.
tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/AtsTypeScriptCodeGeneratorTests.cs Tests export parity and declarations.
tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/Snapshots/AtsTypeScriptCodeGeneratorTests.ApiExport.verified.json Captures schema output.
tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/Snapshots/AtsTypeScriptCodeGeneratorTests.ApiDeclarations.verified.txt Captures declaration fragments.
tests/Aspire.Cli.Tests/Commands/Sdk/SdkExportCommandTests.cs Tests CLI behavior.
tests/Aspire.Cli.Tests/TestServices/FakeAppHostServerSession.cs Extends the fake RPC client.
tests/Aspire.Cli.Tests/Utils/CliTestHelper.cs Registers the command in tests.

Review details

Suppressed comments (3)

src/Aspire.Cli/Commands/Sdk/SdkExportCommand.cs:128

  • The code generator is restored at the running CLI version rather than the requested package version. Exporting an older/newer package can therefore apply a different release's projection rules (or fail when --source contains only the requested release), so the result is not canonical for the requested Name@Version. Restore the generator at packageVersion, as normal AppHost generation does for its effective SDK version.
            integrations.Add(IntegrationReference.FromPackage(codeGenPackage, ExecutionContext.IdentityVersion));

src/Aspire.Cli/Commands/Sdk/SdkExportCommand.cs:76

  • This command promises that every human-readable message goes to stderr, but it never switches InteractionService.Console from its default stdout route. As a result, preparation diagnostics and the --output success message can be written to stdout. Route the command's human output to stderr before any discovery or preparation work.
        var language = parseResult.GetValue(s_languageOption)!;

src/Aspire.Cli/Commands/Sdk/SdkExportCommand.cs:119

  • An explicit Aspire.Hosting@Version is never restored here. Both scanner implementations ignore the sdkVersion argument, so a CLI running a different version scans its bundled/repository Aspire.Hosting assembly and then labels that surface with the requested version. The command therefore cannot honor its exact Name@Version contract for the core package; the scanner must load the requested core package version rather than skipping it.
            if (!string.Equals(packageName, CorePackageName, StringComparison.OrdinalIgnoreCase))
            {
                integrations.Add(reference);
            }
  • Files reviewed: 25/27 changed files
  • Comments generated: 4
  • Review effort level: Balanced

Comment thread src/Aspire.Cli/Commands/Sdk/SdkExportCommand.cs Outdated
Comment thread src/Aspire.Hosting.CodeGeneration.TypeScript/TypeScriptApiProjector.cs Outdated
Comment thread src/Aspire.Cli/Commands/Sdk/SdkCommandPreparation.cs Outdated
Adam Ratzman (adamint) and others added 4 commits August 5, 2026 18:44
The generated capability scanner writes a Directory.Packages.props that turns central package
management on, which rejects an inline Version attribute with NU1008. Any integration outside
the repo failed to build, so sdk export could not be pointed at a third-party package.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 819baaf4-91c3-44ce-8004-3dbc9110f810
Ownership only transfers to PreparedSdkSession once one is returned. A throw from StartAsync or
GetRpcClientAsync left the spawned scanner running and holding the temp directory.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 819baaf4-91c3-44ce-8004-3dbc9110f810
Fragments built from raw string literals carry whatever line endings the source was checked out
with, so a Windows build disagreed with a Linux build about identical declarations. Consumers
deduplicate a manifest by comparing content for the same ID, so the text has to be stable.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 819baaf4-91c3-44ce-8004-3dbc9110f810
Every integration that extends DistributedApplicationBuilder produced the same augmentation item
ID, which recreated the cross-package collision the augmentation kind exists to avoid, so the
contributing package is now part of the ID.

CreateBuilderOptions also gained its client-only throwOnPendingRejections property from the module
emitter alone, so the export described a smaller interface than the one we ship. That list moved
to the projector and both paths read it from there.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 819baaf4-91c3-44ce-8004-3dbc9110f810
Copilot AI review requested due to automatic review settings August 5, 2026 22:44

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.

Review details

Suppressed comments (3)

src/Aspire.Hosting.CodeGeneration.TypeScript/TypeScriptApiProjector.cs:276

  • This hardcodes the generated options-bag parameter name to options, while the source emitter calls GetPublicOptionsParameterName to avoid collisions. A capability that already has a parameter named options is therefore exported with a different—or even duplicate and invalid—signature from the generated .mts, defeating the canonical-source contract. Resolve and pass the collision-safe name here too.
        var parameterList = BuildPublicParameterList(
            requiredParams,
            hasOptionals,
            optionsTypeName,
            trailingCancellationToken: GetTrailingCancellationTokenParameter(optionalParams));

src/Aspire.Hosting.CodeGeneration.TypeScript/TypeScriptApiProjector.cs:744

  • The structured parameters array is still built from raw ATS parameters instead of the resolved public signature. For example, the checked-in export declares withOptionalString(options?: WithOptionalStringOptions) but reports value and enabled as its parameters. Any consumer rendering the structured fields will reconstruct the same stale positional API this PR is intended to eliminate. Build this list from the required parameters plus the resolved options-bag and trailing cancellation-token parameters.
        var parameters = capability.Parameters
            .Where(p => builderModel is null || p.Name != targetParamName)
            .Select(p => new TypeScriptApiParameter
            {
                Name = p.Name,
                DeclaredType = MapParameterToTypeScript(p),
                IsOptional = p.IsOptional || p.IsNullable,
                Summary = p.Documentation?.Summary
            })

src/Aspire.Cli/Commands/Sdk/SdkExportCommand.cs:194

  • The new CLI command has only fake project/session/RPC tests; there is no CLI end-to-end test that starts the real scanner and validates the machine-output contract. That leaves package/codegen restoration, RPC registration, and stdout/stderr separation untested—the exact integration points this command introduces. Add a Hex1b CLI E2E test using a local-hive package that invokes sdk export, redirects stdout, parses the JSON, and verifies stderr remains separate.
        await using var session = await SdkCommandPreparation.PrepareSessionAsync(
            _appHostServerProjectFactory,
            _serverSessionFactory,
            InteractionService,
            _logger,
            "aspire-sdk-export-",
            sdkVersion,
            integrations,
            packageSource,
            cancellationToken);
  • Files reviewed: 27/29 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread src/Aspire.Cli/Commands/Sdk/SdkExportCommand.cs Outdated
The scanner never honored a requested core version. Both PrepareAsync
implementations accept sdkVersion and ignore it, so
`sdk export --package Aspire.Hosting@13.0.0` returned byte-identical JSON
to a 13.5.0-dev export, relabelled 13.0.0. That is the stale-signature
problem this command exists to fix.

Honoring the request is not possible: the prebuilt server bundles core and
the RPC host compiled together, so loading a foreign core would break the
RPC contract. Reject the skew instead, with a message pointing at the CLI
that can produce the requested export.

Integration packages are unaffected: they become real PackageReferences and
already resolve to the requested version.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 819baaf4-91c3-44ce-8004-3dbc9110f810
Copilot AI review requested due to automatic review settings August 5, 2026 23:19

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.

Review details

Suppressed comments (3)

src/Aspire.Cli/Commands/Sdk/SdkExportCommand.cs:135

  • A non-core Aspire package can request any version here, but the scanner and TypeScript code-generation package are still pinned to the running CLI's IdentityVersion. Thus a 13.6 CLI exporting Aspire.Hosting.Redis@13.5.0 applies the 13.6 signature-shaping generator and labels the result as 13.5.0, recreating the version drift this command is intended to prevent. Either restore/run the matching SDK and generator or reject official Aspire.Hosting.* versions that differ from IdentitySdkVersion, as is already done for core.
            else
            {
                integrations.Add(reference);
            }

src/Aspire.Hosting.CodeGeneration.TypeScript/TypeScriptApiProjector.cs:276

  • ResolveMethodSignature always names the synthesized optional-parameter bag options, but the source emitter calls GetPublicOptionsParameterName to avoid collisions. Real generated APIs such as addCSharpApp and promptProgress therefore use optionsBag, while this export reports options, reintroducing the docs/generated-signature drift this PR is intended to remove. Compute and pass the same collision-safe name here.
        var parameterList = BuildPublicParameterList(
            requiredParams,
            hasOptionals,
            optionsTypeName,
            trailingCancellationToken: GetTrailingCancellationTokenParameter(optionalParams));

src/Aspire.Hosting.CodeGeneration.TypeScript/TypeScriptApiProjector.cs:740

  • The structured parameters array is built from the original ATS parameters rather than the public signature. For example, the snapshot declares addTestRedis(name, options?: AddTestRedisOptions) but serializes name and port, with no options parameter. Consumers using these schema fields will reconstruct the same stale positional API this export is meant to eliminate. Build this list from the required public parameters plus the synthesized options bag (and any separately emitted cancellation token).
        var parameters = capability.Parameters
            .Where(p => builderModel is null || p.Name != targetParamName)
            .Select(p => new TypeScriptApiParameter
            {
                Name = p.Name,
  • Files reviewed: 27/29 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread src/Aspire.Cli/Commands/Sdk/SdkExportCommand.cs
The command always emits JSON, so it has no --format option for BaseCommand's
json redirect to key off. Preparation diagnostics and the --output success
message therefore went to stdout and corrupted the document when a caller
piped it. DisplaySuccess takes no per-call override, so it could not opt out.

Route the interaction service to stderr at command entry. The JSON write
already overrides back to stdout explicitly, and an explicit override wins
over the service setting.

SdkExportSendsProgressToStderrOnly asserted on the per-call override, which is
null for these calls, so it passed vacuously. It now resolves the effective
destination and covers the --output path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 819baaf4-91c3-44ce-8004-3dbc9110f810
Copilot AI review requested due to automatic review settings August 5, 2026 23:39
Check cancellation before resolving the TypeScript projection and cover the ordering explicitly.

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

Copilot-Session: d65b598f-72dd-4fca-bdc4-8debbbd7f821

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.

Review details

Suppressed comments (1)

src/Aspire.Cli/Commands/Sdk/SdkExportCommand.cs:72

  • IdentitySdkVersion can be supplied by ASPIRE_CLI_VERSION, but both scanner project implementations ignore the sdkVersion passed to PrepareAsync and load the physical CLI/checkout core assemblies. An override therefore makes the default command export the current surface while labeling it as another SDK version—the stale-signature failure this command is meant to prevent. Validate against the physical bundled SDK version (while allowing an equivalent install-sidecar identity) before exporting core.
        var packageName = CorePackageName;
        var packageVersion = ExecutionContext.IdentitySdkVersion;
  • Files reviewed: 33/35 changed files
  • Comments generated: 2
  • Review effort level: Balanced

Comment thread src/Aspire.Hosting.RemoteHost/AssemblyLoader.cs Outdated
Comment thread src/Aspire.Hosting.RemoteHost/AtsContextFilter.cs
Adam Ratzman added 3 commits August 14, 2026 19:29
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d65b598f-72dd-4fca-bdc4-8debbbd7f821
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d65b598f-72dd-4fca-bdc4-8debbbd7f821
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d65b598f-72dd-4fca-bdc4-8debbbd7f821

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.

Review details

  • Files reviewed: 34/36 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@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.

@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.

Adam Ratzman (adamint) and others added 4 commits August 24, 2026 17:30
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 5ddc8e81-7527-446e-97c0-d7db76bd6766
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 5ddc8e81-7527-446e-97c0-d7db76bd6766
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 5ddc8e81-7527-446e-97c0-d7db76bd6766
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 5ddc8e81-7527-446e-97c0-d7db76bd6766
@adamint

Copy link
Copy Markdown
Member Author

I reran sdk export against pushed head 7a53efb5ff6ea7e2ffc00cb15db9e417e38aa054. The CLI was rebuilt from that checkout first.

Scenario Exit stdout/stderr bytes Result
Default TypeScript export 0 1,087,382/0 Valid JSON; stderr empty
Exact Redis 13.5.0 0 34,236/0 Canonical Redis JSON; stderr empty
Lowercase Redis identity 0 34,236/0 Canonical identity; byte-identical to exact casing
Default repeat 0 1,087,382/0 Byte-identical to the first run
Missing @Version 1 0/156 Expected invalid-package error
Unavailable Redis 0.0.1 6 0/4,609 Expected NU1102 restore failure
Emulated core mismatch 1 0/510 Expected CLI/core mismatch
Unsupported C# 1 0/174 Clear InvalidCommand; no exception
Four-component prerelease 6 0/4,619 Parsed and reached restore; expected NU1102
# Default export
artifacts/bin/Aspire.Cli/Debug/net10.0/aspire sdk export \
  --language typescript --non-interactive

# Exact package version
artifacts/bin/Aspire.Cli/Debug/net10.0/aspire sdk export \
  --language typescript \
  --package Aspire.Hosting.Redis@13.5.0 \
  --non-interactive

# Package IDs are case-insensitive
artifacts/bin/Aspire.Cli/Debug/net10.0/aspire sdk export \
  --language typescript \
  --package aspire.hosting.redis@13.5.0 \
  --non-interactive

# Missing version
artifacts/bin/Aspire.Cli/Debug/net10.0/aspire sdk export \
  --language typescript \
  --package Aspire.Hosting.Redis \
  --non-interactive

# Unavailable exact version
artifacts/bin/Aspire.Cli/Debug/net10.0/aspire sdk export \
  --language typescript \
  --package Aspire.Hosting.Redis@0.0.1 \
  --non-interactive

# Emulated CLI/core mismatch
ASPIRE_CLI_VERSION=0.0.1 \
  artifacts/bin/Aspire.Cli/Debug/net10.0/aspire sdk export \
  --language typescript --non-interactive

# Unsupported language
artifacts/bin/Aspire.Cli/Debug/net10.0/aspire sdk export \
  --language csharp --non-interactive

# Valid four-component prerelease syntax reaches exact restore
artifacts/bin/Aspire.Cli/Debug/net10.0/aspire sdk export \
  --language typescript \
  --package Aspire.Hosting.Redis@1.0.0.0-beta \
  --non-interactive

Successful JSON summaries:

default: schema=1 language=typescript generator=Aspire.Hosting.CodeGeneration.TypeScript@13.6.0-dev package=Aspire.Hosting@13.6.0-dev modules=1 declarations=330
redis:   schema=1 language=typescript generator=Aspire.Hosting.CodeGeneration.TypeScript@13.6.0-dev package=Aspire.Hosting.Redis@13.5.0 modules=1 declarations=80

Selected failures:

Invalid package 'Aspire.Hosting.Redis'. Expected PackageName@Version.
error NU1102: Unable to find package Aspire.Hosting.Redis with version (= 0.0.1)
This CLI reports SDK version 0.0.1, but its embedded Aspire.Hosting surface is from 13.6.0-dev.
SDK API export is not supported for C# (.NET) because it does not use a code generator.
error NU1102: Unable to find package Aspire.Hosting.Redis with version (= 1.0.0-beta)

All successful stderr streams were empty; all negative stdout streams were empty. cmp returned 0 for the repeated default exports and for exact-casing vs. lowercase Redis exports.

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.

Review details

Files not reviewed (1)
  • src/Aspire.Cli/Resources/ErrorStrings.Designer.cs: Generated file
  • Files reviewed: 49/52 changed files
  • Comments generated: 3
  • Review effort level: Balanced

Comment thread src/Aspire.Cli/Commands/Sdk/SdkExportCommand.cs Outdated
Comment thread docs/specs/cli-output-formats.md Outdated
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 5ddc8e81-7527-446e-97c0-d7db76bd6766

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.

Review details

Files not reviewed (1)
  • src/Aspire.Cli/Resources/ErrorStrings.Designer.cs: Generated file
Suppressed comments (2)

Previously missed (2) — in code that hasn't changed since the last review.

src/Aspire.Cli/Commands/Sdk/SdkExportCommand.cs:78

  • An explicitly supplied empty or whitespace-only --package value is treated as if the option were omitted, so aspire sdk export --package " " --language typescript silently exports the default Aspire.Hosting package instead of rejecting malformed input. Only null should mean that --package was not supplied; pass every non-null value through TryParsePackage.
        if (!string.IsNullOrWhiteSpace(packageArgument))

src/Aspire.Cli/Projects/DotNetBasedAppHostServerProject.cs:231

  • This repository-mode exact-package restore path is only tested by inspecting the generated XML. The added CLI E2E test exercises the prebuilt server, which has a separate restore implementation, so it would not catch this path failing under central package management (for example, NU1008/NU1010). Add a test that runs dotnet restore on the generated project against an offline package source and verifies the exact package resolves.
                otherPackages.Select(p => new XElement("PackageReference",
                    new XAttribute("Include", p.Name),
                    new XAttribute("VersionOverride", p.Version)))));
  • Files reviewed: 49/52 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 5ddc8e81-7527-446e-97c0-d7db76bd6766
@adamint

Copy link
Copy Markdown
Member Author

One more review scenario on the updated CLI:

$ aspire sdk export --language typescript --package Aspire.Hosting.CodeGeneration.TypeScript@0.0.1
❌ SDK API export can only export Aspire.Hosting.CodeGeneration.TypeScript at 13.6.0-dev when that package supplies the selected language's code generator; 0.0.1 was requested.

Exit code: 1. Standard output: empty. The command rejects this before restore/project preparation.

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.

Review details

Files not reviewed (1)
  • src/Aspire.Cli/Resources/ErrorStrings.Designer.cs: Generated file
Suppressed comments (3)

Previously missed (2) — in code that hasn't changed since the last review.

src/Aspire.Hosting.RemoteHost/CodeGeneration/CodeGeneratorResolver.cs:175

  • Exporter discovery is independent from generator discovery and silently uses the last implementation for a language. AssemblyLoader appends restored integration assemblies after configured code-generation assemblies, so a requested integration that contains an IApiReferenceExporter for TypeScript replaces the built-in exporter even if it provides no TypeScript generator. GetApiReferenceExporter then pairs the official generator with this unrelated exporter and can emit a non-canonical schema. Pair exporters with their generator provider or reject duplicate language exporters.
    tests/Aspire.Cli.EndToEnd.Tests/SdkExportTests.cs:33
  • This pattern also matches legacy symbol packages named Aspire.Hosting.Redis.<version>.symbols.nupkg; localhive.sh explicitly copies those into stable hives. If find returns one first, the extracted version becomes <version>.symbols, and the export fails for an invalid/nonexistent package instead of testing the runtime package. Exclude symbol package suffixes before -print -quit.

This issue also appears on line 79 of the same file.

tests/Aspire.Cli.EndToEnd.Tests/SdkExportTests.cs:79

  • The end-to-end test only checks the JSON envelope. If package ownership or projection regresses and Redis yields no documented items, modules is still an array and the always-present runtime declaration keeps declarations valid, so this test passes without proving that the requested package was exported. Assert at least one item owned by Aspire.Hosting.Redis (or a known Redis symbol).
  • Files reviewed: 49/52 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread src/Aspire.Cli/Commands/Sdk/SdkExportCommand.cs Outdated
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 5ddc8e81-7527-446e-97c0-d7db76bd6766
@adamint

Copy link
Copy Markdown
Member Author

The matching generator-package scenario now fails before restore/project preparation too:

$ aspire sdk export --language typescript --package Aspire.Hosting.CodeGeneration.TypeScript@13.6.0-dev
❌ SDK API export cannot export Aspire.Hosting.CodeGeneration.TypeScript because that package supplies the selected language's code generator instead of an integration API surface.

Exit code: 1. Standard output: empty. Before 93cc33e0b, the matching locally packed version reached the real RPC and exited 6 because none of the package assemblies reached the scanned API surface.

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.

Review details

Files not reviewed (1)
  • src/Aspire.Cli/Resources/ErrorStrings.Designer.cs: Generated file
Suppressed comments (1)

src/Aspire.Hosting.RemoteHost/AtsContextFilter.cs:51

  • GetKnownAssemblyNames is not proof that a loaded assembly has this name: it unconditionally includes ATS ID prefixes, but instance capability IDs may be namespace-qualified while IsCapabilityOwnedBySelectedAssembly prefers the method/property's actual CLR assembly. A request matching such a namespace can therefore resolve here, then filter to no owned symbols and return a successful empty export. Build candidates with the same ownership precedence as the filter (only use the ID prefix when CLR ownership is unavailable), or reject an export whose resolved ownership scope produces no package-owned surface.
  • Files reviewed: 49/52 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@mitchdenny Mitch Denny (mitchdenny) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

No high-confidence issues found.

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ CI Failure Analysis: Possible Flaky Test(s)

The CI build failed due to test failure(s) that appear unrelated to the PR changes. These may be flaky tests.

Suspected flaky test(s):

  • Aspire.Cli.Tests.Projects.AppHostServerSessionTests.Start_StopRequested_GracefulIgnored_ExpireEscalatesToTreeKill in job Tests / Cli / Cli (windows-latest)
    • Error: System.TimeoutException : The operation has timed out.
    • Stack Trace (first frames):
      at Aspire.Cli.Tests.Projects.AppHostServerSessionTests.Start_StopRequested_GracefulIgnored_ExpireEscalatesToTreeKill() in D:\a\aspire\aspire\tests\Aspire.Cli.Tests\Projects\AppHostServerSessionTests.cs:line 294
         at Aspire.Cli.Tests.Projects.AppHostServerSessionTests.Start_StopRequested_GracefulIgnored_ExpireEscalatesToTreeKill() in D:\a\aspire\aspire\tests\Aspire.Cli.Tests\Projects\AppHostServerSessionTests.cs:line 300
      --- End of stack trace from previous location ---
      
    • Why likely flaky: Timing-sensitive graceful-shutdown-to-tree-kill escalation test timing out on Windows CI runner; part of a known family of similar timeout-prone tests (ProcessGuestLauncherTests escalation tests) with several tracked flaky issues. PR changes do not touch this teardown/escalation code path.

Suggested actions:

  • Re-run the failed CI jobs to confirm if the failure is intermittent
  • If the test continues to fail, consider quarantining it using /quarantine-test <test name> <issue URL>
  • Search existing issues to see if this test is already known to be flaky

You can re-run the failed jobs from the workflow run page.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants