Improve release process and fix configuration issues

- Add proper --dry-run support to release.sh that prevents file modifications
- Fix iOS Info.plist to use $(MARKETING_VERSION) and $(CURRENT_PROJECT_VERSION)
- Update CHANGELOG.md lookup to prefer project root location
- Add comprehensive RELEASE_PROCESS.md documentation
- Improve error messages and parameter validation in release script
- Fix confusing warnings about CHANGELOG.md location

These changes make the release process more robust and less error-prone.
This commit is contained in:
Peter Steinberger 2025-07-08 02:08:23 +01:00
parent 5f4c870d67
commit 8c2fcc7488
3 changed files with 419 additions and 118 deletions

165
docs/RELEASE_PROCESS.md Normal file
View file

@ -0,0 +1,165 @@
# VibeTunnel Release Process
This document explains how to create releases for VibeTunnel, including the relationship between version numbers, release types, and the release script.
## Understanding Version Management
### Version Sources
1. **version.xcconfig** - Contains `MARKETING_VERSION` (e.g., `1.0.0-beta.7`)
2. **web/package.json** - Must match the marketing version
3. **Release script parameters** - Used for validation and GitHub release metadata
### Key Concept: Version vs Release Type
The release script **does NOT modify the version string** based on your parameters. Instead:
- The version comes from `version.xcconfig`
- Script parameters validate that your intent matches the configured version
- For pre-releases, the version must already include the suffix (e.g., `-beta.7`)
## Release Types
### Stable Release
```bash
# version.xcconfig must have: MARKETING_VERSION = 1.0.0
./scripts/release.sh stable
```
### Beta Release
```bash
# version.xcconfig must have: MARKETING_VERSION = 1.0.0-beta.7
./scripts/release.sh beta 7
```
### Alpha Release
```bash
# version.xcconfig must have: MARKETING_VERSION = 1.0.0-alpha.2
./scripts/release.sh alpha 2
```
### Release Candidate
```bash
# version.xcconfig must have: MARKETING_VERSION = 1.0.0-rc.1
./scripts/release.sh rc 1
```
## Pre-Release Workflow
1. **Update version.xcconfig** first:
```
MARKETING_VERSION = 1.0.0-beta.7
CURRENT_PROJECT_VERSION = 170
```
2. **Update web/package.json** to match:
```json
"version": "1.0.0-beta.7"
```
3. **Update CHANGELOG.md** with release notes
4. **Commit these changes**:
```bash
git add mac/VibeTunnel/version.xcconfig web/package.json CHANGELOG.md
git commit -m "Prepare for v1.0.0-beta.7 release"
git push
```
5. **Run the release script** with matching parameters:
```bash
cd mac
./scripts/release.sh beta 7
```
## Common Mistakes
### ❌ Wrong: Running release with just a number
```bash
./scripts/release.sh 7 # This treats "7" as an unknown release type
```
### ❌ Wrong: Mismatched parameters
```bash
# version.xcconfig has: 1.0.0-beta.7
./scripts/release.sh beta 8 # Error: expects beta.8 but version has beta.7
```
### ✅ Correct: Parameters match the version
```bash
# version.xcconfig has: 1.0.0-beta.7
./scripts/release.sh beta 7 # Success!
```
## Dry Run Mode
Test your release without making changes:
```bash
./scripts/release.sh beta 7 --dry-run
```
This will:
- Show what version would be released
- Validate all preconditions
- Display what actions would be taken
- NOT modify any files or create releases
## Release Script Actions
The release script automates:
1. **Validation**
- Ensures clean git state
- Verifies version consistency
- Checks signing certificates
- Validates build numbers
2. **Building**
- Cleans build artifacts
- Builds universal binary
- Sets `IS_PRERELEASE_BUILD` flag appropriately
3. **Signing & Notarization**
- Code signs the app
- Submits for Apple notarization
- Waits for approval
4. **Distribution**
- Creates signed DMG
- Generates GitHub release
- Updates appcast XML files
- Commits and pushes changes
## Version Numbering Guidelines
- **Stable**: `MAJOR.MINOR.PATCH` (e.g., `1.0.0`, `1.1.0`, `2.0.0`)
- **Beta**: `MAJOR.MINOR.PATCH-beta.N` (e.g., `1.0.0-beta.1`)
- **Alpha**: `MAJOR.MINOR.PATCH-alpha.N` (e.g., `1.0.0-alpha.1`)
- **RC**: `MAJOR.MINOR.PATCH-rc.N` (e.g., `1.0.0-rc.1`)
Build numbers must always increment, even across different release types.
## Troubleshooting
### "Version already contains pre-release suffix"
This is a warning, not an error. It means the script detected that version.xcconfig already has the correct suffix. The release will proceed normally.
### "Not up to date with origin/main"
Pull the latest changes:
```bash
git pull --rebase origin main
```
### "Build number X already exists"
Increment the build number in version.xcconfig.
### Script times out
The release process can take 10-15 minutes due to notarization. Use a longer timeout or run in a persistent terminal session.
## Resume Failed Releases
If a release fails partway through:
```bash
./scripts/release-resume.sh
```
This will pick up where the release left off, skipping completed steps.

View file

@ -15,9 +15,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>1</string>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>CFBundleDisplayName</key>
<string>VibeTunnel</string>
<key>LSRequiresIPhoneOS</key>

View file

@ -9,12 +9,15 @@
# and appcast updates. It supports both stable and pre-release versions.
#
# USAGE:
# ./scripts/release.sh <type> [number]
# ./scripts/release.sh [--dry-run] <type> [number]
#
# ARGUMENTS:
# type Release type: stable, beta, alpha, rc
# number Pre-release number (required for beta/alpha/rc)
#
# OPTIONS:
# --dry-run Preview what would be done without making changes
#
# IMPORTANT NOTES:
# - This script can take 10-15 minutes due to notarization
# - If running from Claude or other tools with timeouts, use a longer timeout
@ -60,6 +63,8 @@
# ./scripts/release.sh beta 1 # Create beta.1 release
# ./scripts/release.sh alpha 2 # Create alpha.2 release
# ./scripts/release.sh rc 1 # Create rc.1 release
# ./scripts/release.sh --dry-run stable # Preview stable release
# ./scripts/release.sh --dry-run beta 1 # Preview beta.1 release
#
# OUTPUT:
# - GitHub release at: https://github.com/amantus-ai/vibetunnel/releases
@ -81,49 +86,129 @@ YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# Parse arguments
RELEASE_TYPE="${1:-}"
PRERELEASE_NUMBER="${2:-}"
# Parse arguments and flags
DRY_RUN=false
RELEASE_TYPE=""
PRERELEASE_NUMBER=""
# Validate arguments
if [[ -z "$RELEASE_TYPE" ]]; then
echo -e "${RED}❌ Error: Release type required${NC}"
echo ""
# Function to show usage
show_usage() {
echo "Usage:"
echo " $0 stable # Create stable release"
echo " $0 beta <number> # Create beta.N release"
echo " $0 alpha <number> # Create alpha.N release"
echo " $0 rc <number> # Create rc.N release"
echo " $0 [--dry-run] <release-type> [number]"
echo ""
echo "Arguments:"
echo " release-type stable, beta, alpha, or rc"
echo " number Pre-release number (required for beta/alpha/rc)"
echo ""
echo "Options:"
echo " --dry-run Show what would be done without making changes"
echo ""
echo "Examples:"
echo " $0 stable"
echo " $0 beta 1"
echo " $0 rc 3"
echo " $0 stable # Create stable release"
echo " $0 beta 1 # Create beta.1 release"
echo " $0 alpha 2 # Create alpha.2 release"
echo " $0 rc 3 # Create rc.3 release"
echo " $0 --dry-run stable # Preview stable release"
echo " $0 --dry-run beta 1 # Preview beta.1 release"
}
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
--dry-run)
DRY_RUN=true
shift
;;
-h|--help)
show_usage
exit 0
;;
stable|beta|alpha|rc)
if [[ -n "$RELEASE_TYPE" ]]; then
echo -e "${RED}❌ Error: Release type already specified as '$RELEASE_TYPE'${NC}"
echo ""
show_usage
exit 1
fi
RELEASE_TYPE="$1"
shift
;;
*)
# Check if this could be a pre-release number
if [[ -n "$RELEASE_TYPE" ]] && [[ "$RELEASE_TYPE" != "stable" ]] && [[ -z "$PRERELEASE_NUMBER" ]]; then
# This might be intended as a pre-release number
PRERELEASE_NUMBER="$1"
shift
else
echo -e "${RED}❌ Error: Unknown argument: $1${NC}"
echo ""
show_usage
exit 1
fi
;;
esac
done
# Validate required arguments
if [[ -z "$RELEASE_TYPE" ]]; then
echo -e "${RED}❌ Error: Release type is required${NC}"
echo ""
show_usage
exit 1
fi
# Validate release type
case "$RELEASE_TYPE" in
stable|beta|alpha|rc)
# Valid release type
;;
*)
echo -e "${RED}❌ Error: Invalid release type: $RELEASE_TYPE${NC}"
echo "Valid types are: stable, beta, alpha, rc"
echo ""
show_usage
exit 1
;;
esac
# For pre-releases, validate number
if [[ "$RELEASE_TYPE" != "stable" ]]; then
if [[ -z "$PRERELEASE_NUMBER" ]]; then
echo -e "${RED}❌ Error: Pre-release number required for $RELEASE_TYPE${NC}"
echo -e "${RED}❌ Error: Pre-release number is required for $RELEASE_TYPE releases${NC}"
echo ""
echo "Example: $0 $RELEASE_TYPE 1"
echo ""
show_usage
exit 1
fi
# Validate that pre-release number is a positive integer
if ! [[ "$PRERELEASE_NUMBER" =~ ^[0-9]+$ ]] || [[ "$PRERELEASE_NUMBER" -eq 0 ]]; then
echo -e "${RED}❌ Error: Pre-release number must be a positive integer${NC}"
echo "Got: '$PRERELEASE_NUMBER'"
echo ""
show_usage
exit 1
fi
elif [[ -n "$PRERELEASE_NUMBER" ]]; then
echo -e "${YELLOW}⚠️ Warning: Pre-release number ignored for stable releases${NC}"
PRERELEASE_NUMBER=""
fi
echo -e "${BLUE}🚀 VibeTunnel Automated Release${NC}"
echo "=============================="
echo ""
# Show dry-run mode if enabled
if [[ "$DRY_RUN" == "true" ]]; then
echo -e "${YELLOW}🔍 DRY RUN MODE - No changes will be made${NC}"
echo ""
fi
# Additional strict pre-conditions before preflight check
echo -e "${BLUE}🔍 Running strict pre-conditions...${NC}"
# Check if CHANGELOG.md exists in mac directory
if [[ ! -f "$PROJECT_ROOT/CHANGELOG.md" ]]; then
echo -e "${YELLOW}⚠️ Warning: CHANGELOG.md not found in mac/ directory${NC}"
echo " The release script expects CHANGELOG.md to be in the mac/ directory"
echo " You can copy it from the project root: cp ../CHANGELOG.md ."
fi
# CHANGELOG.md will be checked later with proper fallback logic
# Clean up any stuck VibeTunnel volumes before starting
echo "🧹 Cleaning up any stuck DMG volumes..."
@ -189,19 +274,19 @@ if ! gh auth status >/dev/null 2>&1; then
exit 1
fi
# Check if changelog file exists
if [[ ! -f "$PROJECT_ROOT/CHANGELOG.md" ]]; then
echo -e "${YELLOW}⚠️ Warning: CHANGELOG.md not found in mac/ directory${NC}"
echo " Looking for it in project root..."
if [[ -f "$PROJECT_ROOT/../CHANGELOG.md" ]]; then
echo " Found CHANGELOG.md in project root"
CHANGELOG_PATH="$PROJECT_ROOT/../CHANGELOG.md"
else
echo " CHANGELOG.md not found anywhere - release notes will be basic"
CHANGELOG_PATH=""
fi
else
# Check if changelog file exists - prefer project root location
if [[ -f "$PROJECT_ROOT/../CHANGELOG.md" ]]; then
CHANGELOG_PATH="$PROJECT_ROOT/../CHANGELOG.md"
elif [[ -f "$PROJECT_ROOT/CHANGELOG.md" ]]; then
# Fallback to mac/ directory if it exists there
CHANGELOG_PATH="$PROJECT_ROOT/CHANGELOG.md"
else
echo -e "${YELLOW}⚠️ Warning: CHANGELOG.md not found${NC}"
echo " Searched in:"
echo " - Project root: $PROJECT_ROOT/../CHANGELOG.md"
echo " - Mac directory: $PROJECT_ROOT/CHANGELOG.md"
echo " Release notes will be basic"
CHANGELOG_PATH=""
fi
# Check if we're up to date with origin/main
@ -309,9 +394,6 @@ echo "✓ Cleaned all build artifacts"
echo ""
echo -e "${BLUE}📋 Step 3/8: Setting version...${NC}"
# Backup version.xcconfig
cp "$VERSION_CONFIG" "$VERSION_CONFIG.bak"
# Determine the version string to set
if [[ "$RELEASE_TYPE" == "stable" ]]; then
# For stable releases, ensure MARKETING_VERSION doesn't have pre-release suffix
@ -324,107 +406,161 @@ else
VERSION_TO_SET="$RELEASE_VERSION"
fi
# Update MARKETING_VERSION in version.xcconfig
echo "📝 Updating MARKETING_VERSION to: $VERSION_TO_SET"
sed -i '' "s/MARKETING_VERSION = .*/MARKETING_VERSION = $VERSION_TO_SET/" "$VERSION_CONFIG"
# Verify the update
NEW_MARKETING_VERSION=$(grep 'MARKETING_VERSION' "$VERSION_CONFIG" | sed 's/.*MARKETING_VERSION = //')
if [[ "$NEW_MARKETING_VERSION" != "$VERSION_TO_SET" ]]; then
echo -e "${RED}❌ Failed to update MARKETING_VERSION${NC}"
exit 1
if [[ "$DRY_RUN" == "true" ]]; then
echo "📝 Would update MARKETING_VERSION to: $VERSION_TO_SET"
echo " Current value: $MARKETING_VERSION"
echo -e "${GREEN}✅ Version would be set to: $VERSION_TO_SET${NC}"
else
# Backup version.xcconfig
cp "$VERSION_CONFIG" "$VERSION_CONFIG.bak"
# Update MARKETING_VERSION in version.xcconfig
echo "📝 Updating MARKETING_VERSION to: $VERSION_TO_SET"
sed -i '' "s/MARKETING_VERSION = .*/MARKETING_VERSION = $VERSION_TO_SET/" "$VERSION_CONFIG"
# Verify the update
NEW_MARKETING_VERSION=$(grep 'MARKETING_VERSION' "$VERSION_CONFIG" | sed 's/.*MARKETING_VERSION = //')
if [[ "$NEW_MARKETING_VERSION" != "$VERSION_TO_SET" ]]; then
echo -e "${RED}❌ Failed to update MARKETING_VERSION${NC}"
exit 1
fi
echo -e "${GREEN}✅ Version updated to: $VERSION_TO_SET${NC}"
fi
echo -e "${GREEN}✅ Version updated to: $VERSION_TO_SET${NC}"
# Check if Xcode project was modified and commit if needed
if ! git diff --quiet "$PROJECT_ROOT/VibeTunnel-Mac.xcodeproj/project.pbxproj"; then
echo "📝 Committing Xcode project changes..."
git add "$PROJECT_ROOT/VibeTunnel-Mac.xcodeproj/project.pbxproj"
git commit -m "Update Xcode project for build $BUILD_NUMBER"
echo -e "${GREEN}✅ Xcode project changes committed${NC}"
if [[ "$DRY_RUN" == "true" ]]; then
echo "📝 Would commit Xcode project changes"
echo " Commit message: Update Xcode project for build $BUILD_NUMBER"
else
echo "📝 Committing Xcode project changes..."
git add "$PROJECT_ROOT/VibeTunnel-Mac.xcodeproj/project.pbxproj"
git commit -m "Update Xcode project for build $BUILD_NUMBER"
echo -e "${GREEN}✅ Xcode project changes committed${NC}"
fi
fi
# Step 4: Build the app
echo ""
echo -e "${BLUE}📋 Step 4/8: Building universal application...${NC}"
# Check for custom Node.js build
echo ""
echo "🔍 Checking for custom Node.js build..."
WEB_DIR="$PROJECT_ROOT/../web"
CUSTOM_NODE_PATH=$(find "$WEB_DIR/.node-builds" -name "node-v*-minimal" -type d 2>/dev/null | sort -V | tail -n1)/out/Release/node
if [[ ! -f "$CUSTOM_NODE_PATH" ]]; then
echo -e "${YELLOW}⚠️ Custom Node.js not found. Building for optimal app size...${NC}"
echo " This will take 10-20 minutes on first run."
if [[ "$DRY_RUN" == "true" ]]; then
echo "🔨 Would build ARM64 binary with:"
echo " Configuration: Release"
echo " IS_PRERELEASE_BUILD: $([ "$RELEASE_TYPE" != "stable" ] && echo "YES" || echo "NO")"
echo " Command: $SCRIPT_DIR/build.sh --configuration Release"
echo ""
echo " Would verify:"
echo " - App exists at expected path"
echo " - Build number matches $BUILD_NUMBER"
echo " - Binary architecture is ARM64"
echo -e "${GREEN}✅ Build would be performed${NC}"
else
# Check for custom Node.js build
echo ""
echo "🔍 Checking for custom Node.js build..."
WEB_DIR="$PROJECT_ROOT/../web"
CUSTOM_NODE_PATH=$(find "$WEB_DIR/.node-builds" -name "node-v*-minimal" -type d 2>/dev/null | sort -V | tail -n1)/out/Release/node
# Build custom Node.js
pushd "$WEB_DIR" > /dev/null
if node build-custom-node.js --latest; then
echo -e "${GREEN}✅ Custom Node.js built successfully${NC}"
CUSTOM_NODE_PATH=$(find "$WEB_DIR/.node-builds" -name "node-v*-minimal" -type d 2>/dev/null | sort -V | tail -n1)/out/Release/node
if [[ -f "$CUSTOM_NODE_PATH" ]]; then
CUSTOM_NODE_SIZE=$(ls -lh "$CUSTOM_NODE_PATH" | awk '{print $5}')
echo " Size: $CUSTOM_NODE_SIZE (vs ~110MB for standard Node.js)"
if [[ ! -f "$CUSTOM_NODE_PATH" ]]; then
echo -e "${YELLOW}⚠️ Custom Node.js not found. Building for optimal app size...${NC}"
echo " This will take 10-20 minutes on first run."
# Build custom Node.js
pushd "$WEB_DIR" > /dev/null
if node build-custom-node.js --latest; then
echo -e "${GREEN}✅ Custom Node.js built successfully${NC}"
CUSTOM_NODE_PATH=$(find "$WEB_DIR/.node-builds" -name "node-v*-minimal" -type d 2>/dev/null | sort -V | tail -n1)/out/Release/node
if [[ -f "$CUSTOM_NODE_PATH" ]]; then
CUSTOM_NODE_SIZE=$(ls -lh "$CUSTOM_NODE_PATH" | awk '{print $5}')
echo " Size: $CUSTOM_NODE_SIZE (vs ~110MB for standard Node.js)"
fi
else
echo -e "${RED}❌ Failed to build custom Node.js${NC}"
echo " Continuing with standard Node.js (larger app size)"
fi
popd > /dev/null
else
echo -e "${RED}❌ Failed to build custom Node.js${NC}"
echo " Continuing with standard Node.js (larger app size)"
CUSTOM_NODE_SIZE=$(ls -lh "$CUSTOM_NODE_PATH" | awk '{print $5}')
CUSTOM_NODE_VERSION=$("$CUSTOM_NODE_PATH" --version 2>/dev/null || echo "unknown")
echo -e "${GREEN}✅ Found custom Node.js${NC}"
echo " Version: $CUSTOM_NODE_VERSION"
echo " Size: $CUSTOM_NODE_SIZE"
fi
popd > /dev/null
else
CUSTOM_NODE_SIZE=$(ls -lh "$CUSTOM_NODE_PATH" | awk '{print $5}')
CUSTOM_NODE_VERSION=$("$CUSTOM_NODE_PATH" --version 2>/dev/null || echo "unknown")
echo -e "${GREEN}✅ Found custom Node.js${NC}"
echo " Version: $CUSTOM_NODE_VERSION"
echo " Size: $CUSTOM_NODE_SIZE"
fi
# For pre-release builds, set the environment variable
if [[ "$RELEASE_TYPE" != "stable" ]]; then
echo "📝 Marking build as pre-release..."
export IS_PRERELEASE_BUILD=YES
else
export IS_PRERELEASE_BUILD=NO
fi
# Build ARM64 binary
echo ""
echo "🔨 Building ARM64 binary..."
"$SCRIPT_DIR/build.sh" --configuration Release
# Verify build
APP_PATH="$PROJECT_ROOT/build/Build/Products/Release/VibeTunnel.app"
if [[ ! -d "$APP_PATH" ]]; then
echo -e "${RED}❌ Build failed - app not found${NC}"
exit 1
fi
# Verify build number
BUILT_VERSION=$(defaults read "$APP_PATH/Contents/Info.plist" CFBundleVersion)
if [[ "$BUILT_VERSION" != "$BUILD_NUMBER" ]]; then
echo -e "${RED}❌ Build number mismatch! Expected $BUILD_NUMBER but got $BUILT_VERSION${NC}"
exit 1
fi
# Verify it's an ARM64 binary
APP_BINARY="$APP_PATH/Contents/MacOS/VibeTunnel"
if [[ -f "$APP_BINARY" ]]; then
ARCH_INFO=$(lipo -info "$APP_BINARY" 2>/dev/null || echo "")
if [[ "$ARCH_INFO" == *"arm64"* ]]; then
echo "✅ ARM64 binary created"
# For pre-release builds, set the environment variable
if [[ "$RELEASE_TYPE" != "stable" ]]; then
echo "📝 Marking build as pre-release..."
export IS_PRERELEASE_BUILD=YES
else
echo -e "${RED}❌ Error: Binary is not ARM64: $ARCH_INFO${NC}"
export IS_PRERELEASE_BUILD=NO
fi
# Build ARM64 binary
echo ""
echo "🔨 Building ARM64 binary..."
"$SCRIPT_DIR/build.sh" --configuration Release
# Verify build
APP_PATH="$PROJECT_ROOT/build/Build/Products/Release/VibeTunnel.app"
if [[ ! -d "$APP_PATH" ]]; then
echo -e "${RED}❌ Build failed - app not found${NC}"
exit 1
fi
# Verify build number
BUILT_VERSION=$(defaults read "$APP_PATH/Contents/Info.plist" CFBundleVersion)
if [[ "$BUILT_VERSION" != "$BUILD_NUMBER" ]]; then
echo -e "${RED}❌ Build number mismatch! Expected $BUILD_NUMBER but got $BUILT_VERSION${NC}"
exit 1
fi
# Verify it's an ARM64 binary
APP_BINARY="$APP_PATH/Contents/MacOS/VibeTunnel"
if [[ -f "$APP_BINARY" ]]; then
ARCH_INFO=$(lipo -info "$APP_BINARY" 2>/dev/null || echo "")
if [[ "$ARCH_INFO" == *"arm64"* ]]; then
echo "✅ ARM64 binary created"
else
echo -e "${RED}❌ Error: Binary is not ARM64: $ARCH_INFO${NC}"
exit 1
fi
fi
echo -e "${GREEN}✅ Build complete${NC}"
fi
echo -e "${GREEN}✅ Build complete${NC}"
# Step 5: Sign and notarize
echo ""
echo -e "${BLUE}📋 Step 5/8: Signing and notarizing...${NC}"
if [[ "$DRY_RUN" == "true" ]]; then
echo "🔐 Would sign and notarize the application"
echo " Command: $SCRIPT_DIR/sign-and-notarize.sh --sign-and-notarize"
echo -e "${GREEN}✅ Signing and notarization would be performed${NC}"
# For dry run, we need to exit early since we don't have actual build artifacts
echo ""
echo -e "${BLUE}📋 Remaining steps (would be performed):${NC}"
echo " 6/8: Creating DMG and ZIP"
echo " 7/8: Creating GitHub release with tag $TAG_NAME"
echo " 8/8: Updating appcast files"
echo " 9/9: Committing and pushing changes"
echo ""
echo "📦 Release summary:"
echo " Type: $RELEASE_TYPE"
echo " Version: $RELEASE_VERSION"
echo " Build: $BUILD_NUMBER"
echo " Tag: $TAG_NAME"
echo ""
echo -e "${GREEN}🎉 Dry run complete!${NC}"
echo ""
echo "To perform the actual release, run without --dry-run:"
echo " $0 $RELEASE_TYPE${PRERELEASE_NUMBER:+ $PRERELEASE_NUMBER}"
exit 0
fi
"$SCRIPT_DIR/sign-and-notarize.sh" --sign-and-notarize
# Verify Sparkle component signing