Export canonical TypeScript API data from the CLI - #19032
Export canonical TypeScript API data from the CLI#19032Adam Ratzman (adamint) merged 77 commits into
Conversation
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
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 19032Or
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 19032" |
There was a problem hiding this comment.
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
--sourcecontains only the requested release), so the result is not canonical for the requestedName@Version. Restore the generator atpackageVersion, 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.Consolefrom its default stdout route. As a result, preparation diagnostics and the--outputsuccess 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@Versionis never restored here. Both scanner implementations ignore thesdkVersionargument, so a CLI running a different version scans its bundled/repositoryAspire.Hostingassembly and then labels that surface with the requested version. The command therefore cannot honor its exactName@Versioncontract 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
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
There was a problem hiding this comment.
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 callsGetPublicOptionsParameterNameto avoid collisions. A capability that already has a parameter namedoptionsis 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
parametersarray is still built from raw ATS parameters instead of the resolved public signature. For example, the checked-in export declareswithOptionalString(options?: WithOptionalStringOptions)but reportsvalueandenabledas 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
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
There was a problem hiding this comment.
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 exportingAspire.Hosting.Redis@13.5.0applies 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 officialAspire.Hosting.*versions that differ fromIdentitySdkVersion, as is already done for core.
else
{
integrations.Add(reference);
}
src/Aspire.Hosting.CodeGeneration.TypeScript/TypeScriptApiProjector.cs:276
ResolveMethodSignaturealways names the synthesized optional-parameter bagoptions, but the source emitter callsGetPublicOptionsParameterNameto avoid collisions. Real generated APIs such asaddCSharpAppandpromptProgresstherefore useoptionsBag, while this export reportsoptions, 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
parametersarray is built from the original ATS parameters rather than the public signature. For example, the snapshot declaresaddTestRedis(name, options?: AddTestRedisOptions)but serializesnameandport, with nooptionsparameter. 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
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
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
There was a problem hiding this comment.
Review details
Suppressed comments (1)
src/Aspire.Cli/Commands/Sdk/SdkExportCommand.cs:72
IdentitySdkVersioncan be supplied byASPIRE_CLI_VERSION, but both scanner project implementations ignore thesdkVersionpassed toPrepareAsyncand 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
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
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
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
|
I reran
# 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-interactiveSuccessful JSON summaries: Selected failures: All successful stderr streams were empty; all negative stdout streams were empty. |
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5ddc8e81-7527-446e-97c0-d7db76bd6766
There was a problem hiding this comment.
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
--packagevalue is treated as if the option were omitted, soaspire sdk export --package " " --language typescriptsilently exports the defaultAspire.Hostingpackage instead of rejecting malformed input. Onlynullshould mean that--packagewas not supplied; pass every non-null value throughTryParsePackage.
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 restoreon 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
|
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: |
There was a problem hiding this comment.
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.
AssemblyLoaderappends restored integration assemblies after configured code-generation assemblies, so a requested integration that contains anIApiReferenceExporterforTypeScriptreplaces the built-in exporter even if it provides no TypeScript generator.GetApiReferenceExporterthen 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.shexplicitly copies those into stable hives. Iffindreturns 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,
modulesis still an array and the always-present runtime declaration keepsdeclarationsvalid, so this test passes without proving that the requested package was exported. Assert at least one item owned byAspire.Hosting.Redis(or a known Redis symbol).
- Files reviewed: 49/52 changed files
- Comments generated: 1
- Review effort level: Balanced
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5ddc8e81-7527-446e-97c0-d7db76bd6766
|
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: |
There was a problem hiding this comment.
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
GetKnownAssemblyNamesis not proof that a loaded assembly has this name: it unconditionally includes ATS ID prefixes, but instance capability IDs may be namespace-qualified whileIsCapabilityOwnedBySelectedAssemblyprefers 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
Mitch Denny (mitchdenny)
left a comment
There was a problem hiding this comment.
No high-confidence issues found.
|
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):
Suggested actions:
You can re-run the failed jobs from the workflow run page. |
Description
Adds
aspire sdk export, which emits a deterministic TypeScript API reference as JSON. The command exportsAspire.Hostingat the running CLI SDK version by default, or an exactPackageName@Versionsupplied 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:
IApiReferenceExporter.lib,ref, andruntimes/<rid>/libassets.The reduction intentionally excludes source selection, output-file handling, checkout provenance/substitution machinery, compatibility shims, and normal-build manifest expansion.
Contributes to #17608.
Validation
/etc/resolv.confis denied.Checklist
<remarks />and<code />elements on your triple slash comments?