general: debugging appstore release
This commit is contained in:
201
ARCHIVE_POST_ACTION_SETUP.md
Normal file
201
ARCHIVE_POST_ACTION_SETUP.md
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
# 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!
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
275915892F132A9200D0E60D /* Lottie in Frameworks */ = {isa = PBXBuildFile; productRef = 27AE10B12F10B1FC00E00DBC /* Lottie */; };
|
275915892F132A9200D0E60D /* Lottie in Frameworks */ = {isa = PBXBuildFile; productRef = 27AE10B12F10B1FC00E00DBC /* Lottie */; };
|
||||||
275915902F132B0000D0E60D /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 27AE10B22F10B20000E00DBC /* Sparkle */; };
|
275915902F132B0000D0E60D /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 27AE10B22F10B20000E00DBC /* Sparkle */; settings = {ATTRIBUTES = (Weak, ); }; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
|||||||
120
Gaze.xcodeproj/xcshareddata/xcschemes/Gaze.xcscheme
Normal file
120
Gaze.xcodeproj/xcshareddata/xcschemes/Gaze.xcscheme
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "2620"
|
||||||
|
version = "1.7">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES"
|
||||||
|
buildArchitectures = "Automatic">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "27A21B3B2F0F69DC0018C4F3"
|
||||||
|
BuildableName = "Gaze.app"
|
||||||
|
BlueprintName = "Gaze"
|
||||||
|
ReferencedContainer = "container:Gaze.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
shouldAutocreateTestPlan = "YES">
|
||||||
|
<Testables>
|
||||||
|
<TestableReference
|
||||||
|
skipped = "NO"
|
||||||
|
parallelizable = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "27A21B482F0F69DD0018C4F3"
|
||||||
|
BuildableName = "GazeTests.xctest"
|
||||||
|
BlueprintName = "GazeTests"
|
||||||
|
ReferencedContainer = "container:Gaze.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</TestableReference>
|
||||||
|
<TestableReference
|
||||||
|
skipped = "NO"
|
||||||
|
parallelizable = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "27A21B522F0F69DD0018C4F3"
|
||||||
|
BuildableName = "GazeUITests.xctest"
|
||||||
|
BlueprintName = "GazeUITests"
|
||||||
|
ReferencedContainer = "container:Gaze.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</TestableReference>
|
||||||
|
</Testables>
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "27A21B3B2F0F69DC0018C4F3"
|
||||||
|
BuildableName = "Gaze.app"
|
||||||
|
BlueprintName = "Gaze"
|
||||||
|
ReferencedContainer = "container:Gaze.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "27A21B3B2F0F69DC0018C4F3"
|
||||||
|
BuildableName = "Gaze.app"
|
||||||
|
BlueprintName = "Gaze"
|
||||||
|
ReferencedContainer = "container:Gaze.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
<PostActions>
|
||||||
|
<ExecutionAction
|
||||||
|
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
|
||||||
|
<ActionContent
|
||||||
|
title = "Run Script"
|
||||||
|
scriptText = "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 ">
|
||||||
|
<EnvironmentBuildable>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "27A21B3B2F0F69DC0018C4F3"
|
||||||
|
BuildableName = "Gaze.app"
|
||||||
|
BlueprintName = "Gaze"
|
||||||
|
ReferencedContainer = "container:Gaze.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</EnvironmentBuildable>
|
||||||
|
</ActionContent>
|
||||||
|
</ExecutionAction>
|
||||||
|
</PostActions>
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
@@ -10,5 +10,23 @@
|
|||||||
<integer>0</integer>
|
<integer>0</integer>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
|
<key>SuppressBuildableAutocreation</key>
|
||||||
|
<dict>
|
||||||
|
<key>27A21B3B2F0F69DC0018C4F3</key>
|
||||||
|
<dict>
|
||||||
|
<key>primary</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
<key>27A21B482F0F69DD0018C4F3</key>
|
||||||
|
<dict>
|
||||||
|
<key>primary</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
<key>27A21B522F0F69DD0018C4F3</key>
|
||||||
|
<dict>
|
||||||
|
<key>primary</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -20,28 +20,49 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
|
|||||||
private var hasStartedTimers = false
|
private var hasStartedTimers = false
|
||||||
|
|
||||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||||
|
print("🚀 Gaze: applicationDidFinishLaunching")
|
||||||
|
|
||||||
// Set activation policy to hide dock icon
|
// Set activation policy to hide dock icon
|
||||||
NSApplication.shared.setActivationPolicy(.accessory)
|
NSApplication.shared.setActivationPolicy(.accessory)
|
||||||
|
print("✓ Activation policy set to accessory")
|
||||||
|
|
||||||
timerEngine = TimerEngine(settingsManager: settingsManager)
|
timerEngine = TimerEngine(settingsManager: settingsManager)
|
||||||
|
print("✓ TimerEngine initialized")
|
||||||
|
|
||||||
// Initialize update manager after onboarding is complete
|
// Initialize update manager after onboarding is complete
|
||||||
if settingsManager.settings.hasCompletedOnboarding {
|
if settingsManager.settings.hasCompletedOnboarding {
|
||||||
|
print("✓ Onboarding completed, initializing UpdateManager")
|
||||||
updateManager = UpdateManager.shared
|
updateManager = UpdateManager.shared
|
||||||
|
} else {
|
||||||
|
print("ℹ️ Onboarding not completed, skipping UpdateManager")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect App Store version asynchronously at launch
|
// Detect App Store version asynchronously at launch
|
||||||
Task {
|
Task {
|
||||||
|
do {
|
||||||
|
print("🔍 Detecting App Store version...")
|
||||||
await settingsManager.detectAppStoreVersion()
|
await settingsManager.detectAppStoreVersion()
|
||||||
|
print("✓ App Store detection complete: \(settingsManager.settings.isAppStoreVersion)")
|
||||||
|
} catch {
|
||||||
|
print("⚠️ Failed to detect App Store version: \(error)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setupLifecycleObservers()
|
setupLifecycleObservers()
|
||||||
|
print("✓ Lifecycle observers set up")
|
||||||
|
|
||||||
observeSettingsChanges()
|
observeSettingsChanges()
|
||||||
|
print("✓ Settings change observer set up")
|
||||||
|
|
||||||
// Start timers if onboarding is complete
|
// Start timers if onboarding is complete
|
||||||
if settingsManager.settings.hasCompletedOnboarding {
|
if settingsManager.settings.hasCompletedOnboarding {
|
||||||
|
print("▶️ Starting timers (onboarding complete)")
|
||||||
startTimers()
|
startTimers()
|
||||||
|
} else {
|
||||||
|
print("⏸️ Timers not started (onboarding incomplete)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
print("✅ Gaze: Launch complete")
|
||||||
}
|
}
|
||||||
|
|
||||||
func onboardingCompleted() {
|
func onboardingCompleted() {
|
||||||
|
|||||||
@@ -13,12 +13,18 @@ struct GazeApp: App {
|
|||||||
@StateObject private var settingsManager = SettingsManager.shared
|
@StateObject private var settingsManager = SettingsManager.shared
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
print("🚀 GazeApp: init")
|
||||||
|
|
||||||
// Handle test launch arguments
|
// Handle test launch arguments
|
||||||
if TestingEnvironment.shouldSkipOnboarding {
|
if TestingEnvironment.shouldSkipOnboarding {
|
||||||
|
print("ℹ️ Test mode: Skipping onboarding")
|
||||||
SettingsManager.shared.settings.hasCompletedOnboarding = true
|
SettingsManager.shared.settings.hasCompletedOnboarding = true
|
||||||
} else if TestingEnvironment.shouldResetOnboarding {
|
} else if TestingEnvironment.shouldResetOnboarding {
|
||||||
|
print("ℹ️ Test mode: Resetting onboarding")
|
||||||
SettingsManager.shared.settings.hasCompletedOnboarding = false
|
SettingsManager.shared.settings.hasCompletedOnboarding = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
print("✓ GazeApp initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
|
|||||||
@@ -17,30 +17,55 @@ enum AppStoreDetector {
|
|||||||
/// This method is asynchronous due to the use of StoreKit's async API.
|
/// This method is asynchronous due to the use of StoreKit's async API.
|
||||||
static func isAppStoreVersion() async -> Bool {
|
static func isAppStoreVersion() async -> Bool {
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
print("🔍 AppStoreDetector: DEBUG build, returning false")
|
||||||
return false
|
return false
|
||||||
#else
|
#else
|
||||||
|
print("🔍 AppStoreDetector: Checking App Store status...")
|
||||||
if #available(macOS 15.0, *) {
|
if #available(macOS 15.0, *) {
|
||||||
|
print(" ℹ️ Using macOS 15+ AppTransaction API")
|
||||||
do {
|
do {
|
||||||
_ = try await AppTransaction.shared
|
let transaction = try await AppTransaction.shared
|
||||||
|
print(" ✅ AppTransaction found: This is an App Store version")
|
||||||
return true
|
return true
|
||||||
} catch {
|
} catch {
|
||||||
|
print(" ⚠️ AppTransaction error: \(error.localizedDescription)")
|
||||||
|
print(" → Assuming NOT an App Store version")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fallback for older macOS: use legacy receipt check
|
// Fallback for older macOS: use legacy receipt check
|
||||||
|
print(" ℹ️ Using legacy receipt check (macOS <15)")
|
||||||
|
|
||||||
guard let receiptURL = Bundle.main.appStoreReceiptURL else {
|
guard let receiptURL = Bundle.main.appStoreReceiptURL else {
|
||||||
|
print(" ⚠️ No receipt URL available")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
guard FileManager.default.fileExists(atPath: receiptURL.path) else {
|
print(" 📄 Receipt URL: \(receiptURL.path)")
|
||||||
|
|
||||||
|
do {
|
||||||
|
let fileExists = FileManager.default.fileExists(atPath: receiptURL.path)
|
||||||
|
guard fileExists else {
|
||||||
|
print(" ⚠️ Receipt file does not exist")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
print(" ✓ Receipt file exists")
|
||||||
|
|
||||||
guard let receiptData = try? Data(contentsOf: receiptURL),
|
guard let receiptData = try? Data(contentsOf: receiptURL),
|
||||||
receiptData.count > 2
|
receiptData.count > 2
|
||||||
else {
|
else {
|
||||||
|
print(" ⚠️ Receipt file is empty or unreadable")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
print(" ✓ Receipt data loaded (\(receiptData.count) bytes)")
|
||||||
|
|
||||||
let bytes = [UInt8](receiptData.prefix(2))
|
let bytes = [UInt8](receiptData.prefix(2))
|
||||||
return bytes[0] == 0x30 && bytes[1] == 0x82
|
let isValid = bytes[0] == 0x30 && bytes[1] == 0x82
|
||||||
|
print(" \(isValid ? "✅" : "⚠️") Receipt validation: \(isValid)")
|
||||||
|
return isValid
|
||||||
|
} catch {
|
||||||
|
print(" ❌ Receipt check error: \(error.localizedDescription)")
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user