Replaces the bash/AppleScript/Python app bundle with a SwiftUI app backed by a testable library target. The app now reads Contacts and EventKit directly rather than sanitizing files someone exported by hand, so the properties that break click-wheel firmware are never written instead of being stripped afterwards: no PHOTO, no X-, no VALARM, no VTIMEZONE, no TZID. Recurring events arrive from EventKit already expanded into occurrences, so no RRULE is emitted either - the open question from the previous version no longer applies. Sanitizing existing .vcf/.ics files is kept as a second path, and gains handling the Python lacked: vCard 2.1 quoted-printable soft line breaks, Apple's item1. property groups, nested STANDARD/DAYLIGHT components inside VTIMEZONE, and UTC times converted to local floating times. Output is laid out as Contacts/ and Calendars/ subfolders mirroring the device, one file per contact and one per calendar. Built via SwiftPM plus Scripts/bundle.sh, which assembles and ad-hoc signs the bundle. Ad-hoc signing is required rather than cosmetic: Contacts and Calendars key permissions off the code signature. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01C5X1tYo9oxAfvoiFMh1QQr
85 lines
3.1 KiB
Bash
Executable File
85 lines
3.1 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
# Assembles build/iPod Contacts and Calendar Sync.app from the SwiftPM executable.
|
|
#
|
|
# SwiftPM builds a bare binary; macOS needs it inside a bundle with an
|
|
# Info.plist before Contacts and EventKit will even prompt for access, so the
|
|
# bundle is put together here rather than by an Xcode target.
|
|
#
|
|
# ./Scripts/bundle.sh # native architecture
|
|
# ./Scripts/bundle.sh --universal # arm64 + x86_64
|
|
#
|
|
set -euo pipefail
|
|
|
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
APP_NAME="iPod Contacts and Calendar Sync"
|
|
BUNDLE_ID="ca.unsupervised.ipodsync"
|
|
VERSION="2.0"
|
|
MIN_MACOS="14.0"
|
|
|
|
BUILD_DIR="$ROOT/build"
|
|
APP="$BUILD_DIR/$APP_NAME.app"
|
|
|
|
# Bash 3.2 ships with macOS and treats an empty array as unbound under
|
|
# `set -u`, hence the ${ARCH_FLAGS[@]+…} guard at each use site.
|
|
ARCH_FLAGS=()
|
|
if [ "${1:-}" = "--universal" ]; then
|
|
ARCH_FLAGS=(--arch arm64 --arch x86_64)
|
|
fi
|
|
|
|
echo "Building…"
|
|
swift build --package-path "$ROOT" -c release ${ARCH_FLAGS[@]+"${ARCH_FLAGS[@]}"}
|
|
BIN_PATH="$(swift build --package-path "$ROOT" -c release ${ARCH_FLAGS[@]+"${ARCH_FLAGS[@]}"} --show-bin-path)"
|
|
|
|
echo "Assembling $APP_NAME.app…"
|
|
rm -rf "$APP"
|
|
mkdir -p "$APP/Contents/MacOS" "$APP/Contents/Resources"
|
|
|
|
cp "$BIN_PATH/iPodSyncApp" "$APP/Contents/MacOS/$APP_NAME"
|
|
|
|
cat > "$APP/Contents/Info.plist" <<PLIST
|
|
<?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>CFBundleName</key>
|
|
<string>$APP_NAME</string>
|
|
<key>CFBundleDisplayName</key>
|
|
<string>$APP_NAME</string>
|
|
<key>CFBundleIdentifier</key>
|
|
<string>$BUNDLE_ID</string>
|
|
<key>CFBundleVersion</key>
|
|
<string>$VERSION</string>
|
|
<key>CFBundleShortVersionString</key>
|
|
<string>$VERSION</string>
|
|
<key>CFBundlePackageType</key>
|
|
<string>APPL</string>
|
|
<key>CFBundleExecutable</key>
|
|
<string>$APP_NAME</string>
|
|
<key>CFBundleInfoDictionaryVersion</key>
|
|
<string>6.0</string>
|
|
<key>LSMinimumSystemVersion</key>
|
|
<string>$MIN_MACOS</string>
|
|
<key>NSHighResolutionCapable</key>
|
|
<true/>
|
|
<key>NSSupportsAutomaticTermination</key>
|
|
<true/>
|
|
<key>NSContactsUsageDescription</key>
|
|
<string>iPod Contacts and Calendar Sync reads your contacts so it can write one iPod-compatible vCard file per person.</string>
|
|
<key>NSCalendarsFullAccessUsageDescription</key>
|
|
<string>iPod Contacts and Calendar Sync reads your calendars so it can write iPod-compatible calendar files.</string>
|
|
<key>NSCalendarsUsageDescription</key>
|
|
<string>iPod Contacts and Calendar Sync reads your calendars so it can write iPod-compatible calendar files.</string>
|
|
</dict>
|
|
PLIST
|
|
echo "</plist>" >> "$APP/Contents/Info.plist"
|
|
|
|
# Ad-hoc signature. Contacts and Calendars identify an app by its code
|
|
# signature, so an unsigned bundle either re-prompts constantly or is denied
|
|
# outright. This is not a Developer ID signature — Gatekeeper still requires
|
|
# right-click → Open the first time.
|
|
echo "Signing (ad-hoc)…"
|
|
codesign --force --sign - --timestamp=none "$APP" >/dev/null 2>&1
|
|
|
|
echo "Built $APP"
|