|
| 1 | +#!/usr/bin/env bash |
| 2 | + |
| 3 | +# Build local NuGet packages and create/update an Aspire CLI hive that points at them. |
| 4 | +# |
| 5 | +# Usage: |
| 6 | +# ./localhive.sh [options] |
| 7 | +# ./localhive.sh [Release|Debug] [HiveName] |
| 8 | +# |
| 9 | +# Options: |
| 10 | +# -c, --configuration Build configuration: Release or Debug |
| 11 | +# -n, --name Hive name (default: local) |
| 12 | +# -v, --versionsuffix Prerelease version suffix (default: auto-generates local.YYYYMMDD.tHHmmss) |
| 13 | +# --copy Copy .nupkg files instead of creating a symlink |
| 14 | +# -h, --help Show this help and exit |
| 15 | +# |
| 16 | +# Notes: |
| 17 | +# - If no configuration is specified, the script tries Release then Debug. |
| 18 | +# - The hive is created at $HOME/.aspire/hives/<HiveName> so the Aspire CLI can discover a channel. |
| 19 | + |
| 20 | +set -euo pipefail |
| 21 | + |
| 22 | +print_usage() { |
| 23 | + cat <<EOF |
| 24 | +Usage: |
| 25 | + ./localhive.sh [options] |
| 26 | + ./localhive.sh [Release|Debug] [HiveName] |
| 27 | +
|
| 28 | +Options: |
| 29 | + -c, --configuration Build configuration: Release or Debug |
| 30 | + -n, --name Hive name (default: local) |
| 31 | + -v, --versionsuffix Prerelease version suffix (default: auto-generates local.YYYYMMDD.tHHmmss) |
| 32 | + --copy Copy .nupkg files instead of creating a symlink |
| 33 | + -h, --help Show this help and exit |
| 34 | +
|
| 35 | +Examples: |
| 36 | + ./localhive.sh -c Release -n local |
| 37 | + ./localhive.sh Debug my-feature |
| 38 | + ./localhive.sh -c Release -n demo -v local.20250811.t033324 |
| 39 | +
|
| 40 | +This will pack NuGet packages into artifacts/packages/<Config>/Shipping and create/update |
| 41 | +a hive at \$HOME/.aspire/hives/<HiveName> so the Aspire CLI can use it as a channel. |
| 42 | +EOF |
| 43 | +} |
| 44 | + |
| 45 | +log() { echo "[localhive] $*"; } |
| 46 | +warn() { echo "[localhive] Warning: $*" >&2; } |
| 47 | +error() { echo "[localhive] Error: $*" >&2; } |
| 48 | + |
| 49 | +if [ -z "${ZSH_VERSION:-}" ]; then |
| 50 | + source="${BASH_SOURCE[0]}" |
| 51 | + # resolve $SOURCE until the file is no longer a symlink |
| 52 | + while [[ -h $source ]]; do |
| 53 | + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" |
| 54 | + source="$(readlink "$source")" |
| 55 | + [[ $source != /* ]] && source="$scriptroot/$source" |
| 56 | + done |
| 57 | + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" |
| 58 | +else |
| 59 | + # :A resolves symlinks, :h truncates to directory |
| 60 | + scriptroot=${0:A:h} |
| 61 | +fi |
| 62 | + |
| 63 | +REPO_ROOT=$(cd "${scriptroot}"; pwd) |
| 64 | + |
| 65 | +CONFIG="" |
| 66 | +HIVE_NAME="local" |
| 67 | +USE_COPY=0 |
| 68 | +VERSION_SUFFIX="" |
| 69 | +is_valid_versionsuffix() { |
| 70 | + local s="$1" |
| 71 | + # Must be dot-separated identifiers containing only 0-9A-Za-z- per SemVer2. |
| 72 | + if [[ ! "$s" =~ ^[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*$ ]]; then |
| 73 | + return 1 |
| 74 | + fi |
| 75 | + # Numeric identifiers must not have leading zeros. |
| 76 | + IFS='.' read -r -a parts <<< "$s" |
| 77 | + for part in "${parts[@]}"; do |
| 78 | + if [[ "$part" =~ ^[0-9]+$ ]] && [[ ${#part} -gt 1 ]] && [[ "$part" == 0* ]]; then |
| 79 | + return 1 |
| 80 | + fi |
| 81 | + done |
| 82 | + return 0 |
| 83 | +} |
| 84 | + |
| 85 | + |
| 86 | +# Parse flags and positional fallbacks |
| 87 | +while [[ $# -gt 0 ]]; do |
| 88 | + case "$1" in |
| 89 | + -h|--help) |
| 90 | + print_usage |
| 91 | + exit 0 |
| 92 | + ;; |
| 93 | + -c|--configuration) |
| 94 | + if [[ $# -lt 2 ]]; then error "Missing value for $1"; exit 1; fi |
| 95 | + CONFIG="$2"; shift 2 ;; |
| 96 | + -n|--name|--hive|--hive-name) |
| 97 | + if [[ $# -lt 2 ]]; then error "Missing value for $1"; exit 1; fi |
| 98 | + HIVE_NAME="$2"; shift 2 ;; |
| 99 | + -v|--versionsuffix) |
| 100 | + if [[ $# -lt 2 ]]; then error "Missing value for $1"; exit 1; fi |
| 101 | + VERSION_SUFFIX="$2"; shift 2 ;; |
| 102 | + --copy) |
| 103 | + USE_COPY=1; shift ;; |
| 104 | + --) |
| 105 | + shift; break ;; |
| 106 | + Release|Debug|release|debug) |
| 107 | + # Positional config (for backward-compat) |
| 108 | + if [[ -z "$CONFIG" ]]; then CONFIG="$1"; else HIVE_NAME="$1"; fi |
| 109 | + shift ;; |
| 110 | + *) |
| 111 | + # Treat first unknown as hive name if not set, else error |
| 112 | + if [[ "$HIVE_NAME" == "local" ]]; then HIVE_NAME="$1"; shift; else error "Unknown argument: $1"; exit 1; fi ;; |
| 113 | + esac |
| 114 | +done |
| 115 | + |
| 116 | +# Normalize config value if set |
| 117 | +if [[ -n "$CONFIG" ]]; then |
| 118 | + case "${CONFIG,,}" in |
| 119 | + release) CONFIG=Release ;; |
| 120 | + debug) CONFIG=Debug ;; |
| 121 | + *) error "Unsupported configuration '$CONFIG'. Use Release or Debug."; exit 1 ;; |
| 122 | + esac |
| 123 | +fi |
| 124 | + |
| 125 | +# If no version suffix provided, auto-generate one so packages rev every build. |
| 126 | +if [[ -z "$VERSION_SUFFIX" ]]; then |
| 127 | + VERSION_SUFFIX="local.$(date -u +%Y%m%d).t$(date -u +%H%M%S)" |
| 128 | +fi |
| 129 | + |
| 130 | +# Validate provided/auto-generated suffix early to avoid NuGet failures. |
| 131 | +if ! is_valid_versionsuffix "$VERSION_SUFFIX"; then |
| 132 | + error "Invalid versionsuffix '$VERSION_SUFFIX'. It must be dot-separated identifiers using [0-9A-Za-z-] only; numeric identifiers cannot have leading zeros." |
| 133 | + warn "Examples: preview.1, rc.2, local.20250811.t033324" |
| 134 | + exit 1 |
| 135 | +fi |
| 136 | +log "Using prerelease version suffix: $VERSION_SUFFIX" |
| 137 | + |
| 138 | +if [ -n "$CONFIG" ]; then |
| 139 | + log "Building and packing NuGet packages [-c $CONFIG] with versionsuffix '$VERSION_SUFFIX'" |
| 140 | + # Single invocation: restore + build + pack to ensure all Build-triggered targets run and packages are produced. |
| 141 | + "$REPO_ROOT/build.sh" -r -b --pack -c "$CONFIG" /p:VersionSuffix="$VERSION_SUFFIX" |
| 142 | + PKG_DIR="$REPO_ROOT/artifacts/packages/$CONFIG/Shipping" |
| 143 | + if [ ! -d "$PKG_DIR" ]; then |
| 144 | + error "Could not find packages path $PKG_DIR for CONFIG=$CONFIG" |
| 145 | + exit 1 |
| 146 | + fi |
| 147 | +else |
| 148 | + # Try Release, then Debug |
| 149 | + log "Building and packing NuGet packages [-c Release] with versionsuffix '$VERSION_SUFFIX'" |
| 150 | + if "$REPO_ROOT/build.sh" -r -b --pack -c Release /p:VersionSuffix="$VERSION_SUFFIX"; then |
| 151 | + PKG_DIR="$REPO_ROOT/artifacts/packages/Release/Shipping" |
| 152 | + else |
| 153 | + warn "Release build/pack failed; trying Debug" |
| 154 | + "$REPO_ROOT/build.sh" -r -b --pack -c Debug /p:VersionSuffix="$VERSION_SUFFIX" |
| 155 | + PKG_DIR="$REPO_ROOT/artifacts/packages/Debug/Shipping" |
| 156 | + fi |
| 157 | + if [ ! -d "$PKG_DIR" ]; then |
| 158 | + error "Could not find packages path in $REPO_ROOT/artifacts/packages for Release or Debug" |
| 159 | + exit 1 |
| 160 | + fi |
| 161 | +fi |
| 162 | + |
| 163 | +# Ensure there are some .nupkg files |
| 164 | +shopt -s nullglob |
| 165 | +packages=("$PKG_DIR"/*.nupkg) |
| 166 | +pkg_count=${#packages[@]} |
| 167 | +shopt -u nullglob |
| 168 | +if [[ $pkg_count -eq 0 ]]; then |
| 169 | + error "No .nupkg files found in $PKG_DIR. Did the pack step succeed?" |
| 170 | + exit 1 |
| 171 | +fi |
| 172 | +log "Found $pkg_count packages in $PKG_DIR" |
| 173 | + |
| 174 | +HIVES_ROOT="$HOME/.aspire/hives" |
| 175 | +HIVE_PATH="$HIVES_ROOT/$HIVE_NAME" |
| 176 | + |
| 177 | +log "Preparing hive directory: $HIVES_ROOT" |
| 178 | +mkdir -p "$HIVES_ROOT" |
| 179 | + |
| 180 | +if [[ $USE_COPY -eq 1 ]]; then |
| 181 | + log "Populating hive '$HIVE_NAME' by copying .nupkg files" |
| 182 | + mkdir -p "$HIVE_PATH" |
| 183 | + cp -f "$PKG_DIR"/*.nupkg "$HIVE_PATH"/ 2>/dev/null || true |
| 184 | + log "Created/updated hive '$HIVE_NAME' at $HIVE_PATH (copied packages)." |
| 185 | +else |
| 186 | + log "Linking hive '$HIVE_NAME' to $PKG_DIR" |
| 187 | + if ln -sfn "$PKG_DIR" "$HIVE_PATH" 2>/dev/null; then |
| 188 | + log "Created/updated hive '$HIVE_NAME' -> $PKG_DIR" |
| 189 | + else |
| 190 | + warn "Symlink not supported; copying .nupkg files instead" |
| 191 | + mkdir -p "$HIVE_PATH" |
| 192 | + cp -f "$PKG_DIR"/*.nupkg "$HIVE_PATH"/ 2>/dev/null || true |
| 193 | + log "Created/updated hive '$HIVE_NAME' at $HIVE_PATH (copied packages)." |
| 194 | + fi |
| 195 | +fi |
| 196 | + |
| 197 | +echo |
| 198 | +log "Done." |
| 199 | +echo |
| 200 | +log "Aspire CLI will discover a channel named '$HIVE_NAME' from:" |
| 201 | +log " $HIVE_PATH" |
| 202 | +echo |
| 203 | +log "Channel behavior: Aspire* and Microsoft.Extensions.ServiceDiscovery* come from the hive; others from nuget.org." |
| 204 | +echo |
| 205 | +log "The Aspire CLI discovers channels automatically from the hives directory; no extra flags are required." |
0 commit comments