Skip to content

Add tests and plumb .aspire-update.json into distribution archives#15946

Draft
radical wants to merge 5 commits intomicrosoft:mainfrom
radical:aspire-update-json
Draft

Add tests and plumb .aspire-update.json into distribution archives#15946
radical wants to merge 5 commits intomicrosoft:mainfrom
radical:aspire-update-json

Conversation

@radical
Copy link
Copy Markdown
Member

@radical radical commented Apr 7, 2026

Summary

Two related changes to improve CLI distribution channel handling:

1. .aspire-update.json self-update opt-out mechanism (#15932)

Plumbs the .aspire-update.json file into the WinGet and Homebrew distribution pipelines so that package-manager-installed CLIs correctly suppress self-update notifications and show the right update instructions.

2. Embed distribution channel in CLI binary (#15947)

Replaces the mutable "channel" property in aspire.config.json with a compile-time CliChannel assembly metadata attribute baked into the CLI binary. Stable-channel binaries inherently know they are stable without relying on a mutable config file.

Changes

.aspire-update.json plumbing

  • tools/CreateLayout/Program.cs: Added --update-instructions option and WriteAspireUpdateJson() method
  • eng/Bundle.proj: Forward UpdateInstructions property to CreateLayout
  • eng/clipack/Common.projitems: Added CliUpdateInstructions per-RID and _WriteAspireUpdateJson target
  • eng/homebrew/aspire.rb.template: Added postflight block to write .aspire-update.json
  • eng/scripts/get-aspire-cli.sh / get-aspire-cli.ps1: Delete .aspire-update.json after extraction

CliChannel embedding

  • src/Aspire.Cli/Aspire.Cli.csproj: Added CliChannel MSBuild property + AssemblyMetadata
  • src/Aspire.Cli/Packaging/PackagingService.cs: Added GetEmbeddedChannel() to read channel at runtime
  • src/Aspire.Cli/Commands/UpdateCommand.cs: Uses embedded channel as self-update default
  • src/Aspire.Cli/Configuration/AspireConfigFile.cs: Removed Channel property; added JsonExtensionData for backward compat
  • All channel consumers updated to fall back to embedded channel
  • Build infrastructure: Common.projitems defaults CliChannel=stable, Bundle.proj forwards it, localhive scripts use CliChannel=pr

Channel resolution priority (highest to lowest)

  1. --channel CLI flag
  2. Local .aspire/settings.json channel (polyglot backward compat)
  3. Global aspire.config.json channel (backward compat via ExtensionData)
  4. Embedded CliChannel assembly metadata
  5. Implicit channel (NuGet feed defaults)

Tests

  • 3 new tests for self-update disabled behavior (dotnet-tool, WinGet, Homebrew)
  • 1 new test for notification suppression when self-update disabled
  • Updated existing tests that referenced removed Channel property

Backward compatibility

  • Existing aspire.config.json files with "channel" are preserved via JsonExtensionData on round-trip
  • Legacy .aspire/settings.json channel is still read for polyglot projects
  • Existing stable installs continue to work — the embedded channel serves as a default, not an override

Fixes #15932
Fixes #15947

radical and others added 3 commits April 7, 2026 16:01
Introduce IInstallationDetector service that detects how the CLI was
installed and whether self-update is available. When a .aspire-update.json
file is present next to the CLI binary with selfUpdateDisabled: true,
all three self-update paths are guarded:

- aspire update --self: shows disabled message and update instructions
- Post-project-update prompt: shows instructions instead of prompting
- No-project-found fallback: shows instructions instead of prompting

The CliUpdateNotifier also suppresses background update notifications
when self-update is disabled (e.g., WinGet/Homebrew installs).

Key design decisions:
- Symlink resolution before config lookup (critical for Homebrew)
- Fail-closed on malformed/unreadable JSON (safer for package managers)
- Cached result (computed once per process lifetime)
- DotNet tool detection takes priority over config file
- Fixes pre-existing bug where post-project-update prompt didn't check
  IsRunningAsDotNetTool

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Tests:
- UpdateCommand --self when SelfUpdateDisabled shows disabled message
- UpdateCommand post-project-update skips confirm prompt when disabled
- UpdateCommand no-project-found shows instructions without prompt
- CliUpdateNotifier suppresses notification when SelfUpdateDisabled=true

Distribution plumbing:
- Common.projitems: Write .aspire-update.json per-RID (win→winget, osx→brew)
- Bundle.proj: Forward UpdateInstructions property to CreateLayout
- CreateLayout: Add --update-instructions option for bundle layout
- Homebrew cask template: postflight writes .aspire-update.json
- Install scripts (sh/ps1): Delete .aspire-update.json after extraction
  so script-installed users retain self-update capability

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

github-actions bot commented Apr 7, 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 -- 15946

Or

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

radical and others added 2 commits April 7, 2026 22:11
…oft#15947)

Replace the mutable 'channel' property in aspire.config.json with a
compile-time CliChannel assembly metadata attribute baked into the CLI
binary. This ensures stable-channel binaries inherently know they are
stable without relying on a mutable config file that can get out of sync.

Changes:
- Add CliChannel MSBuild property and AssemblyMetadata to Aspire.Cli.csproj
- Add PackagingService.GetEmbeddedChannel() to read channel at runtime
- UpdateCommand uses embedded channel as self-update default (no prompt)
- Remove Channel property from AspireConfigFile; add JsonExtensionData
  to preserve unknown properties on round-trip
- All channel consumers fall back to embedded channel when no explicit
  --channel flag or local config channel is set
- Build infrastructure: Common.projitems defaults CliChannel=stable,
  Bundle.proj forwards CliChannel, localhive scripts use CliChannel=pr
- Update tests to remove references to removed Channel property

Channel resolution priority (highest to lowest):
1. --channel CLI flag
2. Local .aspire/settings.json channel (polyglot backward compat)
3. Global aspire.config.json channel (backward compat via ExtensionData)
4. Embedded CliChannel assembly metadata
5. Implicit channel (NuGet feed defaults)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…n, and GetEmbeddedChannel

- ExtensionData round-trip: verify unknown JSON properties (like legacy
  'channel') survive load → save → reload
- ExtensionData preservation: verify unknown properties captured on load
- ExtensionData null: verify no ExtensionData when all props are known
- FromLegacy channel exclusion: verify channel is NOT migrated from legacy
  config (now embedded in binary instead)
- FromLegacy other fields: verify all non-channel fields still migrate
- GetEmbeddedChannel dev build: verify returns null when no assembly
  metadata is set (test/dev builds)

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

github-actions bot commented Apr 8, 2026

Re-running the failed jobs in the CI workflow for this pull request because 1 job was identified as retry-safe transient failures in the CI run attempt.
GitHub was asked to rerun all failed jobs for that attempt, and the rerun is being tracked in the rerun attempt.
The job links below point to the failed attempt jobs that matched the retry-safe transient failure rules.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

Re-running the failed jobs in the CI workflow for this pull request because 1 job was identified as retry-safe transient failures in the CI run attempt.
GitHub was asked to rerun all failed jobs for that attempt, and the rerun is being tracked in the rerun attempt.
The job links below point to the failed attempt jobs that matched the retry-safe transient failure rules.

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

Labels

None yet

Projects

None yet

1 participant