Skip to content

Commit c22e1b5

Browse files
authored
Add localhive.sh script for building NuGet packages and managing Aspire CLI hives (#10905)
* Add localhive.sh script for building NuGet packages and managing Aspire CLI hives * Improve localhive.sh to build and pack NuGet packages in a single step
1 parent 44ecec4 commit c22e1b5

File tree

1 file changed

+205
-0
lines changed

1 file changed

+205
-0
lines changed

localhive.sh

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
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

Comments
 (0)