#!/bin/bash # ============================================================================= # VibeTunnel Pre-flight Check Script # ============================================================================= # # This script validates that everything is ready for a VibeTunnel release by # performing comprehensive checks on git status, build configuration, tools, # certificates, and the IS_PRERELEASE_BUILD system. # # USAGE: # ./scripts/preflight-check.sh # # VALIDATION CHECKS: # - Git repository status (clean working tree, main branch, synced) # - Version information and build number validation # - Required development tools (Rust, Node.js, GitHub CLI, Sparkle tools) # - Code signing certificates and notarization credentials # - Sparkle configuration (keys, appcast files) # - IS_PRERELEASE_BUILD system configuration # # EXIT CODES: # 0 All checks passed - ready to release # 1 Some checks failed - fix issues before releasing # # DEPENDENCIES: # - git (repository management) # - cargo/rustup (Rust toolchain) # - node/pnpm (web frontend build) # - gh (GitHub CLI) # - sign_update (Sparkle EdDSA signing) # - xcbeautify (optional, build output formatting) # - security (keychain access for certificates) # - xmllint (appcast validation) # # ENVIRONMENT VARIABLES: # APP_STORE_CONNECT_API_KEY_P8 App Store Connect API key (for notarization) # APP_STORE_CONNECT_KEY_ID API Key ID # APP_STORE_CONNECT_ISSUER_ID API Key Issuer ID # # EXAMPLES: # ./scripts/preflight-check.sh # # ============================================================================= set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" # Colors for output GREEN='\033[0;32m' YELLOW='\033[1;33m' RED='\033[0;31m' NC='\033[0m' # No Color # Track if any checks fail CHECKS_PASSED=true echo "🔍 VibeTunnel Release Pre-flight Check" echo "====================================" echo "" # Function to print check results check_pass() { echo -e "${GREEN}✅ PASS${NC}: $1" } check_fail() { echo -e "${RED}❌ FAIL${NC}: $1" CHECKS_PASSED=false } check_warn() { echo -e "${YELLOW}⚠️ WARN${NC}: $1" } # 1. Check Git status echo "📌 Git Status:" # Refresh the index to avoid false positives git update-index --refresh >/dev/null 2>&1 || true if git diff-index --quiet HEAD -- 2>/dev/null; then check_pass "Working directory is clean" else check_fail "Uncommitted changes detected" git status --short fi # Check if on main branch CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) if [[ "$CURRENT_BRANCH" == "main" ]]; then check_pass "On main branch" else check_warn "Not on main branch (current: $CURRENT_BRANCH)" fi # Check if up to date with remote git fetch origin main --quiet LOCAL=$(git rev-parse HEAD) REMOTE=$(git rev-parse origin/main) if [[ "$LOCAL" == "$REMOTE" ]]; then check_pass "Up to date with origin/main" else check_fail "Not synced with origin/main" fi echo "" # 2. Check version information echo "📌 Version Information:" VERSION_CONFIG="$PROJECT_ROOT/VibeTunnel/version.xcconfig" if [[ -f "$VERSION_CONFIG" ]]; then MARKETING_VERSION=$(grep 'MARKETING_VERSION' "$VERSION_CONFIG" | sed 's/.*MARKETING_VERSION = //') BUILD_NUMBER=$(grep 'CURRENT_PROJECT_VERSION' "$VERSION_CONFIG" | sed 's/.*CURRENT_PROJECT_VERSION = //') echo " Marketing Version: $MARKETING_VERSION" echo " Build Number: $BUILD_NUMBER" check_pass "Version configuration found in version.xcconfig" else check_fail "Version configuration file not found at $VERSION_CONFIG" MARKETING_VERSION="" BUILD_NUMBER="" fi # Check for existing pre-release suffix in version if [[ -n "$MARKETING_VERSION" ]] && [[ "$MARKETING_VERSION" =~ -([a-zA-Z]+)\.([0-9]+)$ ]]; then SUFFIX_TYPE="${BASH_REMATCH[1]}" SUFFIX_NUMBER="${BASH_REMATCH[2]}" check_warn "Version already contains pre-release suffix: $MARKETING_VERSION" echo " Pre-release type: $SUFFIX_TYPE" echo " Pre-release number: $SUFFIX_NUMBER" echo " ⚠️ Make sure to use matching arguments with release.sh" echo " Example: ./scripts/release.sh $SUFFIX_TYPE $SUFFIX_NUMBER" fi echo "" # 3. Check build numbers echo "📌 Build Number Validation:" USED_BUILD_NUMBERS="" if [[ -f "$PROJECT_ROOT/../appcast.xml" ]]; then APPCAST_BUILDS=$(grep -E '[0-9]+' "$PROJECT_ROOT/../appcast.xml" 2>/dev/null | sed 's/.*\([0-9]*\)<\/sparkle:version>.*/\1/' | tr '\n' ' ' || true) USED_BUILD_NUMBERS+="$APPCAST_BUILDS" fi if [[ -f "$PROJECT_ROOT/../appcast-prerelease.xml" ]]; then PRERELEASE_BUILDS=$(grep -E '[0-9]+' "$PROJECT_ROOT/../appcast-prerelease.xml" 2>/dev/null | sed 's/.*\([0-9]*\)<\/sparkle:version>.*/\1/' | tr '\n' ' ' || true) USED_BUILD_NUMBERS+="$PRERELEASE_BUILDS" fi # Find highest build number HIGHEST_BUILD=0 for EXISTING_BUILD in $USED_BUILD_NUMBERS; do if [[ "$EXISTING_BUILD" -gt "$HIGHEST_BUILD" ]]; then HIGHEST_BUILD=$EXISTING_BUILD fi done if [[ -z "$USED_BUILD_NUMBERS" ]]; then check_pass "No existing builds found" else echo " Existing builds: $USED_BUILD_NUMBERS" echo " Highest build: $HIGHEST_BUILD" # Check for duplicates for EXISTING_BUILD in $USED_BUILD_NUMBERS; do if [[ "$BUILD_NUMBER" == "$EXISTING_BUILD" ]]; then check_fail "Build number $BUILD_NUMBER already exists!" fi done # Check if monotonically increasing if [[ "$BUILD_NUMBER" -gt "$HIGHEST_BUILD" ]]; then check_pass "Build number $BUILD_NUMBER is valid (> $HIGHEST_BUILD)" else check_fail "Build number must be > $HIGHEST_BUILD" fi fi echo "" # Check if Xcode project uses version.xcconfig echo "📌 Xcode Project Configuration:" XCODEPROJ="$PROJECT_ROOT/VibeTunnel-Mac.xcodeproj/project.pbxproj" if [[ -f "$XCODEPROJ" ]]; then if grep -q "version.xcconfig" "$XCODEPROJ"; then check_pass "Xcode project references version.xcconfig" # Check if MARKETING_VERSION uses variable expansion if grep -q 'MARKETING_VERSION = "$(MARKETING_VERSION)"' "$XCODEPROJ"; then check_pass "MARKETING_VERSION uses version.xcconfig value" else check_warn "MARKETING_VERSION may not use version.xcconfig value" echo " Consider updating to: MARKETING_VERSION = \"\$(MARKETING_VERSION)\"" fi # Check if CURRENT_PROJECT_VERSION uses variable expansion if grep -q 'CURRENT_PROJECT_VERSION = "$(CURRENT_PROJECT_VERSION)"' "$XCODEPROJ" || grep -q 'CURRENT_PROJECT_VERSION = $(CURRENT_PROJECT_VERSION)' "$XCODEPROJ"; then check_pass "CURRENT_PROJECT_VERSION uses version.xcconfig value" else check_warn "CURRENT_PROJECT_VERSION may not use version.xcconfig value" echo " Consider updating to use version.xcconfig" fi else check_fail "Xcode project doesn't reference version.xcconfig - versions may not match!" fi else check_fail "Xcode project file not found" fi echo "" # 4. Check required tools echo "📌 Required Tools:" # Rust toolchain if command -v cargo &> /dev/null; then check_pass "Rust toolchain installed" else check_fail "Rust not installed - visit https://rustup.rs" fi # Node.js if command -v node &> /dev/null; then check_pass "Node.js installed" else check_fail "Node.js not installed - required for web frontend build" fi # GitHub CLI if command -v gh &> /dev/null; then check_pass "GitHub CLI (gh) installed" if gh auth status &> /dev/null; then check_pass "GitHub CLI authenticated" else check_fail "GitHub CLI not authenticated - run: gh auth login" fi else check_fail "GitHub CLI not installed - run: brew install gh" fi # Sparkle tools if [[ -f "$HOME/.local/bin/sign_update" ]]; then check_pass "Sparkle sign_update installed" else check_fail "Sparkle tools not installed - see RELEASE.md" fi # xcbeautify (optional but recommended) if command -v xcbeautify &> /dev/null; then check_pass "xcbeautify installed" else check_warn "xcbeautify not installed (optional) - run: brew install xcbeautify" fi echo "" # 5. Check signing configuration echo "📌 Signing Configuration:" # Check for Developer ID certificate if security find-identity -v -p codesigning | grep -q "Developer ID Application"; then check_pass "Developer ID certificate found" else check_fail "No Developer ID certificate found" fi # Check for notarization credentials if [[ -n "${APP_STORE_CONNECT_API_KEY_P8:-}" ]]; then check_pass "Notarization API key configured" else check_warn "Notarization API key not in environment" fi echo "" # 6. Check Sparkle configuration echo "📌 Sparkle Configuration:" # Check public key PUBLIC_KEY_FILE="$PROJECT_ROOT/VibeTunnel/sparkle-public-ed-key.txt" if [[ -f "$PUBLIC_KEY_FILE" ]]; then PUBLIC_KEY=$(cat "$PUBLIC_KEY_FILE" | tr -d '\n') if [[ -n "$PUBLIC_KEY" ]]; then check_pass "Sparkle public key configured" else check_fail "Sparkle public key file is empty" fi else check_fail "Sparkle public key file not found at $PUBLIC_KEY_FILE" fi # Check private key in keychain export PATH="$HOME/.local/bin:$PATH" if command -v generate_keys &> /dev/null && generate_keys -p &>/dev/null; then check_pass "Sparkle private key found in Keychain" else check_fail "Sparkle private key not found in Keychain - run: generate_keys" fi echo "" # 7. Check appcast files echo "📌 Appcast Files:" if [[ -f "$PROJECT_ROOT/../appcast.xml" ]]; then if xmllint --noout "$PROJECT_ROOT/../appcast.xml" 2>/dev/null; then check_pass "appcast.xml is valid XML" else check_fail "appcast.xml has XML errors" fi else check_warn "appcast.xml not found (OK if no stable releases yet)" fi if [[ -f "$PROJECT_ROOT/../appcast-prerelease.xml" ]]; then if xmllint --noout "$PROJECT_ROOT/../appcast-prerelease.xml" 2>/dev/null; then check_pass "appcast-prerelease.xml is valid XML" else check_fail "appcast-prerelease.xml has XML errors" fi else check_warn "appcast-prerelease.xml not found (OK if no pre-releases yet)" fi echo "" # 8. Check IS_PRERELEASE_BUILD Configuration echo "📌 IS_PRERELEASE_BUILD System:" # Check if IS_PRERELEASE_BUILD is configured in Info.plist if grep -q 'IS_PRERELEASE_BUILD' "$PROJECT_ROOT/VibeTunnel-Info.plist" || grep -q 'IS_PRERELEASE_BUILD' "$PROJECT_ROOT/VibeTunnel/Info.plist" 2>/dev/null; then check_pass "IS_PRERELEASE_BUILD flag configured in Info.plist" else check_warn "IS_PRERELEASE_BUILD flag not found in Info.plist (will be set at build time)" fi # Check if UpdateChannel.swift has the flag detection logic if grep -q "Bundle.main.object.*IS_PRERELEASE_BUILD" "$PROJECT_ROOT/VibeTunnel/Core/Models/UpdateChannel.swift"; then check_pass "UpdateChannel has IS_PRERELEASE_BUILD detection logic" else check_fail "UpdateChannel.swift missing IS_PRERELEASE_BUILD flag detection" fi # Check if release script sets the environment variable if grep -q "export IS_PRERELEASE_BUILD=" "$PROJECT_ROOT/scripts/release.sh"; then check_pass "Release script sets IS_PRERELEASE_BUILD environment variable" else check_fail "Release script missing IS_PRERELEASE_BUILD environment variable setup" fi # Check if AppBehaviorSettingsManager uses defaultChannel APP_BEHAVIOR_SETTINGS="$PROJECT_ROOT/VibeTunnel/Core/Services/Settings/AppBehaviorSettingsManager.swift" if [[ -f "$APP_BEHAVIOR_SETTINGS" ]]; then if grep -q "UpdateChannel.defaultChannel" "$APP_BEHAVIOR_SETTINGS"; then check_pass "AppBehaviorSettingsManager uses UpdateChannel.defaultChannel()" else check_fail "AppBehaviorSettingsManager not using UpdateChannel.defaultChannel() for auto-detection" fi else check_warn "AppBehaviorSettingsManager.swift not found - skipping UpdateChannel check" fi echo "" # 9. Summary echo "📊 Pre-flight Summary:" echo "====================" if [[ "$CHECKS_PASSED" == true ]]; then echo -e "${GREEN}✅ All critical checks passed!${NC}" echo "" echo "Ready to release:" echo " Version: $MARKETING_VERSION" echo " Build: $BUILD_NUMBER" echo "" echo "Next steps:" echo " - For beta: ./scripts/release.sh beta 1" echo " - For stable: ./scripts/release.sh stable" exit 0 else echo -e "${RED}❌ Some checks failed. Please fix the issues above.${NC}" exit 1 fi