Skip to content

Commit bd8fb9f

Browse files
committed
Add 13.1.3 installer publishing changes
1 parent 0db61fa commit bd8fb9f

26 files changed

Lines changed: 2918 additions & 86 deletions

eng/Versions.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<!-- This repo version -->
44
<MajorVersion>13</MajorVersion>
55
<MinorVersion>1</MinorVersion>
6-
<PatchVersion>2</PatchVersion>
6+
<PatchVersion>3</PatchVersion>
77
<VersionPrefix>$(MajorVersion).$(MinorVersion).$(PatchVersion)</VersionPrefix>
88
<PreReleaseVersionLabel>preview.1</PreReleaseVersionLabel>
99
<DefaultTargetFramework>net8.0</DefaultTargetFramework>

eng/homebrew/README.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Homebrew Distribution for Aspire CLI
2+
3+
## Overview
4+
5+
Aspire CLI is distributed via [Homebrew Cask](https://docs.brew.sh/Cask-Cookbook) for macOS (arm64 and x64). Cask PRs are submitted to the upstream [Homebrew/homebrew-cask](https://github.com/Homebrew/homebrew-cask) repository.
6+
7+
### Install commands
8+
9+
```bash
10+
brew install --cask aspire # stable
11+
# brew install --cask aspire@prerelease # preview (not yet supported)
12+
```
13+
14+
## Contents
15+
16+
| File | Description |
17+
|---|---|
18+
| `aspire.rb.template` | Cask template for stable releases |
19+
| `aspire@prerelease.rb.template` | Cask template for prerelease builds |
20+
| `generate-cask.sh` | Downloads tarballs, computes SHA256 hashes, generates cask from template |
21+
22+
### Pipeline templates
23+
24+
| File | Description |
25+
|---|---|
26+
| `eng/pipelines/templates/prepare-homebrew-cask.yml` | Generates, validates, audits, and tests the cask |
27+
| `eng/pipelines/templates/publish-homebrew.yml` | Submits the cask as a PR to `Homebrew/homebrew-cask` |
28+
29+
## Supported Platforms
30+
31+
macOS only (arm64, x64). The cask uses `arch arm: "arm64", intel: "x64"` for URL templating.
32+
33+
## Artifact URLs
34+
35+
```text
36+
https://ci.dot.net/public/aspire/{ARTIFACT_VERSION}/aspire-cli-osx-{arch}-{VERSION}.tar.gz
37+
```
38+
39+
Where arch is `arm64` or `x64`.
40+
41+
## Why Cask
42+
43+
| Product | Type | Install command | Preview channel |
44+
|---|---|---|---|
45+
| GitHub Copilot CLI | homebrew-cask | `brew install --cask copilot-cli` | `copilot-cli@prerelease` |
46+
| .NET SDK | homebrew-cask | `brew install --cask dotnet-sdk` | `dotnet-sdk@preview` |
47+
| PowerShell | homebrew-cask | `brew install --cask powershell` | `powershell@preview` |
48+
49+
- **URL templating**: `url "...osx-#{arch}-#{version}.tar.gz"` — a single line instead of nested `on_macos do / if Hardware::CPU.arm?` blocks
50+
- **Official repo path**: Casks can be submitted to `Homebrew/homebrew-cask` for `brew install aspire` without a tap
51+
- **Cleaner multi-channel**: `aspire` and `aspire@prerelease` follow established cask naming conventions
52+
53+
## CI Pipeline
54+
55+
| Pipeline | Prepares | Publishes |
56+
|---|---|---|
57+
| `azure-pipelines.yml` (prepare stage) | Stable casks (artifacts only) ||
58+
| `release-publish-nuget.yml` (release) || Stable cask only |
59+
60+
Publishing submits a PR to `Homebrew/homebrew-cask` using `gh pr create`:
61+
62+
1. Forks `Homebrew/homebrew-cask` (idempotent — reuses existing fork)
63+
2. Creates a branch named `aspire-{version}`
64+
3. Copies the generated cask to `Casks/a/aspire.rb` (or `aspire@prerelease.rb`)
65+
4. Pushes and opens a PR with title `aspire {version}`
66+
67+
## Open Items
68+
69+
- [ ] Submit initial `aspire` cask PR to `Homebrew/homebrew-cask` for acceptance
70+
- [ ] Submit `aspire@prerelease` cask PR to `Homebrew/homebrew-cask`
71+
- [ ] Configure `aspire-homebrew-bot-pat` secret in the pipeline variable group
72+
73+
## References
74+
75+
- [Homebrew Cask Cookbook](https://docs.brew.sh/Cask-Cookbook)
76+
- [Copilot CLI cask](https://formulae.brew.sh/cask/copilot-cli) — our reference implementation
77+
- [.NET SDK cask](https://formulae.brew.sh/cask/dotnet-sdk) — stable + preview example

eng/homebrew/aspire.rb.template

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
cask "aspire" do
2+
arch arm: "arm64", intel: "x64"
3+
4+
version "${VERSION}"
5+
sha256 arm: "${SHA256_OSX_ARM64}",
6+
intel: "${SHA256_OSX_X64}"
7+
8+
url "https://ci.dot.net/public/aspire/${ARTIFACT_VERSION}/aspire-cli-osx-#{arch}-#{version}.tar.gz",
9+
verified: "ci.dot.net/public/aspire/"
10+
name "Aspire CLI"
11+
desc "CLI tool for building observable, production-ready distributed applications with Aspire"
12+
homepage "https://aspire.dev/"
13+
14+
conflicts_with cask: "aspire@prerelease"
15+
16+
binary "aspire"
17+
18+
zap trash: "~/.aspire"
19+
end
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
cask "aspire@prerelease" do
2+
arch arm: "arm64", intel: "x64"
3+
4+
version "${VERSION}"
5+
sha256 arm: "${SHA256_OSX_ARM64}",
6+
intel: "${SHA256_OSX_X64}"
7+
8+
url "https://ci.dot.net/public/aspire/${ARTIFACT_VERSION}/aspire-cli-osx-#{arch}-#{version}.tar.gz",
9+
verified: "ci.dot.net/public/aspire/"
10+
name "Aspire CLI (Prerelease)"
11+
desc "CLI tool for building observable, production-ready distributed applications with Aspire"
12+
homepage "https://aspire.dev/"
13+
14+
conflicts_with cask: "aspire"
15+
16+
binary "aspire"
17+
18+
zap trash: "~/.aspire"
19+
end

eng/homebrew/dogfood.sh

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# Installs the Aspire CLI Homebrew cask from a local artifact directory.
5+
# This script is intended for dogfooding builds before they are published to Homebrew/homebrew-cask.
6+
#
7+
# Usage:
8+
# ./dogfood.sh # Auto-detects cask file in the same directory
9+
# ./dogfood.sh aspire.rb # Explicit cask file path
10+
# ./dogfood.sh --uninstall # Uninstall a previously dogfooded cask
11+
12+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13+
TAP_NAME="local/aspire-dogfood"
14+
15+
usage() {
16+
cat <<EOF
17+
Usage: $(basename "$0") [OPTIONS] [CASK_FILE]
18+
19+
Installs the Aspire CLI from a local Homebrew cask file for dogfooding.
20+
21+
Arguments:
22+
CASK_FILE Path to the .rb cask file (default: auto-detect in script directory)
23+
24+
Options:
25+
--uninstall Uninstall a previously dogfooded cask and remove the local tap
26+
--help Show this help message
27+
28+
Examples:
29+
$(basename "$0") # Auto-detect and install
30+
$(basename "$0") ./aspire.rb # Install from specific cask file
31+
$(basename "$0") --uninstall # Clean up dogfood install
32+
EOF
33+
exit 0
34+
}
35+
36+
uninstall() {
37+
echo "Uninstalling dogfooded Aspire CLI..."
38+
39+
# Determine which cask is installed
40+
for caskName in "aspire" "aspire@prerelease"; do
41+
if brew list --cask "$TAP_NAME/$caskName" &>/dev/null; then
42+
echo " Uninstalling $TAP_NAME/$caskName..."
43+
brew uninstall --cask "$TAP_NAME/$caskName"
44+
echo " Uninstalled."
45+
fi
46+
done
47+
48+
if brew tap-info "$TAP_NAME" &>/dev/null; then
49+
echo " Removing tap $TAP_NAME..."
50+
brew untap "$TAP_NAME"
51+
echo " Removed."
52+
fi
53+
54+
echo ""
55+
echo "Done. Dogfood install removed."
56+
exit 0
57+
}
58+
59+
CASK_FILE=""
60+
UNINSTALL=false
61+
62+
while [[ $# -gt 0 ]]; do
63+
case "$1" in
64+
--uninstall) UNINSTALL=true; shift ;;
65+
--help) usage ;;
66+
-*) echo "Unknown option: $1"; usage ;;
67+
*) CASK_FILE="$1"; shift ;;
68+
esac
69+
done
70+
71+
if [[ "$UNINSTALL" == true ]]; then
72+
uninstall
73+
fi
74+
75+
# Auto-detect cask file if not specified
76+
if [[ -z "$CASK_FILE" ]]; then
77+
for candidate in "$SCRIPT_DIR/aspire.rb" "$SCRIPT_DIR/aspire@prerelease.rb"; do
78+
if [[ -f "$candidate" ]]; then
79+
CASK_FILE="$candidate"
80+
break
81+
fi
82+
done
83+
84+
if [[ -z "$CASK_FILE" ]]; then
85+
echo "Error: No cask file found in $SCRIPT_DIR"
86+
echo "Expected aspire.rb or aspire@prerelease.rb"
87+
exit 1
88+
fi
89+
fi
90+
91+
if [[ ! -f "$CASK_FILE" ]]; then
92+
echo "Error: Cask file not found: $CASK_FILE"
93+
exit 1
94+
fi
95+
96+
CASK_FILE="$(cd "$(dirname "$CASK_FILE")" && pwd)/$(basename "$CASK_FILE")"
97+
CASK_FILENAME="$(basename "$CASK_FILE")"
98+
CASK_NAME="${CASK_FILENAME%.rb}"
99+
100+
echo "Aspire CLI Homebrew Dogfood Installer"
101+
echo "======================================"
102+
echo " Cask file: $CASK_FILE"
103+
echo " Cask name: $CASK_NAME"
104+
echo ""
105+
106+
# Check for conflicts with official installs
107+
for check in "aspire" "aspire@prerelease"; do
108+
if brew list --cask "$check" &>/dev/null; then
109+
echo "Error: '$check' is already installed from the official Homebrew tap."
110+
echo "Uninstall it first with: brew uninstall --cask $check"
111+
exit 1
112+
fi
113+
done
114+
115+
# Check for leftover local/aspire tap from pipeline testing
116+
if brew tap-info "local/aspire" &>/dev/null 2>&1; then
117+
echo "Error: A 'local/aspire' tap already exists (likely from a pipeline test run)."
118+
echo "Remove it first with: brew untap local/aspire"
119+
exit 1
120+
fi
121+
122+
if brew tap-info "local/aspire-test" &>/dev/null 2>&1; then
123+
echo "Error: A 'local/aspire-test' tap already exists (likely from a pipeline test run)."
124+
echo "Remove it first with: brew untap local/aspire-test"
125+
exit 1
126+
fi
127+
128+
# Clean up any previous dogfood tap
129+
if brew tap-info "$TAP_NAME" &>/dev/null 2>&1; then
130+
echo "Removing previous dogfood tap..."
131+
# Uninstall any casks from the old tap first
132+
for old in "aspire" "aspire@prerelease"; do
133+
if brew list --cask "$TAP_NAME/$old" &>/dev/null; then
134+
brew uninstall --cask "$TAP_NAME/$old" || true
135+
fi
136+
done
137+
brew untap "$TAP_NAME"
138+
fi
139+
140+
# Set up local tap
141+
echo "Setting up local tap ($TAP_NAME)..."
142+
brew tap-new --no-git "$TAP_NAME"
143+
tapOrg="${TAP_NAME%%/*}"
144+
tapRepo="${TAP_NAME##*/}"
145+
tapRoot="$(brew --repository)/Library/Taps/$tapOrg/homebrew-$tapRepo"
146+
tapCaskDir="$tapRoot/Casks"
147+
mkdir -p "$tapCaskDir"
148+
cp "$CASK_FILE" "$tapCaskDir/$CASK_FILENAME"
149+
150+
# Install
151+
echo ""
152+
echo "Installing $CASK_NAME from local tap..."
153+
# Disable auto-update during install — auto-update can re-index the tap before
154+
# the cask file is picked up, causing a "cask unavailable" error.
155+
HOMEBREW_NO_AUTO_UPDATE=1 brew install --cask "$TAP_NAME/$CASK_NAME"
156+
157+
# Verify
158+
echo ""
159+
if command -v aspire &>/dev/null; then
160+
echo "Installed successfully!"
161+
echo " Path: $(command -v aspire)"
162+
aspireVersion="$(aspire --version 2>&1)" || true
163+
echo " Version: $aspireVersion"
164+
else
165+
echo "Warning: aspire command not found in PATH after install."
166+
echo "You may need to restart your shell or add the install location to your PATH."
167+
fi
168+
169+
echo ""
170+
echo "To uninstall: $(basename "$0") --uninstall"

0 commit comments

Comments
 (0)