496 lines
15 KiB
Bash
Executable File
496 lines
15 KiB
Bash
Executable File
#!/bin/bash
|
||
set -e
|
||
|
||
# Colors for output
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
BLUE='\033[0;34m'
|
||
NC='\033[0m' # No Color
|
||
|
||
# Configuration paths
|
||
INFO_PLIST="Gaze/Info.plist"
|
||
ENTITLEMENTS="Gaze/Gaze.entitlements"
|
||
PROJECT_FILE="Gaze.xcodeproj/project.pbxproj"
|
||
PACKAGE_RESOLVED="Gaze.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved"
|
||
BACKUP_DIR=".distribution_configs"
|
||
|
||
# Distribution configurations
|
||
APPSTORE_CONFIG="${BACKUP_DIR}/appstore"
|
||
SELF_CONFIG="${BACKUP_DIR}/self"
|
||
|
||
# Sparkle package details
|
||
SPARKLE_REPO="https://github.com/sparkle-project/Sparkle"
|
||
SPARKLE_VERSION="2.8.1"
|
||
|
||
# Function to print colored messages
|
||
print_info() {
|
||
echo -e "${BLUE}ℹ${NC} $1"
|
||
}
|
||
|
||
print_success() {
|
||
echo -e "${GREEN}✓${NC} $1"
|
||
}
|
||
|
||
print_warning() {
|
||
echo -e "${YELLOW}⚠${NC} $1"
|
||
}
|
||
|
||
print_error() {
|
||
echo -e "${RED}✗${NC} $1"
|
||
}
|
||
|
||
# Function to check if Sparkle is in Package.resolved
|
||
has_sparkle_package() {
|
||
if [ -f "${PACKAGE_RESOLVED}" ]; then
|
||
grep -q "Sparkle" "${PACKAGE_RESOLVED}"
|
||
return $?
|
||
fi
|
||
return 1
|
||
}
|
||
|
||
# Function to remove Sparkle from Xcode project
|
||
remove_sparkle_package() {
|
||
print_info "Removing Sparkle package dependency..."
|
||
|
||
# Check if Sparkle exists in the project
|
||
if ! has_sparkle_package && ! grep -q "Sparkle" "${PROJECT_FILE}"; then
|
||
print_info "Sparkle already removed"
|
||
return 0
|
||
fi
|
||
|
||
# Backup project file
|
||
cp "${PROJECT_FILE}" "${PROJECT_FILE}.backup"
|
||
|
||
# Remove Sparkle from Package.resolved
|
||
if [ -f "${PACKAGE_RESOLVED}" ]; then
|
||
python3 -c "
|
||
import json
|
||
|
||
try:
|
||
with open('${PACKAGE_RESOLVED}', 'r') as f:
|
||
data = json.load(f)
|
||
|
||
# Filter out Sparkle from pins
|
||
if 'pins' in data:
|
||
data['pins'] = [pin for pin in data['pins'] if 'sparkle' not in pin.get('identity', '').lower()]
|
||
|
||
with open('${PACKAGE_RESOLVED}', 'w') as f:
|
||
json.dump(data, f, indent=2)
|
||
|
||
print('✓ Updated Package.resolved')
|
||
except Exception as e:
|
||
print(f'Error: {e}', file=sys.stderr)
|
||
sys.exit(1)
|
||
"
|
||
if [ $? -ne 0 ]; then
|
||
print_error "Failed to update Package.resolved"
|
||
mv "${PROJECT_FILE}.backup" "${PROJECT_FILE}"
|
||
return 1
|
||
fi
|
||
fi
|
||
|
||
# Use Python script to safely remove Sparkle from project.pbxproj
|
||
if ! python3 .manage_sparkle.py remove "${PROJECT_FILE}"; then
|
||
print_error "Failed to update project.pbxproj"
|
||
mv "${PROJECT_FILE}.backup" "${PROJECT_FILE}"
|
||
return 1
|
||
fi
|
||
|
||
rm -f "${PROJECT_FILE}.backup"
|
||
|
||
print_success "Removed Sparkle package dependency"
|
||
print_info "Xcode will resolve packages on next build"
|
||
}
|
||
|
||
# Function to add Sparkle to Xcode project
|
||
add_sparkle_package() {
|
||
print_info "Adding Sparkle package dependency..."
|
||
|
||
# Check if Sparkle package reference already fully exists (9 references like Lottie)
|
||
local sparkle_count=$(grep -c "Sparkle" "${PROJECT_FILE}" 2>/dev/null || echo "0")
|
||
sparkle_count=$(echo "$sparkle_count" | tr -d '[:space:]')
|
||
if [ "$sparkle_count" -ge 9 ]; then
|
||
print_info "Sparkle already fully configured"
|
||
return 0
|
||
fi
|
||
|
||
# Backup project file
|
||
cp "${PROJECT_FILE}" "${PROJECT_FILE}.backup"
|
||
|
||
# Remove any partial Sparkle from Package.resolved if it exists (Xcode will resolve it fresh)
|
||
if [ -f "${PACKAGE_RESOLVED}" ] && has_sparkle_package; then
|
||
python3 -c "
|
||
import json
|
||
|
||
try:
|
||
with open('${PACKAGE_RESOLVED}', 'r') as f:
|
||
data = json.load(f)
|
||
|
||
# Filter out Sparkle from pins - Xcode will resolve it fresh
|
||
if 'pins' in data:
|
||
data['pins'] = [pin for pin in data['pins'] if 'sparkle' not in pin.get('identity', '').lower()]
|
||
|
||
with open('${PACKAGE_RESOLVED}', 'w') as f:
|
||
json.dump(data, f, indent=2)
|
||
|
||
print('✓ Cleaned Package.resolved (Xcode will resolve Sparkle)')
|
||
except Exception as e:
|
||
print(f'Warning: {e}', file=sys.stderr)
|
||
"
|
||
fi
|
||
|
||
# Use Python script to safely add Sparkle to project.pbxproj
|
||
if ! python3 .manage_sparkle.py add "${PROJECT_FILE}"; then
|
||
print_error "Failed to update project.pbxproj"
|
||
mv "${PROJECT_FILE}.backup" "${PROJECT_FILE}"
|
||
return 1
|
||
fi
|
||
|
||
rm -f "${PROJECT_FILE}.backup"
|
||
|
||
print_success "Added Sparkle package dependency"
|
||
print_info "Run './run build' or open Xcode to resolve packages"
|
||
|
||
return 0
|
||
}
|
||
|
||
# Function to create backup directories
|
||
create_backup_dirs() {
|
||
mkdir -p "${APPSTORE_CONFIG}"
|
||
mkdir -p "${SELF_CONFIG}"
|
||
}
|
||
|
||
# Function to backup current configuration
|
||
backup_current_config() {
|
||
local config_name=$1
|
||
local config_dir=$2
|
||
|
||
print_info "Backing up ${config_name} configuration..."
|
||
|
||
# Backup Info.plist
|
||
if [ -f "${INFO_PLIST}" ]; then
|
||
cp "${INFO_PLIST}" "${config_dir}/Info.plist"
|
||
fi
|
||
|
||
# Backup entitlements
|
||
if [ -f "${ENTITLEMENTS}" ]; then
|
||
cp "${ENTITLEMENTS}" "${config_dir}/Gaze.entitlements"
|
||
fi
|
||
|
||
# Backup relevant parts of project.pbxproj (just the Release config)
|
||
if [ -f "${PROJECT_FILE}" ]; then
|
||
# Extract the Release configuration section
|
||
awk '/27A21B5F2F0F69DD0018C4F3 \/\* Release \*\/ = {/,/name = Release;/ {print}' "${PROJECT_FILE}" > "${config_dir}/project_release.txt"
|
||
fi
|
||
|
||
print_success "Backed up ${config_name} configuration"
|
||
}
|
||
|
||
# Function to restore configuration
|
||
restore_config() {
|
||
local config_name=$1
|
||
local config_dir=$2
|
||
|
||
print_info "Restoring ${config_name} configuration..."
|
||
|
||
# Restore Info.plist
|
||
if [ -f "${config_dir}/Info.plist" ]; then
|
||
cp "${config_dir}/Info.plist" "${INFO_PLIST}"
|
||
print_success "Restored Info.plist"
|
||
else
|
||
print_warning "No Info.plist backup found for ${config_name}"
|
||
fi
|
||
|
||
# Restore entitlements
|
||
if [ -f "${config_dir}/Gaze.entitlements" ]; then
|
||
cp "${config_dir}/Gaze.entitlements" "${ENTITLEMENTS}"
|
||
print_success "Restored entitlements"
|
||
else
|
||
print_warning "No entitlements backup found for ${config_name}"
|
||
fi
|
||
|
||
# Restore project.pbxproj Release configuration
|
||
if [ -f "${config_dir}/project_release.txt" ]; then
|
||
# Check if we're restoring to appstore or self mode based on file content
|
||
if grep -q "OTHER_SWIFT_FLAGS.*APPSTORE" "${config_dir}/project_release.txt"; then
|
||
# Add APPSTORE flag to both Debug and Release
|
||
if ! grep -q "OTHER_SWIFT_FLAGS.*APPSTORE" "${PROJECT_FILE}"; then
|
||
# Add to Debug configuration
|
||
sed -i.backup '/27A21B5E2F0F69DD0018C4F3 \/\* Debug \*\/ = {/,/name = Debug;/{
|
||
/MARKETING_VERSION = /a\
|
||
OTHER_SWIFT_FLAGS = "-D APPSTORE";
|
||
}' "${PROJECT_FILE}"
|
||
# Add to Release configuration
|
||
sed -i.backup2 '/27A21B5F2F0F69DD0018C4F3 \/\* Release \*\/ = {/,/name = Release;/{
|
||
/MARKETING_VERSION = /a\
|
||
OTHER_SWIFT_FLAGS = "-D APPSTORE";
|
||
}' "${PROJECT_FILE}"
|
||
rm -f "${PROJECT_FILE}.backup" "${PROJECT_FILE}.backup2"
|
||
print_success "Added APPSTORE compiler flag"
|
||
fi
|
||
else
|
||
# Remove APPSTORE flag from both Debug and Release
|
||
if grep -q "OTHER_SWIFT_FLAGS.*APPSTORE" "${PROJECT_FILE}"; then
|
||
sed -i.backup '/OTHER_SWIFT_FLAGS = "-D APPSTORE";/d' "${PROJECT_FILE}"
|
||
rm -f "${PROJECT_FILE}.backup"
|
||
print_success "Removed APPSTORE compiler flag"
|
||
fi
|
||
fi
|
||
else
|
||
print_warning "No project.pbxproj backup found for ${config_name}"
|
||
fi
|
||
}
|
||
|
||
# Function to initialize configurations if they don't exist
|
||
initialize_configs() {
|
||
create_backup_dirs
|
||
|
||
# Check if we have existing backups
|
||
if [ ! -f "${SELF_CONFIG}/Info.plist" ]; then
|
||
print_info "No self-distribution config found. Creating from current state..."
|
||
|
||
# The current state should be self-distribution (before my changes)
|
||
# Let's create the self-distribution version with Sparkle keys
|
||
cat > "${SELF_CONFIG}/Info.plist" <<'EOF'
|
||
<?xml version="1.0" encoding="UTF-8"?>
|
||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||
<plist version="1.0">
|
||
<dict>
|
||
<key>CFBundleExecutable</key>
|
||
<string>$(EXECUTABLE_NAME)</string>
|
||
<key>CFBundlePackageType</key>
|
||
<string>APPL</string>
|
||
<key>LSUIElement</key>
|
||
<true/>
|
||
<key>LSApplicationCategoryType</key>
|
||
<string>public.app-category.productivity</string>
|
||
<key>CFBundleName</key>
|
||
<string>Gaze</string>
|
||
<key>CFBundleDisplayName</key>
|
||
<string>Gaze</string>
|
||
<key>CFBundleIdentifier</key>
|
||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||
<key>CFBundleVersion</key>
|
||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||
<key>CFBundleShortVersionString</key>
|
||
<string>$(MARKETING_VERSION)</string>
|
||
<key>NSHumanReadableCopyright</key>
|
||
<string>Copyright © 2026 Mike Freno. All rights reserved.</string>
|
||
<key>SUPublicEDKey</key>
|
||
<string>Z2RmohI1y2bgeGQQUDqO9F0HNF2AzFotOt8CwGB6VJM=</string>
|
||
<key>SUFeedURL</key>
|
||
<string>https://freno.me/api/Gaze/appcast.xml</string>
|
||
<key>SUEnableAutomaticChecks</key>
|
||
<true/>
|
||
<key>SUScheduledCheckInterval</key>
|
||
<integer>86400</integer>
|
||
<key>SUEnableInstallerLauncherService</key>
|
||
<true/>
|
||
</dict>
|
||
</plist>
|
||
EOF
|
||
|
||
cat > "${SELF_CONFIG}/Gaze.entitlements" <<'EOF'
|
||
<?xml version="1.0" encoding="UTF-8"?>
|
||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||
<plist version="1.0">
|
||
<dict>
|
||
<key>com.apple.security.app-sandbox</key>
|
||
<true/>
|
||
<key>com.apple.security.network.client</key>
|
||
<true/>
|
||
<key>com.apple.security.temporary-exception.mach-lookup.global-name</key>
|
||
<array>
|
||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)-spks</string>
|
||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)-spki</string>
|
||
</array>
|
||
</dict>
|
||
</plist>
|
||
EOF
|
||
|
||
# Extract current Release config WITHOUT APPSTORE flag
|
||
awk '/27A21B5F2F0F69DD0018C4F3 \/\* Release \*\/ = {/,/name = Release;/ {
|
||
if ($0 !~ /OTHER_SWIFT_FLAGS/) {
|
||
print
|
||
} else {
|
||
# Skip the APPSTORE flag line
|
||
if ($0 !~ /APPSTORE/) {
|
||
print
|
||
}
|
||
}
|
||
}' "${PROJECT_FILE}" > "${SELF_CONFIG}/project_release.txt"
|
||
|
||
print_success "Created self-distribution config"
|
||
fi
|
||
|
||
if [ ! -f "${APPSTORE_CONFIG}/Info.plist" ]; then
|
||
print_info "Creating App Store config..."
|
||
|
||
# Backup current state as App Store config (after my changes)
|
||
backup_current_config "App Store" "${APPSTORE_CONFIG}"
|
||
fi
|
||
}
|
||
|
||
# Function to show current distribution mode
|
||
show_current_mode() {
|
||
print_info "Current configuration:"
|
||
echo ""
|
||
|
||
# Check for Sparkle keys in Info.plist
|
||
if grep -q "SUPublicEDKey" "${INFO_PLIST}" 2>/dev/null; then
|
||
echo " Info.plist: ${GREEN}Self-Distribution${NC} (has Sparkle keys)"
|
||
else
|
||
echo " Info.plist: ${BLUE}App Store${NC} (no Sparkle keys)"
|
||
fi
|
||
|
||
# Check for Sparkle entitlements
|
||
if grep -q "mach-lookup.global-name" "${ENTITLEMENTS}" 2>/dev/null; then
|
||
echo " Entitlements: ${GREEN}Self-Distribution${NC} (has Sparkle exceptions)"
|
||
else
|
||
echo " Entitlements: ${BLUE}App Store${NC} (no Sparkle exceptions)"
|
||
fi
|
||
|
||
# Check for APPSTORE flag
|
||
if grep -q "OTHER_SWIFT_FLAGS.*APPSTORE" "${PROJECT_FILE}" 2>/dev/null; then
|
||
echo " Build Settings: ${BLUE}App Store${NC} (has APPSTORE flag)"
|
||
else
|
||
echo " Build Settings: ${GREEN}Self-Distribution${NC} (no APPSTORE flag)"
|
||
fi
|
||
|
||
# Check for Sparkle package dependency
|
||
if has_sparkle_package || grep -q "Sparkle" "${PROJECT_FILE}" 2>/dev/null; then
|
||
echo " Package Dependency: ${GREEN}Self-Distribution${NC} (has Sparkle)"
|
||
else
|
||
echo " Package Dependency: ${BLUE}App Store${NC} (no Sparkle)"
|
||
fi
|
||
|
||
echo ""
|
||
}
|
||
|
||
# Function to switch to App Store configuration
|
||
switch_to_appstore() {
|
||
print_info "Switching to App Store distribution configuration..."
|
||
echo ""
|
||
|
||
# Backup current state if it's self-distribution
|
||
if grep -q "SUPublicEDKey" "${INFO_PLIST}" 2>/dev/null; then
|
||
backup_current_config "self-distribution" "${SELF_CONFIG}"
|
||
fi
|
||
|
||
# Remove Sparkle package dependency
|
||
remove_sparkle_package
|
||
echo ""
|
||
|
||
# Restore App Store config
|
||
restore_config "App Store" "${APPSTORE_CONFIG}"
|
||
|
||
echo ""
|
||
print_success "Switched to App Store distribution mode"
|
||
print_info "Sparkle framework has been removed from dependencies"
|
||
print_info "You can now archive and submit to App Store"
|
||
}
|
||
|
||
# Function to switch to self-distribution configuration
|
||
switch_to_self() {
|
||
print_info "Switching to self-distribution configuration..."
|
||
echo ""
|
||
|
||
# Backup current state if it's App Store
|
||
if ! grep -q "SUPublicEDKey" "${INFO_PLIST}" 2>/dev/null; then
|
||
backup_current_config "App Store" "${APPSTORE_CONFIG}"
|
||
fi
|
||
|
||
# Restore self-distribution config
|
||
restore_config "self-distribution" "${SELF_CONFIG}"
|
||
|
||
echo ""
|
||
|
||
# Add Sparkle package dependency
|
||
add_sparkle_package
|
||
|
||
echo ""
|
||
print_success "Switched to self-distribution mode"
|
||
print_info "Sparkle auto-updates enabled"
|
||
|
||
# Check if Sparkle was added to project.pbxproj (not Package.resolved since Xcode will resolve it)
|
||
if ! grep -q "Sparkle" "${PROJECT_FILE}"; then
|
||
echo ""
|
||
print_warning "⚠️ Sparkle not successfully added to project!"
|
||
else
|
||
echo ""
|
||
print_info "Sparkle package will be resolved on next build"
|
||
fi
|
||
}
|
||
|
||
# Function to show usage
|
||
show_usage() {
|
||
cat << EOF
|
||
${BLUE}Gaze Distribution Configuration Switcher${NC}
|
||
|
||
${GREEN}Usage:${NC}
|
||
./switch_to [command]
|
||
|
||
${GREEN}Commands:${NC}
|
||
appstore Switch to App Store distribution configuration
|
||
- Removes Sparkle package dependency from project
|
||
- Removes Sparkle keys from Info.plist
|
||
- Removes Sparkle entitlements
|
||
- Adds APPSTORE compiler flag
|
||
|
||
self Switch to self-distribution configuration
|
||
- Prompts to add Sparkle package dependency
|
||
- Adds Sparkle keys to Info.plist
|
||
- Adds Sparkle entitlements for XPC services
|
||
- Removes APPSTORE compiler flag
|
||
|
||
status Show current distribution configuration
|
||
|
||
help Show this help message
|
||
|
||
${GREEN}Examples:${NC}
|
||
./switch_to appstore # Prepare for App Store submission
|
||
./switch_to self # Prepare for direct distribution with auto-updates
|
||
./switch_to status # Check current configuration
|
||
|
||
${YELLOW}Note:${NC} Configuration backups are stored in ${BACKUP_DIR}/
|
||
EOF
|
||
}
|
||
|
||
# Main script logic
|
||
main() {
|
||
# Check if we're in the right directory
|
||
if [ ! -f "${PROJECT_FILE}" ]; then
|
||
print_error "Not in Gaze project directory. Please run from project root."
|
||
exit 1
|
||
fi
|
||
|
||
# Initialize configurations if needed
|
||
initialize_configs
|
||
|
||
# Parse command
|
||
case "${1:-}" in
|
||
appstore)
|
||
switch_to_appstore
|
||
;;
|
||
self)
|
||
switch_to_self
|
||
;;
|
||
status)
|
||
show_current_mode
|
||
;;
|
||
help|--help|-h)
|
||
show_usage
|
||
;;
|
||
*)
|
||
print_error "Unknown command: ${1:-}"
|
||
echo ""
|
||
show_usage
|
||
exit 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
# Run main function
|
||
main "$@"
|