this repo has no description
at main 244 lines 6.3 kB view raw
1#!/bin/bash 2set -euo pipefail 3 4WORKFLOW_NAME="Release" 5WORKFLOW_FILE="release.yml" 6WORKFLOW_IDENTIFIER="$WORKFLOW_FILE" 7 8log_info() { printf '\n🔷 %s\n' "$1"; } 9log_error() { printf '❌ %s\n' "$1" >&2; exit 1; } 10log_success() { printf '\n✅ %s\n' "$1"; } 11 12usage() { 13 cat <<'USAGE' 14AXe Release Helper 15 16Usage: scripts/create-release.sh [VERSION|major|minor|patch] [--notes-file FILE] [--dry-run] 17 scripts/create-release.sh --help 18 19Arguments: 20 VERSION Explicit semantic version (e.g. 1.4.0 or 1.5.0-beta.1) 21 major|minor|patch Semantic version bump type (defaults to minor when omitted) 22 23Options: 24 --notes-file FILE Read release notes from FILE instead of prompting 25 --dry-run Preview actions without pushing 26 -h, --help Show this help text 27USAGE 28} 29 30ensure_command() { 31 if ! command -v "$1" >/dev/null 2>&1; then 32 log_error "$1 not found. Please install it first." 33 fi 34} 35 36# --- Version helpers --- 37parse_version() { 38 echo "$1" | sed -E 's/^([0-9]+)\.([0-9]+)\.([0-9]+)(-.*)?$/\1 \2 \3 \4/' 39} 40 41bump_version() { 42 local current_version=$1 43 local bump_type=$2 44 45 if [[ -z "$current_version" ]]; then 46 case $bump_type in 47 major) echo "1.0.0" ;; 48 minor) echo "0.1.0" ;; 49 patch) echo "0.0.1" ;; 50 esac 51 return 52 fi 53 54 local parsed=($(parse_version "$current_version")) 55 local major=${parsed[0]} 56 local minor=${parsed[1]} 57 local patch=${parsed[2]} 58 59 case $bump_type in 60 major) echo "$((major + 1)).0.0" ;; 61 minor) echo "${major}.$((minor + 1)).0" ;; 62 patch) echo "${major}.${minor}.$((patch + 1))" ;; 63 *) log_error "Unknown bump type: $bump_type" ;; 64 esac 65} 66 67latest_version() { 68 git tag | grep -E '^v?[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?$' | sed 's/^v//' | sort -V | tail -1 69} 70 71# --- Parse arguments --- 72VERSION="" 73BUMP_TYPE="" 74NOTES_FILE="" 75DRY_RUN=false 76 77while [[ $# -gt 0 ]]; do 78 case "$1" in 79 --notes-file) 80 shift 81 [[ $# -gt 0 ]] || log_error "--notes-file requires a path" 82 NOTES_FILE=$1 83 ;; 84 --dry-run) 85 DRY_RUN=true 86 ;; 87 -h|--help) 88 usage 89 exit 0 90 ;; 91 major|minor|patch) 92 if [[ -n "$VERSION" ]]; then 93 log_error "Cannot specify both explicit version and bump type." 94 fi 95 BUMP_TYPE=$1 96 ;; 97 *) 98 if [[ -z "$VERSION" ]]; then 99 VERSION=$1 100 else 101 log_error "Unexpected argument: $1" 102 fi 103 ;; 104 esac 105 shift || true 106done 107 108ensure_command gh 109ensure_command jq 110 111if ! gh auth status >/dev/null 2>&1; then 112 log_error "GitHub CLI is not authenticated. Run 'gh auth login'." 113fi 114 115REPO_NAME=$(gh repo view --json nameWithOwner -q .nameWithOwner) 116 117git fetch --tags >/dev/null 2>&1 || true 118 119LATEST_VERSION=$(latest_version) 120if [[ -n "$LATEST_VERSION" ]]; then 121 log_info "Latest version: $LATEST_VERSION" 122else 123 log_info "No previous versions found." 124fi 125 126if [[ -n "$VERSION" && -n "$BUMP_TYPE" ]]; then 127 log_error "Specify either an explicit version or a bump type, not both." 128fi 129 130if [[ -n "$BUMP_TYPE" ]]; then 131 VERSION=$(bump_version "$LATEST_VERSION" "$BUMP_TYPE") 132 log_info "Bump type '$BUMP_TYPE' selected -> $VERSION" 133fi 134 135if [[ -z "$VERSION" ]]; then 136 DEFAULT_VERSION=$(bump_version "$LATEST_VERSION" "minor") 137 read -rp "Enter version [$DEFAULT_VERSION]: " VERSION_INPUT 138 if [[ -z "$VERSION_INPUT" ]]; then 139 VERSION=$DEFAULT_VERSION 140 else 141 VERSION=$VERSION_INPUT 142 fi 143fi 144 145[[ -n "$VERSION" ]] || log_error "No version provided." 146 147if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?$ ]]; then 148 log_error "Invalid version format '$VERSION'." 149fi 150 151TAG_NAME="v$VERSION" 152 153if git rev-parse "$TAG_NAME" >/dev/null 2>&1; then 154 log_error "Tag $TAG_NAME already exists locally." 155fi 156 157if git ls-remote --tags origin | grep -q "refs/tags/$TAG_NAME$"; then 158 log_error "Tag $TAG_NAME already exists on origin." 159fi 160 161if ! git diff-index --quiet HEAD --; then 162 log_error "Working tree is not clean. Commit or stash changes first." 163fi 164 165if [[ -n "$NOTES_FILE" ]]; then 166 [[ -f "$NOTES_FILE" ]] || log_error "Release notes file not found: $NOTES_FILE" 167 RELEASE_NOTES=$(<"$NOTES_FILE") 168else 169 log_info "Enter release notes (Ctrl+D to finish):" 170 RELEASE_NOTES=$(cat) 171fi 172 173if [[ -z "$RELEASE_NOTES" ]]; then 174 RELEASE_NOTES="Release $TAG_NAME" 175fi 176 177log_info "Preparing release for $TAG_NAME" 178log_info "Workflow: $WORKFLOW_NAME" 179 180TMP_NOTES=$(mktemp) 181printf '%s\n' "$RELEASE_NOTES" > "$TMP_NOTES" 182trap 'rm -f "$TMP_NOTES"' EXIT 183 184if $DRY_RUN; then 185 log_info "[dry-run] Would create annotated tag $TAG_NAME" 186else 187 git tag -a "$TAG_NAME" -F "$TMP_NOTES" 188 log_success "Tag $TAG_NAME created." 189fi 190 191CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) 192HEAD_SHA=$(git rev-parse HEAD) 193 194if $DRY_RUN; then 195 log_info "[dry-run] Would push branch $CURRENT_BRANCH and tag $TAG_NAME" 196else 197 git push origin "$CURRENT_BRANCH" 198 git push origin "$TAG_NAME" 199 log_success "Tag $TAG_NAME pushed to origin." 200fi 201 202if $DRY_RUN; then 203 log_info "[dry-run] Skipping workflow monitoring." 204 exit 0 205fi 206 207log_info "Waiting for GitHub Actions workflow to start..." 208RUN_ID="" 209FALLBACK_USED=false 210for attempt in {1..30}; do 211 RUN_JSON=$(gh run list --workflow "$WORKFLOW_IDENTIFIER" --limit 10 --json databaseId,headSha,status,event 2>/dev/null || true) 212 if [[ -z "$RUN_JSON" ]]; then 213 RUN_JSON="[]" 214 fi 215 RUN_ID=$(echo "$RUN_JSON" | jq -r ".[] | select(.headSha == \"$HEAD_SHA\") | .databaseId" | head -n1) 216 if [[ -z "$RUN_ID" && "$WORKFLOW_IDENTIFIER" == "$WORKFLOW_FILE" && "$FALLBACK_USED" == "false" ]]; then 217 WORKFLOW_IDENTIFIER="$WORKFLOW_NAME" 218 FALLBACK_USED=true 219 continue 220 fi 221 if [[ -n "$RUN_ID" ]]; then 222 break 223 fi 224 sleep 10 225done 226 227if [[ -z "$RUN_ID" ]]; then 228 log_error "Could not find workflow run for commit $HEAD_SHA. Monitor manually on GitHub." 229fi 230 231log_info "Monitoring workflow run ID $RUN_ID..." 232if gh run watch "$RUN_ID" --exit-status; then 233 log_success "Workflow succeeded!" 234 log_info "Release will be published automatically." 235 log_info "https://github.com/$REPO_NAME/releases/tag/$TAG_NAME" 236else 237 log_info "Workflow failed. Cleaning up..." 238 if gh release view "$TAG_NAME" >/dev/null 2>&1; then 239 gh release delete "$TAG_NAME" --yes || true 240 fi 241 git push origin ":refs/tags/$TAG_NAME" || true 242 git tag -d "$TAG_NAME" || true 243 log_error "Release workflow failed. Fix issues and rerun the script with version $VERSION." 244fi