Skip to content

fix(cli): honor --source for TypeScript empty AppHost restore#17166

Open
radical wants to merge 3 commits into
microsoft:mainfrom
radical:radical/issue-17159-aspire-empty-typescript-hive
Open

fix(cli): honor --source for TypeScript empty AppHost restore#17166
radical wants to merge 3 commits into
microsoft:mainfrom
radical:radical/issue-17159-aspire-empty-typescript-hive

Conversation

@radical
Copy link
Copy Markdown
Member

@radical radical commented May 16, 2026

Description

What broke

Creating an empty TypeScript AppHost from PR hive packages failed even when the caller explicitly pointed the CLI at that hive:

aspire new aspire-empty --language typescript --source <pr-hive>/packages --version <pr-version>

Instead of creating the project, the command failed during TypeScript code generation with a TypeLoadException for Aspire.TypeSystem.AtsJsonCodeWriter.

Full error from the failing run
RPC server exception:
System.TypeLoadException: Could not load type 'Aspire.TypeSystem.AtsJsonCodeWriter' from assembly 'Aspire.TypeSystem, Version=42.42.42.42, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'.
      at Aspire.Hosting.CodeGeneration.TypeScript.AtsTypeScriptCodeGenerator.RenderTypeScriptExportedValue(JsonNode value, AtsTypeRef typeRef, IReadOnlyDictionary`2 dtoTypesById)
      at Aspire.Hosting.CodeGeneration.TypeScript.AtsTypeScriptCodeGenerator.WriteTypeScriptExportedValueChildren(ExportedValueTreeNode node, IReadOnlyDictionary`2 dtoTypesById, Int32 indentLevel) in /_/src/Aspire.Hosting.CodeGeneration.TypeScript/AtsTypeScriptCodeGenerator.cs:line 907
      at Aspire.Hosting.CodeGeneration.TypeScript.AtsTypeScriptCodeGenerator.GenerateExportedValues(IReadOnlyList`1 exportedValues, IReadOnlyDictionary`2 dtoTypesById) in /_/src/Aspire.Hosting.CodeGeneration.TypeScript/AtsTypeScriptCodeGenerator.cs:line 885
      at Aspire.Hosting.CodeGeneration.TypeScript.AtsTypeScriptCodeGenerator.GenerateAspireSdk(AtsContext context) in /_/src/Aspire.Hosting.CodeGeneration.TypeScript/AtsTypeScriptCodeGenerator.cs:line 666
      at Aspire.Hosting.CodeGeneration.TypeScript.AtsTypeScriptCodeGenerator.GenerateDistributedApplication(AtsContext context) in /_/src/Aspire.Hosting.CodeGeneration.TypeScript/AtsTypeScriptCodeGenerator.cs:line 545
      at Aspire.Hosting.RemoteHost.CodeGeneration.CodeGenerationService.GenerateCode(String language, String assemblyName) in /Users/runner/work/aspire/aspire/src/Aspire.Hosting.RemoteHost/CodeGeneration/CodeGenerationService.cs:line 194
      at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
      at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)

❌ An unexpected error occurred: Could not load type 'Aspire.TypeSystem.AtsJsonCodeWriter' from assembly
'Aspire.TypeSystem, Version=42.42.42.42, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'.

Why it broke

aspire new parsed --source, but the empty guest-language scaffolding path dropped it before restoring the prebuilt AppHost packages.

That meant the restore never searched the requested PR hive. NuGet then treated the requested PR version as a minimum version and floated to a different preview package set. The TypeScript generator from that stale package set was loaded next to the PR CLI's Aspire.TypeSystem, and the binary mismatch surfaced as the TypeLoadException.

Fix

This change threads the explicit source through the full guest-language scaffold path:

TemplateInputs.Source → ScaffoldContext → IAppHostServerProject.PrepareAsync → PrebuiltAppHostServer restore

PrebuiltAppHostServer now uses that source for both package-only restore and project-reference closure restore. When an explicit source is supplied, it also restores exact package versions so a missing PR package fails clearly instead of silently resolving another Aspire prerelease.

This intentionally does not change default route-local hive discovery for PR/localhive installs; that belongs with the acquisition coherence work.

Fixes #17159

Validation

  • dotnet test --project tests/Aspire.Cli.Tests/Aspire.Cli.Tests.csproj --no-launch-profile -- --filter-class "*.Projects.PrebuiltAppHostServerTests" --filter-class "*.Scaffolding.ChannelReseedTests" --filter-not-trait "quarantined=true" --filter-not-trait "outerloop=true"
  • dotnet test --project tests/Aspire.Cli.Tests/Aspire.Cli.Tests.csproj --no-launch-profile -- --filter-not-trait "quarantined=true" --filter-not-trait "outerloop=true"

Checklist

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
    • No
  • Did you add public API?
    • Yes
      • If yes, did you have an API Review for it?
        • Yes
        • No
      • Did you add <remarks /> and <code /> elements on your triple slash comments?
        • Yes
        • No
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
      • If yes, have you done a threat model and had a security review?
        • Yes
        • No
    • No

aspire new aspire-empty --language typescript --source <pr-hive>
--version <pr-version> parsed --source, but the empty AppHost
TypeScript scaffolding path dropped it before the prebuilt AppHost
restored Aspire.Hosting and TypeScript code-generation packages. The
bundled restore then searched channel sources, missed the requested PR
hive packages, and NuGet floated to a stale preview package set, which
later failed with TypeLoadException when the generator loaded against
the PR CLI's Aspire.TypeSystem.

Flow TemplateInputs.Source into ScaffoldContext, pass it to
IAppHostServerProject.PrepareAsync, and have PrebuiltAppHostServer add
that source to package and closure restores. When a source override is
present, use exact version ranges so restore fails rather than silently
resolving a different Aspire prerelease.

Fixes microsoft#17159

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 16, 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 -- 17166

Or

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

@radical
Copy link
Copy Markdown
Member Author

radical commented May 16, 2026

There is a second part of the fix that improves this when --source is not passed. And it will be included in #17105 .

Remove whitespace-only changes that are unrelated to the source restore fix, keeping the PR focused on the explicit package source propagation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@radical
Copy link
Copy Markdown
Member Author

radical commented May 16, 2026

PR Testing Report

PR Information

CLI Version Verification

  • Expected Commit: a49d604
  • Installed Version: 13.4.0-pr.17166.ga49d604d
  • Status: ✅ Verified; installed version contains PR commit suffix ga49d604d.

Changes Analyzed

Files Changed

  • src/Aspire.Cli/Projects/DotNetBasedAppHostServerProject.cs
  • src/Aspire.Cli/Projects/IAppHostServerProject.cs
  • src/Aspire.Cli/Projects/PrebuiltAppHostServer.cs
  • src/Aspire.Cli/Scaffolding/IScaffoldingService.cs
  • src/Aspire.Cli/Scaffolding/ScaffoldingService.cs
  • src/Aspire.Cli/Templating/CliTemplateFactory.EmptyTemplate.cs
  • tests/Aspire.Cli.Tests/Projects/AppHostServerSessionTests.cs
  • tests/Aspire.Cli.Tests/Projects/PrebuiltAppHostServerTests.cs
  • tests/Aspire.Cli.Tests/Scaffolding/ChannelReseedTests.cs
  • tests/Aspire.Cli.Tests/TestServices/FakeFailingAppHostServerProject.cs

Change Categories

  • CLI changes detected — guest empty-template scaffolding and prebuilt AppHost package restore.
  • Hosting integration changes
  • Dashboard changes
  • Client/Component changes
  • Template package changes
  • Test changes detected

Test Scenarios Executed

Scenario 1: PR CLI install + version match

Objective: Install the PR dogfood CLI and verify it matches the PR head commit.
Status: ✅ Passed

Evidence:

  • Install log: install-output.txt
  • Version output: version.txt

Scenario 2: TypeScript empty template with explicit PR hive source

Objective: Verify aspire new aspire-empty --language typescript succeeds when --source points to PR packages outside the standard hive discovery location.
Status: ✅ Passed

Command shape:

aspire new aspire-empty --language typescript --name PrSourceTs3 --output <temp>/PrSourceTs3 --version 13.4.0-pr.17166.ga49d604d --localhost-tld false --suppress-agent-init --non-interactive --source <explicit-pr-packages>

Evidence:

  • Log: scenarios/scenario-1-typescript-explicit-source-short.log
  • Generated file list: scenarios/scenario-1-typescript-explicit-source-short.files.txt

Observation: The project was created successfully and TypeScript dependencies were installed. This scenario used a short temp HOME with no ~/.aspire/hives directory, so success depended on the explicit --source path.

Scenario 3: TypeScript empty template without explicit source

Objective: Confirm the test setup still exposes the source-dependent failure when the explicit source is omitted.
Status: ✅ Expected failure reproduced

Command shape:

aspire new aspire-empty --language typescript --name NoSourceTs3 --output <temp>/NoSourceTs3 --version 13.4.0-pr.17166.ga49d604d --localhost-tld false --suppress-agent-init --non-interactive

Evidence:

  • Log: scenarios/scenario-4-typescript-no-source-short.log

Observed error:

Could not load type 'Aspire.TypeSystem.AtsJsonCodeWriter' from assembly 'Aspire.TypeSystem, Version=42.42.42.42, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'.

Observation: This reproduces the issue signature when the explicit package source is not available to the restore path.

Non-applicable attempted scenarios

Python and Go were initially attempted for aspire-empty, but the PR CLI reports that aspire-empty does not support those languages. They were not counted as PR failures.

Summary

Scenario Status Notes
PR CLI install/version ✅ Passed Installed 13.4.0-pr.17166.ga49d604d.
TypeScript empty with --source ✅ Passed Explicit out-of-hive PR package source succeeded.
TypeScript empty without --source ✅ Expected failure Reproduced original AtsJsonCodeWriter TypeLoadException.

Overall Result

✅ PR VERIFIED

The downloaded PR CLI honors --source for the reported TypeScript empty AppHost restore path. The contrast run without --source still hits the original failure shape, which confirms the passing scenario is exercising the intended fix.

@radical radical marked this pull request as ready for review May 16, 2026 06:02
Copilot AI review requested due to automatic review settings May 16, 2026 06:02
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

This PR fixes aspire new aspire-empty --language typescript failures when callers supply --source (e.g., a PR hive) by threading the source override through the empty-template scaffolding path into AppHost server preparation, and ensuring the prebuilt AppHost restore uses that override (including exact-version restores when an explicit source is provided) to avoid mixed-binary package sets.

Changes:

  • Thread TemplateInputs.Source through ScaffoldContext into IAppHostServerProject.PrepareAsync(...) as packageSourceOverride.
  • Update PrebuiltAppHostServer restore logic to include the explicit source for both package-only restore and project-closure restore, and restore exact versions when an explicit source is provided.
  • Add/adjust tests to validate the override is passed through and that restore is invoked with the expected sources/version constraints.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated no comments.

Show a summary per file
File Description
tests/Aspire.Cli.Tests/TestServices/FakeFailingAppHostServerProject.cs Updates fake to match new PrepareAsync signature with source override.
tests/Aspire.Cli.Tests/Scaffolding/ChannelReseedTests.cs Adds coverage that scaffolding passes PackageSourceOverride into PrepareAsync.
tests/Aspire.Cli.Tests/Projects/PrebuiltAppHostServerTests.cs Adds coverage that bundled restore uses --source override and exact-version package args.
tests/Aspire.Cli.Tests/Projects/AppHostServerSessionTests.cs Updates test fake to match the updated PrepareAsync signature.
src/Aspire.Cli/Templating/CliTemplateFactory.EmptyTemplate.cs Passes inputs.Source into ScaffoldContext.PackageSourceOverride for empty templates.
src/Aspire.Cli/Scaffolding/ScaffoldingService.cs Forwards PackageSourceOverride into IAppHostServerProject.PrepareAsync.
src/Aspire.Cli/Scaffolding/IScaffoldingService.cs Extends ScaffoldContext with PackageSourceOverride.
src/Aspire.Cli/Projects/PrebuiltAppHostServer.cs Applies source override to restore paths; enforces exact version restore when override is set.
src/Aspire.Cli/Projects/IAppHostServerProject.cs Extends PrepareAsync contract to accept an optional packageSourceOverride.
src/Aspire.Cli/Projects/DotNetBasedAppHostServerProject.cs Updates signature to satisfy the extended IAppHostServerProject contract.

When an explicit package source is passed to guest AppHost restore, keep the exact-version pinning scoped to Aspire packages because that is the source mapping being overridden. Non-Aspire integration packages should retain normal NuGet minimum-version restore semantics.

Also apply the temporary NuGet.config to the project-reference closure restore path instead of only adding sources to the synthetic project. That keeps package source mapping and channel-specific restore settings active when package and project integrations are restored together.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@radical radical added this to the 13.4 milestone May 16, 2026
/// </summary>
internal sealed class PrebuiltAppHostServer : IAppHostServerProject
{
private const string NuGetOrgSource = "https://api.nuget.org/v3/index.json";
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.

why?

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.

aspire new aspire-empty --language typescript fails with TypeLoadException when using PR hive packages

3 participants