Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions docs/dogfooding-pull-requests.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ Notes:
## What gets installed

- Aspire CLI:
- Default location: `~/.aspire/bin/aspire` (or `aspire.exe` on Windows)
- Important: If you already have the Aspire CLI installed under the same prefix (default `~/.aspire`), running this script will overwrite that installation. To switch back to the official build, simply re-run the standard Aspire CLI install script referenced in the README to reinstall the released version.
- Default location: `~/.aspire/dogfood/pr-<PR_NUMBER>/aspire` (or `aspire.exe` on Windows)
- Important: PR installs are self-contained under `~/.aspire/dogfood/pr-<PR_NUMBER>/` and do not overwrite the stable CLI at `~/.aspire/bin/aspire`. Each PR has its own isolated install root.

- PR-scoped NuGet packages "hive":
- Default location: `~/.aspire/hives/pr-<PR_NUMBER>/packages`
- Default location: `~/.aspire/dogfood/pr-<PR_NUMBER>/hives/pr-<PR_NUMBER>/packages`
- This local, PR-specific hive is isolated, making it easy to create new projects with just the packages produced by the PR build without affecting your global NuGet caches or other projects.

The scripts attempt to add `~/.aspire/bin` to your shell/profile PATH so you can invoke `aspire` directly in new terminals. If PATH isn't updated automatically, add it manually per the script's message.
The scripts attempt to add `~/.aspire/dogfood/pr-<PR_NUMBER>` to your shell/profile PATH so you can invoke `aspire` directly in new terminals. If PATH isn't updated automatically, add it manually per the script's message.

## Quickstart

Expand Down Expand Up @@ -166,15 +166,17 @@ The scripts auto-detect your OS and architecture and locate the latest `ci.yml`

## Uninstall/Cleanup

- Remove the CLI:
- Delete `~/.aspire/bin/aspire` (or the custom install path you used)
- Remove a PR dogfood install (CLI, bundle, hives — everything):
- Delete the entire install root: `rm -rf ~/.aspire/dogfood/pr-<PR_NUMBER>` (or the custom install path you used)
- Remove the PATH entry from your shell profile if added

- Remove PR-specific packages:
- Delete `~/.aspire/hives/pr-<PR_NUMBER>/packages`
- Remove all dogfood installs:
- `rm -rf ~/.aspire/dogfood`

- The stable CLI at `~/.aspire/bin/aspire` is not affected by dogfood installs.

## Safety note

Remote one-liners execute scripts fetched from the repository. Review the script source before running if needed:
- Bash: `eng/scripts/get-aspire-cli-pr.sh`
- PowerShell: `eng/scripts/get-aspire-cli-pr.ps1`
- PowerShell: `eng/scripts/get-aspire-cli-pr.ps1`
85 changes: 24 additions & 61 deletions eng/scripts/get-aspire-cli-pr.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@

.PARAMETER InstallPath
Directory prefix to install (default: $HOME/.aspire on Unix, %USERPROFILE%\.aspire on Windows)
CLI will be installed to InstallPath\bin (or InstallPath/bin on Unix)
NuGet packages will be installed to InstallPath\hives\pr-PRNUMBER\packages
CLI will be installed to InstallPath\dogfood\pr-PRNUMBER (or InstallPath/dogfood/pr-PRNUMBER on Unix)
NuGet packages will be installed to InstallPath\dogfood\pr-PRNUMBER\hives\pr-PRNUMBER\packages

.PARAMETER OS
Override OS detection (win, linux, linux-musl, osx)
Expand Down Expand Up @@ -392,11 +392,11 @@ function Backup-ExistingCliExecutable {
[Parameter(Mandatory = $true)]
[string]$TargetExePath
)

if (Test-Path $TargetExePath) {
$unixTimestamp = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds()
$backupPath = "$TargetExePath.old.$unixTimestamp"

if ($PSCmdlet.ShouldProcess($TargetExePath, "Backup to $backupPath")) {
Write-Message "Backing up existing CLI: $TargetExePath -> $backupPath" -Level Verbose

Expand All @@ -412,7 +412,7 @@ function Backup-ExistingCliExecutable {
return $backupPath
}
}

return $null
}

Expand All @@ -422,18 +422,18 @@ function Restore-CliExecutableFromBackup {
param(
[Parameter(Mandatory = $true)]
[string]$BackupPath,

[Parameter(Mandatory = $true)]
[string]$TargetExePath
)

if ($PSCmdlet.ShouldProcess($BackupPath, "Restore to $TargetExePath")) {
Write-Message "Restoring CLI from backup: $BackupPath -> $TargetExePath" -Level Warning

if (Test-Path $TargetExePath) {
Remove-Item -Path $TargetExePath -Force -ErrorAction SilentlyContinue
}

Move-Item -Path $BackupPath -Destination $TargetExePath -Force -ErrorAction Stop
}
}
Expand All @@ -445,15 +445,15 @@ function Remove-OldCliBackupFiles {
[Parameter(Mandatory = $true)]
[string]$TargetExePath
)

$directory = Split-Path -Parent $TargetExePath
if ([string]::IsNullOrEmpty($directory)) {
return
}

$exeName = Split-Path -Leaf $TargetExePath
$searchPattern = "$exeName.old.*"

$oldBackupFiles = Get-ChildItem -Path $directory -Filter $searchPattern -ErrorAction SilentlyContinue
foreach ($backupFile in $oldBackupFiles) {
if ($PSCmdlet.ShouldProcess($backupFile.FullName, "Delete old backup")) {
Expand Down Expand Up @@ -720,37 +720,6 @@ function Remove-TempDirectory {
# END: Shared code
# =============================================================================

# Function to save global settings using the aspire CLI
# Uses 'aspire config set -g' to set global configuration values
# Expected schema of ~/.aspire/globalsettings.json:
# {
# "channel": "string" // The channel name (e.g., "daily", "staging", "pr-1234")
# }
function Save-GlobalSettings {
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory = $true)]
[string]$CliPath,

[Parameter(Mandatory = $true)]
[string]$Key,

[Parameter(Mandatory = $true)]
[string]$Value
)

if ($PSCmdlet.ShouldProcess("$Key = $Value", "Set global config via aspire CLI")) {
Write-Message "Setting global config: $Key = $Value" -Level Verbose

$output = & $CliPath config set -g $Key $Value 2>&1
if ($LASTEXITCODE -ne 0) {
Write-Message "Failed to set global config via aspire CLI" -Level Warning
return
}
Write-Message "Global config saved: $Key = $Value" -Level Verbose
}
}

# Function to check if gh command is available
function Test-GitHubCLIDependency {
[CmdletBinding()]
Expand Down Expand Up @@ -874,32 +843,32 @@ function Get-VersionSuffixFromPackages {
[Parameter(Mandatory = $true)]
[string]$DownloadDir
)

if ($PSCmdlet.ShouldProcess("packages", "Extract version suffix from packages") -and $WhatIfPreference) {
# Return a mock version for WhatIf
return "pr.1234.a1b2c3d4"
}

# Look for any .nupkg file and extract version from its name
$nupkgFiles = Get-ChildItem -Path $DownloadDir -Filter "*.nupkg" -Recurse | Select-Object -First 1

if (-not $nupkgFiles) {
Write-Message "No .nupkg files found to extract version from" -Level Verbose
throw "No NuGet packages found to extract version information from"
}

$filename = $nupkgFiles.Name
Write-Message "Extracting version from package: $filename" -Level Verbose

# Extract version from package name using a more robust approach
# Remove .nupkg extension first, then look for the specific version pattern
$baseName = $filename -replace '\.nupkg$', ''

# Look for semantic version pattern with PR suffix (more specific and robust)
if ($baseName -match '.*\.(\d+\.\d+\.\d+-pr\.\d+\.[0-9a-g]+)$') {
$version = $Matches[1]
Write-Message "Extracted version: $version" -Level Verbose

# Extract just the PR suffix part using more specific regex
if ($version -match '(pr\.[0-9]+\.[0-9a-g]+)') {
$versionSuffix = $Matches[1]
Expand Down Expand Up @@ -1210,9 +1179,9 @@ function Start-DownloadAndInstall {

Write-Message "Using workflow run https://github.com/$Script:Repository/actions/runs/$runId" -Level Info

# Set installation paths
$cliBinDir = Join-Path $resolvedInstallPrefix "bin"
$nugetHiveDir = Join-Path $resolvedInstallPrefix "hives" "pr-$PRNumber" "packages"
# Set installation paths (self-contained dogfood layout)
$cliBinDir = Join-Path $resolvedInstallPrefix "dogfood" "pr-$PRNumber"
$nugetHiveDir = Join-Path $resolvedInstallPrefix "dogfood" "pr-$PRNumber" "hives" "pr-$PRNumber" "packages"

$rid = Get-RuntimeIdentifier $OS $Architecture

Expand Down Expand Up @@ -1257,14 +1226,8 @@ function Start-DownloadAndInstall {
}
}

# Save the global channel setting to the PR hive channel
# This allows 'aspire new' and 'aspire init' to use the same channel by default
if (-not $HiveOnly) {
# Determine CLI path
$cliExe = if ($Script:HostOS -eq "win") { "aspire.exe" } else { "aspire" }
$cliPath = Join-Path $cliBinDir $cliExe
Save-GlobalSettings -CliPath $cliPath -Key "channel" -Value "pr-$PRNumber"
}
# Dogfood installs no longer set the global channel.
# The self-contained install discovers its own hives relative to its install root.

# Update PATH environment variables
if (-not $HiveOnly) {
Expand Down
79 changes: 19 additions & 60 deletions eng/scripts/get-aspire-cli-pr.sh
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ USAGE:
PR_NUMBER Pull request number (required)
--run-id, -r WORKFLOW_ID Workflow run ID to download from (optional)
-i, --install-path PATH Directory prefix to install (default: ~/.aspire)
CLI installs to: <install-path>/bin
NuGet hive: <install-path>/hives/pr-<PR_NUMBER>/packages
CLI installs to: <install-path>/dogfood/pr-<PR_NUMBER>
NuGet hive: <install-path>/dogfood/pr-<PR_NUMBER>/hives/pr-<PR_NUMBER>/packages
--os OS Override OS detection (win, linux, linux-musl, osx)
--arch ARCH Override architecture detection (x64, arm64)
--hive-only Only install NuGet packages to the hive, skip CLI download
Expand Down Expand Up @@ -408,36 +408,6 @@ install_archive() {
say_verbose "Successfully installed archive"
}

# Function to save global settings using the aspire CLI
# Uses 'aspire config set -g' to set global configuration values
# Parameters:
# $1 - cli_path: Path to the aspire CLI executable
# $2 - key: The configuration key to set
# $3 - value: The value to set
# Expected schema of ~/.aspire/globalsettings.json:
# {
# "channel": "string" // The channel name (e.g., "daily", "staging", "pr-1234")
# }
save_global_settings() {
local cli_path="$1"
local key="$2"
local value="$3"

if [[ "$DRY_RUN" == true ]]; then
say_info "[DRY RUN] Would run: $cli_path config set -g $key $value"
return 0
fi

say_verbose "Setting global config: $key = $value"

if ! "$cli_path" config set -g "$key" "$value" 2>/dev/null; then
say_warn "Failed to set global config via aspire CLI"
return 1
fi

say_verbose "Global config saved: $key = $value"
}

# Function to add PATH to shell configuration file
# Parameters:
# $1 - config_file: Path to the shell configuration file
Expand Down Expand Up @@ -631,41 +601,41 @@ get_pr_head_sha() {
# Function to extract version suffix from downloaded NuGet packages
extract_version_suffix_from_packages() {
local download_dir="$1"

if [[ "$DRY_RUN" == true ]]; then
# Return a mock version for dry run
printf "pr.1234.a1b2c3d4"
return 0
fi

# Look for any .nupkg file and extract version from its name
local nupkg_file
nupkg_file=$(find "$download_dir" -name "*.nupkg" | head -1)

if [[ -z "$nupkg_file" ]]; then
say_verbose "No .nupkg files found to extract version from"
return 1
fi

local filename
filename=$(basename "$nupkg_file")
say_verbose "Extracting version from package: $filename"

# Extract version from package name using a more robust two-step approach
# First remove the .nupkg extension, then extract the version part
local base_name="${filename%.nupkg}"
local version

# Look for semantic version pattern with PR suffix (more specific and robust)
version=$(echo "$base_name" | sed -En 's/.*\.([0-9]+\.[0-9]+\.[0-9]+-pr\.[0-9]+\.[a-g0-9]+)/\1/p')

if [[ -z "$version" ]]; then
say_verbose "Could not extract version from package name: $filename"
return 1
fi

say_verbose "Extracted full version: $version"

# Extract just the PR suffix part using bash regex for better compatibility
if [[ "$version" =~ (pr\.[0-9]+\.[a-g0-9]+) ]]; then
local version_suffix="${BASH_REMATCH[1]}"
Expand Down Expand Up @@ -972,9 +942,9 @@ download_and_install_from_pr() {

say_info "Using workflow run https://github.com/${REPO}/actions/runs/$workflow_run_id"

# Set installation paths
local cli_install_dir="$INSTALL_PREFIX/bin"
local nuget_hive_dir="$INSTALL_PREFIX/hives/pr-$PR_NUMBER/packages"
# Set installation paths (self-contained dogfood layout)
local cli_install_dir="$INSTALL_PREFIX/dogfood/pr-$PR_NUMBER"
local nuget_hive_dir="$INSTALL_PREFIX/dogfood/pr-$PR_NUMBER/hives/pr-$PR_NUMBER/packages"

# First, download both artifacts
local cli_archive_path nuget_download_dir
Expand Down Expand Up @@ -1039,19 +1009,8 @@ download_and_install_from_pr() {
fi
fi

# Save the global channel setting to the PR hive channel
# This allows 'aspire new' and 'aspire init' to use the same channel by default
if [[ "$HIVE_ONLY" != true ]]; then
# Determine CLI path
local cli_path
if [[ -f "$cli_install_dir/aspire.exe" ]]; then
cli_path="$cli_install_dir/aspire.exe"
else
cli_path="$cli_install_dir/aspire"
fi
# Non-fatal: channel can be set manually if this fails
save_global_settings "$cli_path" "channel" "pr-$PR_NUMBER" || true
fi
# Dogfood installs no longer set the global channel.
# The self-contained install discovers its own hives relative to its install root.
}

# =============================================================================
Expand Down Expand Up @@ -1084,9 +1043,9 @@ else
INSTALL_PREFIX_UNEXPANDED="$INSTALL_PREFIX"
fi

# Set paths based on install prefix
cli_install_dir="$INSTALL_PREFIX/bin"
INSTALL_PATH_UNEXPANDED="$INSTALL_PREFIX_UNEXPANDED/bin"
# Set paths based on install prefix (self-contained dogfood layout)
cli_install_dir="$INSTALL_PREFIX/dogfood/pr-$PR_NUMBER"
INSTALL_PATH_UNEXPANDED="$INSTALL_PREFIX_UNEXPANDED/dogfood/pr-$PR_NUMBER"

# Create a temporary directory for downloads
if [[ "$DRY_RUN" == true ]]; then
Expand Down
Loading
Loading