Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed

- iCloud Sync between the iPhone and Mac apps: the iOS app now uses the Production CloudKit environment, so a development build no longer syncs into a separate database the Mac never reads.

## [0.50.0] - 2026-06-09

### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
<array>
<string>CloudKit</string>
</array>
<key>com.apple.developer.icloud-container-environment</key>
<string>Production</string>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.TablePro.TableProMobile</string>
Expand Down
53 changes: 53 additions & 0 deletions TableProTests/Core/Sync/EntitlementsEnvironmentParityTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//
// EntitlementsEnvironmentParityTests.swift
// TableProTests
//
// Guards the CloudKit environment pin. The Mac and iOS apps must both target
// the Production environment, or a development iOS build talks to the
// Development database while the Mac talks to Production and sync silently
// moves nothing across devices.
//

import Foundation
import Testing

@Suite("CloudKit environment entitlement parity")
struct EntitlementsEnvironmentParityTests {
private static let environmentKey = "com.apple.developer.icloud-container-environment"
private static let macEntitlements = "TablePro/TablePro.entitlements"
private static let iosEntitlements = "TableProMobile/TableProMobile/TableProMobileRelease.entitlements"

@Test("Mac app pins the Production CloudKit environment")
func macTargetsProduction() throws {
try #expect(environment(in: Self.macEntitlements) == "Production")
}

@Test("iOS app pins the Production CloudKit environment")
func iosTargetsProduction() throws {
try #expect(environment(in: Self.iosEntitlements) == "Production")
}

private func environment(in relativePath: String) throws -> String? {
let url = try repoRoot().appendingPathComponent(relativePath)
let data = try Data(contentsOf: url)
let plist = try PropertyListSerialization.propertyList(from: data, format: nil)
let entitlements = try #require(plist as? [String: Any], "Entitlements at \(relativePath) is not a dictionary")
return entitlements[Self.environmentKey] as? String
}

private func repoRoot(file: StaticString = #filePath) throws -> URL {
var directory = URL(fileURLWithPath: "\(file)").deletingLastPathComponent()
while directory.path != "/" {
let marker = directory.appendingPathComponent("TablePro.xcodeproj")
if FileManager.default.fileExists(atPath: marker.path) {
return directory
}
directory = directory.deletingLastPathComponent()
}
throw EntitlementsParityError.repoRootNotFound
}

private enum EntitlementsParityError: Error {
case repoRootNotFound
}
}
Loading