Skip to content

Comments

[release/13.2] Add WinGet publishing pipeline for Aspire CLI#14502

Draft
radical wants to merge 22 commits intodotnet:release/13.2from
radical:winget
Draft

[release/13.2] Add WinGet publishing pipeline for Aspire CLI#14502
radical wants to merge 22 commits intodotnet:release/13.2from
radical:winget

Conversation

@radical
Copy link
Member

@radical radical commented Feb 14, 2026

Add WinGet publishing pipeline for Aspire CLI

Summary

Adds automated WinGet manifest generation, validation, and publishing to the internal Azure DevOps pipeline so that the Aspire CLI (aspire) can be installed via winget install Microsoft.Aspire (release) or winget install Microsoft.Aspire.Prerelease (preview).

Motivation

Today users install the Aspire CLI through acquisition scripts (aspire.dev/get-aspire-cli.sh / aspire.dev/get-aspire-cli.ps1). Adding WinGet support gives Windows developers a familiar, first-class installation channel (winget install/winget upgrade) with automatic PATH management and upgrade semantics.

What's included

Pipeline changes (eng/pipelines/)

  • New publish_winget stage in azure-pipelines.yml – runs after the build stage on non-PR builds from main, release/*, and internal/release/* branches.
  • New publishReleaseToWinGet parameter (default false) – gates WinGet publishing on release branches so it only happens when explicitly opted in. Main branch builds publish automatically.
  • Branch-aware package identity – release branches use Microsoft.Aspire; all other branches use Microsoft.Aspire.Prerelease.
  • New reusable template eng/pipelines/templates/winget.yml that:
    1. Installs wingetcreate and the winget CLI on the build agent.
    2. Generates WinGet manifests from templates.
    3. Validates manifests with winget validate.
    4. Smoke-tests install/uninstall from the local manifest (with cleanup on failure).
    5. Publishes manifest artifacts.
    6. Submits a PR to microsoft/winget-pkgs via wingetcreate update --submit (gated behind publishToWinGet).

Manifest generation (eng/winget/)

  • generate-manifests.ps1 – PowerShell script that:

    • Takes a version, template directory, and optional RID list (win-x64,win-arm64 by default).
    • Derives installer URLs from the ci.dot.net publishing pattern.
    • Downloads each zip to compute SHA256 hashes.
    • Optionally validates URL accessibility before downloading (-ValidateUrls).
    • Renders three WinGet manifest files (version, locale, installer) from templates.
  • Two sets of YAML templates:

    Directory Package ID Use case
    eng/winget/microsoft.aspire/ Microsoft.Aspire Stable releases (release/*)
    eng/winget/microsoft.aspire.prerelease/ Microsoft.Aspire.Prerelease Preview builds (main)

    Each set contains:

    • Aspire.yaml.template – version manifest
    • Aspire.locale.en-US.yaml.template – locale/metadata manifest (description, tags, license, etc.)
    • Aspire.installer.yaml.template – installer manifest (zip → portable with aspire.exe)

Testing

  • Manifests are validated with winget validate --manifest on every qualifying build.
  • A full install → verify → uninstall cycle runs on the build agent before any submission.
  • Manifest artifacts are published for manual inspection on every run (including dry-runs where publishToWinGet is false).

@github-actions
Copy link
Contributor

github-actions bot commented Feb 14, 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/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 14502

Or

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

radical and others added 2 commits February 17, 2026 15:28
Add WinGet manifest generation, validation, and publishing to the
internal pipeline. Includes install/uninstall testing with proper
cleanup on failure, and fixes _IsPublishBranch string comparison
for template expression evaluation.

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

Add publishReleaseToWinGet pipeline parameter (default: false) so that
release branch builds only publish to WinGet when explicitly opted in.
Main branch non-PR builds continue to publish automatically.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
radical and others added 5 commits February 17, 2026 17:16
Compute the versioned manifest path once in the generate step and emit it
as the $(VersionedManifestPath) pipeline variable instead of repeating the
same directory search in the validate and test install/uninstall steps.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the duplicated branch-matching expressions with a reference to
_IsPublishBranch, keeping the inner main check to preserve the "main
always publishes, release branches require opt-in" semantics.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…only

- azure-pipelines.yml: Always publish to Microsoft.Aspire.Prerelease from CI;
  release winget publishing is now handled by release-publish-nuget.yml
- release-publish-nuget.yml: Add PublishWinGet stage that publishes to
  Microsoft.Aspire winget package after NuGet publish completes
  - SkipWinGetPublish parameter for idempotent re-runs
  - DryRun support via publishToWinGet flag
  - Version from ReleaseVersion parameter
  - Aspire-Secrets variable group for winget PAT token

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The release pipeline pool may not have PSGallery registered by default.
Add diagnostic output and auto-register PSGallery before installing
the Microsoft.WinGet.Client module.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Register-PSRepository -Default fails when the NuGet package provider
isn't installed. Bootstrap it with Install-PackageProvider before
attempting to register PSGallery.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI and others added 12 commits February 19, 2026 15:49
…otnet#14434)

* Add Azure portal link for Resource Group in pipeline summary

When printing the Resource Group in the pipeline summary of `aspire deploy`,
include a clickable link to the Azure portal resource group page.

The link uses the format:
https://portal.azure.com/#@{tenantId}/resource/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/overview

Changes:
- AzureEnvironmentResource.AddToPipelineSummary: construct markdown link for resource group
- ConsoleActivityLogger.FormatPipelineSummaryKvp: convert markdown to Spectre markup for clickable links
- Add ConsoleActivityLoggerTests for the new markdown rendering behavior

Co-authored-by: eerhardt <8291187+eerhardt@users.noreply.github.com>

* Clean up the code

* Fix tests

* More test fixups

* Refactor code

* Update src/Aspire.Cli/Utils/MarkdownToSpectreConverter.cs

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

* Add test for color-enabled non-interactive rendering path

Co-authored-by: eerhardt <8291187+eerhardt@users.noreply.github.com>

* fix test

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: eerhardt <8291187+eerhardt@users.noreply.github.com>
Co-authored-by: Eric Erhardt <eric.erhardt@microsoft.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…ondition (dotnet#14494)

* Fix tools/list infinite loop by removing notification from ListTools handler and adding change detection

The MCP server was entering an infinite tools/list loop because:
1. HandleListToolsAsync sent tools/list_changed notification after refreshing
2. The client responded with another tools/list request
3. This created a feedback loop: list → changed → list → changed

Fix:
- Remove SendToolsListChangedNotificationAsync from HandleListToolsAsync
  (the client already gets the fresh list since it requested it)
- Add change detection to RefreshResourceToolMapAsync (returns bool Changed)
- Only send tools/list_changed in HandleCallToolAsync when tools actually changed
- RefreshToolsTool always sends notification (explicit user action)

Co-authored-by: maddymontaquila <12660687+maddymontaquila@users.noreply.github.com>

* Use HashSet.SetEquals for more efficient tool change detection

Co-authored-by: maddymontaquila <12660687+maddymontaquila@users.noreply.github.com>

* Address review feedback: use no-allocation key comparison and bounded channel wait in test

- Replace HashSet.SetEquals with count check + iterate keys + ContainsKey to
  avoid allocation under lock in McpResourceToolRefreshService.
- Replace Task.Delay(200) in test with Channel.ReadAsync + CancellationTokenSource
  timeout for more deterministic negative assertion.

Co-authored-by: sebastienros <1165805+sebastienros@users.noreply.github.com>

* Check for both new and deleted tools in change detection

The previous change detection only iterated old→new keys, missing the
case where tools are swapped (same count but different keys). Now also
checks new→old to detect newly added tools.

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

* Clean up comments in AgentMcpCommandTests

Removed comments about using TryRead for notifications.

* Fix resource name mapping

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: maddymontaquila <12660687+maddymontaquila@users.noreply.github.com>
Co-authored-by: sebastienros <1165805+sebastienros@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: sebastienros <sebastienros@users.noreply.github.com>
* Fix permission denied error in Azure Pipelines

When we generate temporary dockerfiles, we are generating them directly under the TEMP directory. This can cause issues in some environments because docker build will walk all the files and folders next to the dockerfile as context to the build. For example, in AzDO pipelines, we can get an error like "ERROR: error from sender: lstat /tmp/.mount_azsec-KdAJKO: permission denied".

To fix this, we generate the Dockerfile in a subdirectory of TEMP, so it is the only file passed as context to docker build.

Fix dotnet#14523
…t#14442)

* Add WithCompactResourceNaming() to fix storage name collisions

Fixes dotnet#14427. When Azure Container App environment names are long,
the uniqueString suffix gets truncated in storage account names,
causing naming collisions across deployments.

WithCompactResourceNaming() is an opt-in method that shortens storage
account and managed storage names to preserve the full 13-char
uniqueString while keeping names within Azure's length limits.

- Storage accounts: take('{prefix}sv{resourceToken}', 24)
- Managed storage: take('{name}-{volume}-{resourceToken}', 32)
- File shares: take('{name}-{volume}', 60)

Includes unit tests with snapshot verification and E2E deployment
tests covering both the fix and upgrade safety scenarios.

* Fix upgrade test: handle version prompt and backup/restore dev CLI

- Add version selection prompt handling for 'aspire add' (same as passing test)
- Back up dev CLI before GA install, restore after GA phase
- Update package to dev version after CLI restoration
- Set channel back to local after restore

* Remove the manifest from verify tests. It is not necessary.

* Remove unnecessary suppression

* Fix upgrade test: use 'aspire update' to actually upgrade project packages

The upgrade test was only swapping the CLI binary but the apphost.cs still
had #:package directives pointing to GA 13.1.0 packages. The deployment
logic comes from the NuGet packages, not the CLI, so the test was actually
redeploying with the old GA naming code both times.

Now uses 'aspire update --channel local' to update the #:package directives
in apphost.cs from GA → dev version, ensuring the dev naming code is
exercised during the second deployment.

* Fix upgrade test: handle 'Perform updates?' confirmation prompt

aspire update shows a y/n confirmation before applying package updates.
The test was waiting for 'Update successful' but the command was stuck
at the confirmation prompt.

* Fix upgrade test: handle NuGet.config directory prompt from aspire update

aspire update shows two prompts when switching channels:
1. 'Perform updates? [y/n]' - package confirmation
2. 'Which directory for NuGet.config file?' - NuGet config placement
Both need Enter to accept defaults.

* Fix upgrade test: use timed Enter presses for aspire update prompts

aspire update has multiple sequential prompts (confirm, NuGet.config dir,
NuGet.config apply, potential CLI self-update). Use Wait+Enter pattern
to accept all defaults without needing to track each prompt individually.

* Fix upgrade test: use explicit WaitUntil for each aspire update prompt

Timed Enter presses were unreliable — if prompts appeared at different
speeds, extra Enters would leak to the shell and corrupt subsequent
commands. Now explicitly waits for each of the 3 prompts:
1. 'Perform updates? [y/n]'
2. 'Which directory for NuGet.config file?'
3. 'Apply these changes to NuGet.config? [y/n]'

* Increase deploy WaitForSuccessPrompt timeout from 2 to 5 minutes

---------

Co-authored-by: Mitch Denny <mitch@mitchdeny.com>
Co-authored-by: Eric Erhardt <eric.erhardt@microsoft.com>
…resh loop (dotnet#14539)

TryGetResourceToolMap always returned false because it compared
_selectedAppHostPath against _auxiliaryBackchannelMonitor.SelectedAppHostPath,
which is only set by explicit select_apphost calls (usually null). After
RefreshResourceToolMapAsync sets _selectedAppHostPath to the connection's actual
path, the comparison null != "/path/to/AppHost" always failed, so every
tools/list call triggered a full refresh instead of using the cached map.

Fix: Add ResolvedAppHostPath property to IAuxiliaryBackchannelMonitor that
returns SelectedConnection?.AppHostInfo?.AppHostPath, and compare against that.
Rename field to _lastRefreshedAppHostPath for clarity.

Fixes dotnet#14538

Co-authored-by: Mitch Denny <mitch@mitchdeny.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…tnet#14452)

* Add AzureServiceTags class with common Azure service tags and tests

Co-authored-by: eerhardt <8291187+eerhardt@users.noreply.github.com>

* Use the new constants in more places.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: eerhardt <8291187+eerhardt@users.noreply.github.com>
Co-authored-by: Eric Erhardt <eric.erhardt@microsoft.com>
The winget install step was failing on the NuGet pipeline. Windows
hosted agents already have winget pre-installed, so replace the
complex installation fallback with a simple version check.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add 'Install winget' step to run-tests.yml and tests-runner.yml
workflows to ensure winget is available on all Windows CI jobs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Instead of expecting winget to be pre-installed on the agent,
run eng/install-winget.ps1 when winget is not found.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
radical and others added 3 commits February 20, 2026 17:41
Replace standalone install-winget.ps1 with inline steps that use
NuGetAuthenticate and the ps-gallery-for-aspire Azure Artifacts
feed to install Microsoft.WinGet.Client, then Repair-WinGetPackageManager.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The PowerShell@2 task defaults to Windows PowerShell 5.1 which
rejects the Azure DevOps NuGet v2 feed URL in Register-PSRepository.
Adding pwsh: true switches to PowerShell 7 which handles the URL
correctly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Register-PSRepository validates the URI with an unauthenticated HTTP
probe, which fails on authenticated Azure DevOps feeds. Switch to
PSResourceGet (Register-PSResourceRepository + Install-PSResource),
which is built into PowerShell 7.4+ and uses the NuGet v3 endpoint
without the problematic validation.

Also add diagnostic logging for PS version, PSResourceGet availability,
and credential provider paths to aid debugging if issues recur.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

5 participants