diff --git a/docs/dogfooding-pull-requests.md b/docs/dogfooding-pull-requests.md index dafd90fe7d1..12cf09188fd 100644 --- a/docs/dogfooding-pull-requests.md +++ b/docs/dogfooding-pull-requests.md @@ -329,9 +329,9 @@ The file is scoped to the solution directory and only affects projects under it. The Homebrew cask (`eng/homebrew/aspire.rb.template`) installs Aspire entirely inside the Caskroom version directory — `brew uninstall aspire` removes -the binary and the route sidecar end-to-end. The cask intentionally carries -no `zap` stanza, because `~/.aspire/` is a shared prefix with the script-route -and PR-route installers and a brew-driven recursive delete would clobber state +the binary and the source sidecar end-to-end. The cask intentionally carries +no `zap` stanza, because `~/.aspire/` is a shared prefix with the script-source +and PR-source installers and a brew-driven recursive delete would clobber state those installers still own. If you installed via the Homebrew cask before this change, you may have a @@ -343,7 +343,7 @@ Clean it up manually once after upgrading the cask: rm -rf ~/.aspire/installs/brew-stable ``` -NuGet hives under `~/.aspire/hives/` and any script-route or PR-route +NuGet hives under `~/.aspire/hives/` and any script-source or PR-source binaries under `~/.aspire/bin/` and `~/.aspire/dogfood/` are not touched by the cask in either direction; manage those with the steps above. diff --git a/docs/specs/cli-output-formats.md b/docs/specs/cli-output-formats.md index bbe2393303a..a8c02e0831f 100644 --- a/docs/specs/cli-output-formats.md +++ b/docs/specs/cli-output-formats.md @@ -474,6 +474,43 @@ The JSON form includes secret values. Do not redirect it to logs or files unless `status` is one of `pass`, `warning`, or `fail`. Individual checks can include `details`, `fix`, `link`, or command-specific `metadata`. +### `aspire installs list` + +`aspire installs list --format json` emits the Aspire CLI installs and orphan package hives that the running CLI can discover: + +```json +[ + { + "id": "script", + "kind": "script", + "channel": "stable", + "path": "/home/user/.aspire/bin/aspire", + "hive": "/home/user/.aspire/hives/stable", + "status": "active" + }, + { + "id": "pr-17400", + "kind": "orphan-hive", + "channel": "pr-17400", + "hive": "/home/user/.aspire/hives/pr-17400", + "status": "no install found", + "statusReason": "No discovered install reports this hive's channel." + }, + { + "id": "stable", + "kind": "homebrew", + "channel": "stable", + "path": "/opt/homebrew/Caskroom/aspire/13.2.0/aspire", + "status": "active", + "managedBy": "homebrew" + } +] +``` + +`status` uses the install-discovery status (`active`, `shadowed`, `notOnPath`, `failed: `, `notProbed: `) or `no install found` for orphan hives. + +`aspire installs --self --format json` is a hidden command used by the install-discovery peer-probe path so a newer CLI can ask a peer CLI to describe itself. The shape is an internal cross-version contract between Aspire CLI builds — not a stable surface for tooling — and may change without notice. + ### `aspire config info` `aspire config info --json` is a hidden tooling command that emits configuration paths, feature metadata, settings schemas, and advertised CLI capabilities: diff --git a/docs/specs/install-routes.md b/docs/specs/install-sources.md similarity index 57% rename from docs/specs/install-routes.md rename to docs/specs/install-sources.md index 4def1a0bdd5..37c13348669 100644 --- a/docs/specs/install-routes.md +++ b/docs/specs/install-sources.md @@ -1,8 +1,8 @@ -# Aspire CLI install-route sidecar +# Aspire CLI install-source sidecar > Pairs with `docs/specs/bundle.md` (bundle extraction layout) and `docs/ci/native-cli-packaging.md` (how archives are produced). -The CLI binary identifies its install route by reading a single +The CLI binary identifies its install source by reading a single `.aspire-install.json` sidecar that lives next to the binary. The sidecar's `source` field selects the extract-dir shape used by `BundleService` and, for portable installs, the Aspire home used for hives and local state. @@ -13,10 +13,10 @@ portable installs, the Aspire home used for hives and local state. (`/.aspire-install.json`) and contains exactly one field: ```json -{ "source": "" } +{ "source": "" } ``` -| `source` value | Install route | +| `source` value | Install source | |----------------|--------------------------------------------------------| | `brew` | Homebrew cask | | `winget` | WinGet portable manifest | @@ -37,26 +37,26 @@ install. `script` and `localhive` use the parent of `bin`; `pr` uses the parent of `dogfood/pr-/bin`. Package-manager installs and sidecar-less binaries keep the default user-profile Aspire home. -## Per-route authorship +## Per-source authorship -**The shared per-RID CLI archives (`aspire-cli--*.zip` / `.tar.gz`) ship sidecar-free.** Those archives are reused across brew, winget, the release script, and the PR script — none of them owns the route label. Each route writes its own sidecar at install time. +**The shared per-RID CLI archives (`aspire-cli--*.zip` / `.tar.gz`) ship sidecar-free.** Those archives are reused across Homebrew, WinGet, the release script, and the PR script — none of them owns the source label. Each source writes its own sidecar at install time. -| Route | Archive shape | Sidecar writer | +| Source | Archive shape | Sidecar writer | |-------------|----------------------------------------|---------------------------------------------------------------------| -| brew | shared per-RID tarball | cask `postflight` block in `eng/homebrew/aspire.rb.template` | +| Homebrew | shared per-RID tarball | cask `postflight` block in `eng/homebrew/aspire.rb.template` | | winget | shared per-RID zip | CLI first-run probe (`WingetFirstRunProbe`) — uses the WinGet portable ARP registry entry to confirm the running binary was placed by winget, then stamps the sidecar | | script | shared per-RID archive | `eng/scripts/get-aspire-cli.{sh,ps1}` (post-extraction) | | PR script | shared per-RID archive | `eng/scripts/get-aspire-cli-pr.{sh,ps1}` (post-extraction) | -| dotnet-tool | route-exclusive nupkg | payload-embedded (staged by `Aspire.Cli.csproj` `_PreparePreBuiltCliBinaryForPackTool`) | -| localhive | local-only (no shared archive) | `localhive.{sh,ps1}` writes the sidecar after copying the CLI binary into `/bin/`. When `--output PATH` is used, the sidecar is written inside the output dir, which is appropriate because localhive archives are route-exclusive (only consumed as localhive installs). | +| dotnet-tool | source-exclusive nupkg | payload-embedded (staged by `Aspire.Cli.csproj` `_PreparePreBuiltCliBinaryForPackTool`) | +| localhive | local-only (no shared archive) | `localhive.{sh,ps1}` writes the sidecar after copying the CLI binary into `/bin/`. When `--output PATH` is used, the sidecar is written inside the output dir, which is appropriate because localhive archives are source-exclusive (only consumed as localhive installs). | -The dotnet-tool nupkg is the one exception that payload-embeds the sidecar: the nupkg is route-exclusive (only `dotnet tool install` consumes it), so the embedded sidecar cannot leak into another route's prefix. +The dotnet-tool nupkg is the one exception that payload-embeds the sidecar: the nupkg is source-exclusive (only `dotnet tool install` consumes it), so the embedded sidecar cannot leak into another source's prefix. ## Why no payload-embed in shared archives -Until PR 16817 the per-RID archives baked `{"source":"brew"}` (osx-*) and `{"source":"winget"}` (win-*) into the archive root via an MSBuild target. Because the osx-* tarball is also consumed by `get-aspire-cli-pr.sh`, the smuggled `brew` sidecar landed in the script-route prefix at `/dogfood/pr-/bin/.aspire-install.json`, and `BundleService` then selected `binaryDir` (the `brew` flat-layout case) as the extract dir — producing `/dogfood/pr-/bin/versions//` instead of `/dogfood/pr-/versions//`. +Until PR 16817 the per-RID archives baked source sidecars (`osx-*` as Homebrew, `win-*` as WinGet) into the archive root via an MSBuild target. Because the `osx-*` tarball is also consumed by `get-aspire-cli-pr.sh`, the smuggled `brew` sidecar landed in the script-source prefix at `/dogfood/pr-/bin/.aspire-install.json`, and `BundleService` then selected `binaryDir` (the `brew` flat-layout case) as the extract dir — producing `/dogfood/pr-/bin/versions//` instead of `/dogfood/pr-/versions//`. -Removing the MSBuild target and moving each route to author its own sidecar at install time makes the per-RID archive route-agnostic and prevents the leak by construction. +Removing the MSBuild target and moving each source to author its own sidecar at install time makes the per-RID archive source-agnostic and prevents the leak by construction. ## Producer-side invariants (build / CI) @@ -67,10 +67,10 @@ Two mechanical checks guard the contract: ## Reader-side invariants (runtime) -`BundleService.ComputeDefaultExtractDir` is the single point of truth for layout selection. It performs no path-shape detection: layout is a pure function of the sidecar `source` value (or the fallback when the sidecar is absent, unreadable, malformed, or has an unknown `source`). Unknown `source` values fall back to parent-of-binary for typed bundle layout handling. Coverage lives in `tests/Aspire.Cli.Tests/Bundles/BundleServiceCrossRouteExtractionTests.cs` as a theory over (source × prefix-shape) rows, including the cross-route case where a `brew` sidecar lands under a script-style prefix. +`BundleService.ComputeDefaultExtractDir` is the single point of truth for layout selection. It performs no path-shape detection: layout is a pure function of the sidecar `source` value (or the fallback when the sidecar is absent, unreadable, malformed, or has an unknown `source`). Unknown `source` values fall back to parent-of-binary for typed bundle layout handling. Coverage lives in `tests/Aspire.Cli.Tests/Bundles/BundleServiceCrossSourceExtractionTests.cs` as a theory over (source × prefix-shape) rows, including the cross-source case where a `brew` sidecar lands under a script-style prefix. -`CliPathHelper.GetAspireHomeDirectory` is the single point of truth for Aspire-home selection. It reads the same sidecar but only changes home for Aspire-owned portable routes (`script`, `pr`, and `localhive`); package-manager routes use the user-profile home because their install roots are package-manager-owned. Coverage lives in `tests/Aspire.Cli.Tests/Utils/CliPathHelperTests.cs`. +`CliPathHelper.GetAspireHomeDirectory` is the single point of truth for Aspire-home selection. It reads the same sidecar but only changes home for Aspire-owned portable sources (`script`, `pr`, and `localhive`); package-manager sources use the user-profile home because their install roots are package-manager-owned. Coverage lives in `tests/Aspire.Cli.Tests/Utils/CliPathHelperTests.cs`. -> **Discovery scope (dotnet-tool route).** Install discovery walks the default `dotnet tool install -g` location at `~/.dotnet/tools/.store/aspire.cli` only. Custom `--tool-path` installs are not discovered today: the dotnet CLI has no machine-wide registry of arbitrary `--tool-path` installs to enumerate, and walking the filesystem would balloon the cost of `aspire doctor`. Users with a custom-`--tool-path` install can confirm it directly with `/aspire doctor --self`. +> **Discovery scope (dotnet-tool source).** Install discovery walks the default `dotnet tool install -g` location at `~/.dotnet/tools/.store/aspire.cli` only. Custom `--tool-path` installs are not discovered today: the dotnet CLI has no machine-wide registry of arbitrary `--tool-path` installs to enumerate, and walking the filesystem would balloon the cost of `aspire installs list`. Users with a custom-`--tool-path` install can confirm it directly with `/aspire installs --self`. -For read-only install discovery (`aspire doctor --format json`), sidecar existence is the trust signal for peer probing. A candidate with any readable sidecar is probed even when `source` is not in the known route table; the raw `source` string is surfaced as the installation `route` so future package-manager routes can appear before this consumer updates. Sidecar-less, unreadable, or malformed candidates are listed without executing the binary. +For read-only install discovery (`aspire installs list --format json`), sidecar existence is the trust signal for peer probing. A candidate with any readable sidecar is probed even when `source` is not in the known source table; the raw `source` string is surfaced as the installation `source` so future package-manager sources can appear before this consumer updates. Sidecar-less, unreadable, or malformed candidates are listed without executing the binary. diff --git a/eng/clipack/Common.projitems b/eng/clipack/Common.projitems index d9bfc4e9dab..049c8403def 100644 --- a/eng/clipack/Common.projitems +++ b/eng/clipack/Common.projitems @@ -42,12 +42,12 @@