From d35489105fe4e84fc78b76885d9a67d12477041b Mon Sep 17 00:00:00 2001 From: Michael Freno Date: Tue, 13 Jan 2026 14:04:04 -0500 Subject: [PATCH] fix: manage sparkle in switch_to --- ARCHIVE_POST_ACTION_SETUP.md | 201 ------------ DEPLOYMENT.md | 296 ------------------ DISTRIBUTION.md | 214 ------------- Gaze.xcodeproj/project.pbxproj | 21 +- .../xcshareddata/swiftpm/Package.resolved | 11 +- .../xcshareddata/xcschemes/Gaze.xcscheme | 18 -- switch_to | 144 ++++++++- 7 files changed, 140 insertions(+), 765 deletions(-) delete mode 100644 ARCHIVE_POST_ACTION_SETUP.md delete mode 100644 DEPLOYMENT.md delete mode 100644 DISTRIBUTION.md diff --git a/ARCHIVE_POST_ACTION_SETUP.md b/ARCHIVE_POST_ACTION_SETUP.md deleted file mode 100644 index 4cd6e62..0000000 --- a/ARCHIVE_POST_ACTION_SETUP.md +++ /dev/null @@ -1,201 +0,0 @@ -# Archive Post-Action Setup (REQUIRED for App Store) - -## The Problem - -App Store validation fails with: -``` -App sandbox not enabled. The following executables must include the -"com.apple.security.app-sandbox" entitlement... -Sparkle.framework/Versions/B/Autoupdate -Sparkle.framework/Versions/B/Updater.app -... -``` - -Sparkle.framework **must be physically removed** from the app bundle for App Store distribution. - -## Why Build Phase Scripts Don't Work - -Build Phase scripts run during compilation when files are code-signed and locked by macOS. Even with `chmod` and `chflags`, you get "Operation not permitted" due to System Integrity Protection. - -## The Solution: Archive Post-Action - -Archive Post-Actions run **after** the archive completes, when files are no longer locked. This is the correct place to remove Sparkle. - ---- - -## Setup Instructions (2 minutes) - -### 1. Open Scheme Editor -In Xcode: **Product → Scheme → Edit Scheme...** (or press **⌘<**) - -### 2. Select Archive -Click **Archive** in the left sidebar - -### 3. Add Post-Action Script -- At the bottom, under "Post-actions", click the **+** button -- Select **New Run Script Action** - -### 4. Configure the Action - -**Provide build settings from:** Select **Gaze** from the dropdown (IMPORTANT!) - -**Shell:** Leave as `/bin/bash` - -**Script:** Copy and paste this entire script: - -```bash -if [[ "${OTHER_SWIFT_FLAGS}" == *"APPSTORE"* ]]; then - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - echo "🗑️ Removing Sparkle from archived app..." - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - - SPARKLE_PATH="${ARCHIVE_PATH}/Products/Applications/Gaze.app/Contents/Frameworks/Sparkle.framework" - - if [ -d "$SPARKLE_PATH" ]; then - echo "📂 Found Sparkle at: $SPARKLE_PATH" - - # Make writable and remove - chmod -R u+w "$SPARKLE_PATH" 2>/dev/null || true - chflags -R nouchg "$SPARKLE_PATH" 2>/dev/null || true - rm -rf "$SPARKLE_PATH" - - if [ ! -d "$SPARKLE_PATH" ]; then - echo "✅ Sparkle framework removed successfully!" - else - echo "❌ ERROR: Could not remove Sparkle framework" - echo " This will cause App Store validation to fail" - exit 1 - fi - else - echo "ℹ️ Sparkle framework not found (already removed)" - fi - - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - echo "✅ Archive ready for App Store distribution" - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -else - echo "✓ Self-distribution archive - Sparkle retained" -fi -``` - -### 5. Save -Click **Close** to save the scheme changes - ---- - -## Verification Steps - -### Test the Archive - -1. **Switch to App Store mode:** - ```bash - ./switch_to appstore - ``` - -2. **Archive in Xcode:** - - **Product → Archive** (or **⌘⇧B** then Archive) - - Watch the build log - you should see the post-action output - -3. **Check the archive contents:** - - **Window → Organizer** - - Select your latest Gaze archive - - Right-click → **Show in Finder** - - Right-click the `.xcarchive` file → **Show Package Contents** - - Navigate to: `Products/Applications/Gaze.app/Contents/Frameworks/` - - **Verify:** Only `Lottie.framework` should be present ✅ - -4. **Distribute to App Store:** - - In Organizer, click **Distribute App** - - Choose **App Store Connect** - - Complete the distribution wizard - - **Validation should now pass!** ✅ - ---- - -## What You Should See - -### In the Build Log (after archiving): -``` -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -🗑️ Removing Sparkle from archived app... -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -📂 Found Sparkle at: [path] -✅ Sparkle framework removed successfully! -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -✅ Archive ready for App Store distribution -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -``` - -### In the Frameworks folder: -``` -Gaze.app/Contents/Frameworks/ -└── Lottie.framework/ -``` - -No Sparkle.framework! ✅ - ---- - -## Troubleshooting - -### "I don't see the post-action output" -- Make sure you selected **Gaze** in "Provide build settings from" -- Check View → Navigators → Show Report Navigator (⌘9) -- Select the Archive action to see full logs - -### "Sparkle is still in the archive" -- Verify `./switch_to status` shows "App Store" for all items -- Check the script exactly matches (copy/paste the entire script) -- Try cleaning: Product → Clean Build Folder (⌘⇧K) - -### "Script says 'Sparkle framework not found'" -- This means Sparkle wasn't embedded (good!) -- Continue with distribution - validation should pass - -### "Archive Post-Action section doesn't exist" -- Make sure you're editing the **Archive** section, not Run or Test -- Click the triangle next to "Archive" to expand it - ---- - -## Optional: Remove Old Build Phase Script - -If you previously added a Build Phase script (which doesn't work due to file locking), you can remove it: - -1. Gaze target → Build Phases -2. Find "Remove Sparkle for App Store" or "Run Script" -3. Click the **X** to delete it - -The Archive Post-Action is the correct and only solution needed. - ---- - -## Why This Is Required - -Even though: -- ✅ Sparkle code is disabled via `#if !APPSTORE` -- ✅ Info.plist has no Sparkle keys -- ✅ Entitlements have no Sparkle exceptions - -App Store validation **still checks** for the physical presence of unsandboxed executables in frameworks. Sparkle contains XPC services that aren't App Store compatible, so the entire framework must be removed. - ---- - -## For Self-Distribution - -When building for self-distribution (`./switch_to self`), the script detects the absence of the APPSTORE flag and leaves Sparkle intact. You don't need to change anything! - -```bash -./switch_to self -./self_distribute # Sparkle is retained and works normally -``` - ---- - -## Summary - -✅ **One-time setup:** Add Archive Post-Action script -✅ **Works automatically:** Removes Sparkle only for App Store builds -✅ **Zero maintenance:** Once configured, runs automatically forever - -This is the **correct and only working solution** for removing Sparkle from App Store builds! diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md deleted file mode 100644 index db7ea4c..0000000 --- a/DEPLOYMENT.md +++ /dev/null @@ -1,296 +0,0 @@ -# Gaze Release Deployment Process - -## Overview - -This document describes the complete process for building and deploying new releases of Gaze with Sparkle auto-update support. - -## Prerequisites - -- Xcode with Gaze project configured -- `create-dmg` tool installed (`brew install create-dmg`) -- Sparkle EdDSA signing keys in macOS Keychain (see Key Management below) -- AWS credentials configured in `.env` file for S3 upload -- Access to freno.me hosting infrastructure - -## Version Management - -Version numbers are managed in Xcode project settings: -- **Marketing Version** (`MARKETING_VERSION`): User-facing version (e.g., "0.1.1") -- **Build Number** (`CURRENT_PROJECT_VERSION`): Internal build number (e.g., "1") - -These must be incremented before each release and kept in sync with `build_dmg` script. - -## Release Checklist - -### 1. Prepare Release - -- [ ] Update version numbers in Xcode: - - Project Settings → General → Identity - - Set Marketing Version (e.g., "0.1.2") - - Increment Build Number (e.g., "2") -- [ ] Update `VERSION` and `BUILD_NUMBER` in `build_dmg` script -- [ ] Update CHANGELOG or release notes -- [ ] Commit version changes: `git commit -am "Bump version to X.Y.Z"` -- [ ] Create git tag: `git tag v0.1.2` - -### 2. Build Application - -```bash -# Build the app in Xcode (Product → Archive → Export) -# Or use the run script -./run build - -# Verify the app runs correctly -open Gaze.app -``` - -### 3. Create DMG and Appcast - -```bash -# Run the build_dmg script -./build_dmg - -# This will: -# - Create versioned DMG file -# - Generate appcast.xml with EdDSA signature -# - Upload to S3 if AWS credentials are configured -# - Display next steps -``` - -### 4. Verify Artifacts - -Check that the following files were created in `./releases/`: -- `Gaze-X.Y.Z.dmg` - Installable disk image -- `appcast.xml` - Update feed with signature -- `Gaze-X.Y.Z.delta` (optional) - Delta update from previous version - -### 5. Upload to Hosting (if not using S3 auto-upload) - -**DMG File:** -```bash -# Upload to: https://freno.me/downloads/ -scp ./releases/Gaze-X.Y.Z.dmg your-server:/path/to/downloads/ -``` - -**Appcast File:** -```bash -# Upload to: https://freno.me/api/Gaze/ -scp ./releases/appcast.xml your-server:/path/to/api/Gaze/ -``` - -### 6. Verify Deployment - -Test that files are accessible via HTTPS: - -```bash -# Test appcast accessibility -curl -I https://freno.me/api/Gaze/appcast.xml -# Should return: HTTP/2 200, content-type: application/xml - -# Test DMG accessibility -curl -I https://freno.me/downloads/Gaze-X.Y.Z.dmg -# Should return: HTTP/2 200, content-type: application/octet-stream - -# Validate appcast XML structure -curl https://freno.me/api/Gaze/appcast.xml | xmllint --format - -``` - -### 7. Test Update Flow - -**Manual Testing:** -1. Install previous version of Gaze -2. Launch app and check Settings → General → Software Updates -3. Click "Check for Updates Now" -4. Verify update notification appears -5. Complete update installation -6. Verify new version launches correctly - -**Automatic Update Testing:** -1. Set `SUScheduledCheckInterval` to a low value (e.g., 60 seconds) for testing -2. Install previous version -3. Wait for automatic check -4. Verify update notification appears - -### 8. Finalize Release - -- [ ] Push git tag: `git push origin v0.1.2` -- [ ] Create GitHub release (optional) -- [ ] Announce release to users -- [ ] Monitor for update errors in the first 24 hours - -## Hosting Configuration - -### Current Setup - -- **Appcast URL:** `https://freno.me/api/Gaze/appcast.xml` -- **Download URL:** `https://freno.me/downloads/Gaze-{VERSION}.dmg` -- **Hosting:** AWS S3 with freno.me domain -- **SSL:** HTTPS enabled (required by App Transport Security) - -### AWS S3 Configuration - -Create a `.env` file in the project root with: - -```bash -AWS_ACCESS_KEY_ID=your_access_key -AWS_SECRET_ACCESS_KEY=your_secret_key -AWS_BUCKET_NAME=your_bucket_name -AWS_REGION=us-east-1 -``` - -**Note:** `.env` is gitignored to protect credentials. - -### S3 Bucket Structure - -``` -your-bucket/ -├── downloads/ -│ ├── Gaze-0.1.1.dmg -│ ├── Gaze-0.1.2.dmg -│ └── ... -└── api/ - └── Gaze/ - └── appcast.xml -``` - -## Key Management - -### EdDSA Signing Keys - -**Location:** -- **Public Key:** In `Gaze/Info.plist` as `SUPublicEDKey` -- **Private Key:** In macOS Keychain as "Sparkle EdDSA Private Key" -- **Backup:** `~/sparkle_private_key_backup.pem` (keep secure!) - -**Current Public Key:** -``` -Z2RmohI1y2bgeGQQUDqO9F0HNF2AzFotOt8CwGB6VJM= -``` - -**Security:** -- Never commit private key to version control -- Keep backup in secure location (password manager, encrypted drive) -- Private key is required to sign all future updates - -### Regenerating Keys (Emergency Only) - -If private key is lost, you must: -1. Generate new key pair: `./generate_keys` -2. Update `SUPublicEDKey` in Info.plist -3. Release new version with new public key -4. Previous versions won't be able to update (users must manually install) - -## Troubleshooting - -### Appcast Generation Fails - -**Problem:** `generate_appcast` tool not found - -**Solution:** -```bash -# Build the app first to generate Sparkle tools -xcodebuild -project Gaze.xcodeproj -scheme Gaze -configuration Release - -# Find Sparkle tools -find ~/Library/Developer/Xcode/DerivedData/Gaze-* -name "generate_appcast" -``` - -### Update Check Fails in App - -**Problem:** No updates found or connection error - -**Diagnostics:** -```bash -# Check Console.app for Sparkle logs -# Filter by process: Gaze -# Look for: -# - "Downloading appcast..." -# - "Appcast downloaded successfully" -# - Connection errors -# - Signature verification errors -``` - -**Common Issues:** -- Appcast URL not accessible (check HTTPS) -- Signature mismatch (wrong private key used) -- XML malformed (validate with xmllint) -- Version number not higher than current version - -### DMG Not Downloading - -**Problem:** Update found but download fails - -**Check:** -- DMG URL is correct in appcast -- DMG file is accessible via HTTPS -- File size in appcast matches actual DMG size -- No CORS issues (check browser console) - -## Delta Updates - -Sparkle automatically generates delta updates when multiple versions exist in `./releases/`: - -```bash -# Keep previous versions for delta generation -releases/ -├── Gaze-0.1.1.dmg -├── Gaze-0.1.2.dmg -├── Gaze-0.1.1-to-0.1.2.delta # Generated automatically -└── appcast.xml -``` - -**Benefits:** -- Much smaller downloads (MB vs GB) -- Faster updates for users -- Generated automatically by `generate_appcast` - -**Note:** First-time users still download full DMG. - -## Testing with Local Appcast - -For testing without deploying: - -1. Modify Info.plist temporarily: -```xml -SUFeedURL -file:///Users/mike/Code/Gaze/releases/appcast.xml -``` - -2. Build and run app -3. Check for updates -4. Revert Info.plist before committing - -## Release Notes - -Release notes are embedded in appcast XML as CDATA: - -```xml -What's New in Version X.Y.Z - -]]> -``` - -**Tips:** -- Use simple HTML (h2, ul, li, p, strong, em) -- No external resources (images, CSS, JS) -- Keep concise and user-focused -- Highlight breaking changes - -## References - -- [Sparkle Documentation](https://sparkle-project.org/documentation/) -- [Publishing Updates](https://sparkle-project.org/documentation/publishing/) -- [Sandboxed Apps](https://sparkle-project.org/documentation/sandboxing/) -- [Gaze Repository](https://github.com/YOUR_USERNAME/Gaze) - -## Support - -For issues with deployment: -1. Check Console.app for Sparkle errors -2. Verify appcast validation with xmllint -3. Test with file:// URL first -4. Check AWS S3 permissions and CORS diff --git a/DISTRIBUTION.md b/DISTRIBUTION.md deleted file mode 100644 index 08bbc69..0000000 --- a/DISTRIBUTION.md +++ /dev/null @@ -1,214 +0,0 @@ -# Gaze Distribution Guide - -This guide explains how to build and distribute Gaze for both the Mac App Store and direct distribution with auto-updates. - -## Distribution Methods - -Gaze supports two distribution methods: - -1. **Self-Distribution** (Direct Download) - Includes Sparkle for automatic updates -2. **Mac App Store** - Uses Apple's update mechanism, no Sparkle - -## Quick Start - -### Switching Between Distributions - -Use the `switch_to` script to configure the project for each distribution method: - -```bash -# For self-distribution with Sparkle auto-updates -./switch_to self - -# For Mac App Store submission -./switch_to appstore - -# Check current configuration -./switch_to status -``` - -### What Gets Changed - -The `switch_to` script automatically manages: - -**Self-Distribution Mode:** -- ✅ Adds Sparkle keys to `Info.plist` (SUPublicEDKey, SUFeedURL, etc.) -- ✅ Adds Sparkle entitlements for XPC services -- ✅ Removes `APPSTORE` compiler flag -- ✅ Enables UpdateManager with Sparkle framework - -**App Store Mode:** -- ✅ Removes all Sparkle keys from `Info.plist` -- ✅ Removes Sparkle entitlements -- ✅ Adds `-D APPSTORE` compiler flag -- ✅ Disables Sparkle code at compile time - -## Building for Self-Distribution - -```bash -# 1. Switch to self-distribution mode -./switch_to self -``` - -The script will: -- Prompt for version bump (major/minor/patch) -- Build and code sign with Developer ID -- Notarize the app with Apple -- Create a signed DMG -- Generate Sparkle appcast with EdDSA signature -- (Optional) Upload to S3 if credentials are configured - -## Building for Mac App Store - -```bash -# 1. Switch to App Store mode -./switch_to appstore - -# 2. Add Run Script Phase in Xcode (one-time setup) -# See section below - -# 3. Archive and distribute via Xcode -# Product → Archive -# Window → Organizer → Distribute App → App Store Connect -``` - -### Required: Run Script Phase - -For App Store builds, you **must** add this Run Script phase in Xcode: - -1. Open Gaze.xcodeproj in Xcode -2. Select the Gaze target → Build Phases -3. Click + → New Run Script Phase -4. Name it: "Remove Sparkle for App Store" -5. Place it **after** "Embed Frameworks" -6. Add this script: - -```bash -#!/bin/bash -if [[ "${OTHER_SWIFT_FLAGS}" == *"APPSTORE"* ]]; then - echo "Removing Sparkle framework for App Store build..." - rm -rf "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/Sparkle.framework" - echo "Sparkle framework removed successfully" -fi -``` - -This ensures Sparkle.framework is removed from the app bundle before submission. - -## Configuration Files - -Configuration backups are stored in `.distribution_configs/`: -- `.distribution_configs/appstore/` - App Store configuration -- `.distribution_configs/self/` - Self-distribution configuration - -These backups are created automatically and used when switching between modes. - -## Validation - -Before submitting to App Store Connect, verify your configuration: - -```bash -./switch_to status -``` - -Expected output for App Store: -``` -Info.plist: App Store (no Sparkle keys) -Entitlements: App Store (no Sparkle exceptions) -Build Settings: App Store (has APPSTORE flag) -``` - -Expected output for Self-Distribution: -``` -Info.plist: Self-Distribution (has Sparkle keys) -Entitlements: Self-Distribution (has Sparkle exceptions) -Build Settings: Self-Distribution (no APPSTORE flag) -``` - -## Troubleshooting - -### App Store Validation Fails - -**Error: "App sandbox not enabled" with Sparkle executables** -- Solution: Make sure you ran `./switch_to appstore` and added the Run Script phase - -**Error: "Bad Bundle Executable" or "CFBundlePackageType"** -- Solution: These are now fixed in the Info.plist - -**Error: Still seeing Sparkle in the build** -- Solution: Clean build folder (⌘⇧K) and rebuild - -### Self-Distribution Issues - -**Sparkle updates not working** -- Verify: `./switch_to status` shows "Self-Distribution" mode -- Check: Info.plist contains SUPublicEDKey and SUFeedURL -- Verify: Appcast is accessible at the SUFeedURL - -**Code signing issues** -- Check `.env` file has correct credentials -- Verify Developer ID certificate: `security find-identity -v -p codesigning` - -## Environment Variables - -For self-distribution, create a `.env` file with: - -```bash -# Required for code signing -DEVELOPER_ID_APPLICATION="Developer ID Application: Your Name (TEAM_ID)" -APPLE_TEAM_ID="XXXXXXXXXX" - -# Required for notarization -NOTARY_KEYCHAIN_PROFILE="notary-profile" - -# Optional for S3 upload -AWS_ACCESS_KEY_ID="your-key" -AWS_SECRET_ACCESS_KEY="your-secret" -AWS_BUCKET_NAME="your-bucket" -AWS_REGION="us-east-1" -``` - -Setup notarization profile (one-time): -```bash -xcrun notarytool store-credentials "notary-profile" \ - --apple-id "your@email.com" \ - --team-id "TEAM_ID" -``` - -## Version Management - -The `self_distribute` script handles version bumping: -- **Major** (X.0.0) - Breaking changes -- **Minor** (x.X.0) - New features -- **Patch** (x.x.X) - Bug fixes -- **Custom** - Any version string -- **Keep** - Increment build number only - -Git tags are created automatically for new versions. - -## Testing - -### Test Self-Distribution Build -```bash -./switch_to self -# Test the DMG on a clean macOS system -``` - -### Test App Store Build -```bash -./switch_to appstore -# Archive in Xcode -# Use TestFlight for testing before release -``` - -## Best Practices - -1. **Always use `switch_to`** - Don't manually edit configuration files -2. **Check status before building** - Use `./switch_to status` -3. **Clean builds** - Run Clean Build Folder when switching modes -4. **Test thoroughly** - Test both distribution methods separately -5. **Commit before switching** - Use git to track configuration changes - -## Support - -For issues or questions: -- GitHub Issues: https://github.com/mikefreno/Gaze/issues -- Check AGENTS.md for development guidelines diff --git a/Gaze.xcodeproj/project.pbxproj b/Gaze.xcodeproj/project.pbxproj index 2ba1ab2..93d0cee 100644 --- a/Gaze.xcodeproj/project.pbxproj +++ b/Gaze.xcodeproj/project.pbxproj @@ -8,7 +8,6 @@ /* Begin PBXBuildFile section */ 275915892F132A9200D0E60D /* Lottie in Frameworks */ = {isa = PBXBuildFile; productRef = 27AE10B12F10B1FC00E00DBC /* Lottie */; }; - 275915902F132B0000D0E60D /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 27AE10B22F10B20000E00DBC /* Sparkle */; settings = {ATTRIBUTES = (Weak, ); }; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -71,7 +70,6 @@ buildActionMask = 2147483647; files = ( 275915892F132A9200D0E60D /* Lottie in Frameworks */, - 275915902F132B0000D0E60D /* Sparkle in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -133,7 +131,6 @@ name = Gaze; packageProductDependencies = ( 27AE10B12F10B1FC00E00DBC /* Lottie */, - 27AE10B22F10B20000E00DBC /* Sparkle */, ); productName = Gaze; productReference = 27A21B3C2F0F69DC0018C4F3 /* Gaze.app */; @@ -219,7 +216,6 @@ minimizedProjectReferenceProxies = 1; packageReferences = ( 27AE10B02F10B1FC00E00DBC /* XCRemoteSwiftPackageReference "lottie-spm" */, - 27AE10B32F10B21000E00DBC /* XCRemoteSwiftPackageReference "Sparkle" */, ); preferredProjectObjectVersion = 77; productRefGroup = 27A21B3D2F0F69DC0018C4F3 /* Products */; @@ -424,7 +420,7 @@ CODE_SIGN_ENTITLEMENTS = Gaze/Gaze.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 6; + CURRENT_PROJECT_VERSION = 8; DEVELOPMENT_TEAM = 6GK4F9L62V; ENABLE_APP_SANDBOX = YES; ENABLE_HARDENED_RUNTIME = YES; @@ -460,7 +456,7 @@ CODE_SIGN_ENTITLEMENTS = Gaze/Gaze.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 6; + CURRENT_PROJECT_VERSION = 8; DEVELOPMENT_TEAM = 6GK4F9L62V; ENABLE_APP_SANDBOX = YES; ENABLE_HARDENED_RUNTIME = YES; @@ -618,14 +614,6 @@ minimumVersion = 4.6.0; }; }; - 27AE10B32F10B21000E00DBC /* XCRemoteSwiftPackageReference "Sparkle" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/sparkle-project/Sparkle"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 2.0.0; - }; - }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -634,11 +622,6 @@ package = 27AE10B02F10B1FC00E00DBC /* XCRemoteSwiftPackageReference "lottie-spm" */; productName = Lottie; }; - 27AE10B22F10B20000E00DBC /* Sparkle */ = { - isa = XCSwiftPackageProductDependency; - package = 27AE10B32F10B21000E00DBC /* XCRemoteSwiftPackageReference "Sparkle" */; - productName = Sparkle; - }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 27A21B342F0F69DC0018C4F3 /* Project object */; diff --git a/Gaze.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Gaze.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index b3cf5c5..de23afb 100644 --- a/Gaze.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Gaze.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "513d974fbede884a919977d3446360023f6e3239ac314f4fbd9657e80aca7560", + "originHash" : "6b3386dc9ff1f3a74f1534de9c41d47137eae0901cfe819ed442f1b241549359", "pins" : [ { "identity" : "lottie-spm", @@ -9,15 +9,6 @@ "revision" : "69faaefa7721fba9e434a52c16adf4329c9084db", "version" : "4.6.0" } - }, - { - "identity" : "sparkle", - "kind" : "remoteSourceControl", - "location" : "https://github.com/sparkle-project/Sparkle", - "state" : { - "revision" : "5581748cef2bae787496fe6d61139aebe0a451f6", - "version" : "2.8.1" - } } ], "version" : 3 diff --git a/Gaze.xcodeproj/xcshareddata/xcschemes/Gaze.xcscheme b/Gaze.xcodeproj/xcshareddata/xcschemes/Gaze.xcscheme index e7dc879..b02b961 100644 --- a/Gaze.xcodeproj/xcshareddata/xcschemes/Gaze.xcscheme +++ b/Gaze.xcodeproj/xcshareddata/xcschemes/Gaze.xcscheme @@ -98,23 +98,5 @@ - - - - - - - - - - diff --git a/switch_to b/switch_to index 87e831a..9c83c8e 100755 --- a/switch_to +++ b/switch_to @@ -12,12 +12,17 @@ NC='\033[0m' # No Color 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" @@ -35,6 +40,113 @@ 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 + # Create a temp file without Sparkle entry + python3 -c " +import json +import sys + +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 + + # Remove Sparkle references from project.pbxproj + # This removes: package references, product dependencies, and framework build files + + # Remove XCRemoteSwiftPackageReference for Sparkle + sed -i '' '/XCRemoteSwiftPackageReference "Sparkle"/,/};/d' "${PROJECT_FILE}" + + # Remove XCSwiftPackageProductDependency for Sparkle + sed -i '' '/XCSwiftPackageProductDependency "Sparkle"/,/};/d' "${PROJECT_FILE}" + + # Remove Sparkle from packageProductDependencies array + sed -i '' '/\/\* Sparkle \*\/,/d' "${PROJECT_FILE}" + + # Remove Sparkle from Frameworks build phase + sed -i '' '/Sparkle in Frameworks/d' "${PROJECT_FILE}" + + # Clean up empty lines + sed -i '' '/^[[:space:]]*$/d' "${PROJECT_FILE}" + + 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 already exists + if has_sparkle_package || grep -q "Sparkle" "${PROJECT_FILE}"; then + print_info "Sparkle already present" + return 0 + fi + + print_warning "Sparkle package must be added manually in Xcode:" + echo "" + echo " 1. Open Gaze.xcodeproj in Xcode" + echo " 2. Select the project in the navigator" + echo " 3. Go to 'Package Dependencies' tab" + echo " 4. Click '+' button" + echo " 5. Enter: ${SPARKLE_REPO}" + echo " 6. Select version ${SPARKLE_VERSION}" + echo " 7. Click 'Add Package'" + echo " 8. Select 'Sparkle' product and add to Gaze target" + echo "" + print_info "Or run this from command line:" + echo " xed Gaze.xcodeproj" + echo "" + + # Note: Programmatically adding Swift packages via pbxproj is extremely complex + # and error-prone. Manual addition via Xcode UI is recommended. + + return 0 +} + # Function to create backup directories create_backup_dirs() { mkdir -p "${APPSTORE_CONFIG}" @@ -233,6 +345,13 @@ show_current_mode() { 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 "" } @@ -246,18 +365,17 @@ switch_to_appstore() { 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_warning "Remember to add the Run Script phase to remove Sparkle framework!" - echo "" - echo "Run Script to add in Xcode Build Phases:" - echo '#!/bin/bash' - echo 'if [[ "${OTHER_SWIFT_FLAGS}" == *"APPSTORE"* ]]; then' - echo ' rm -rf "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/Sparkle.framework"' - echo 'fi' + 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 @@ -273,9 +391,19 @@ switch_to_self() { # 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" + + if ! has_sparkle_package; then + echo "" + print_warning "Remember to add Sparkle package in Xcode (see instructions above)" + fi } # Function to show usage @@ -288,11 +416,13 @@ ${GREEN}Usage:${NC} ${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