Add IndieAbout + IndieBackup, TestFlight release pipeline

IndieAbout: About section in iOS Settings and macOS About window; LICENSE
switched to the portfolio-standard ISC text (reflowed for in-app rendering).

IndieBackup: files-only backup/restore of the iCloud container documents
('.notesbackup' document type on both platforms), SwiftData cache rebuilt
via SyncEngine.rebuildCache() after restore, monitors suspended during the
file swap, onOpenURL import with pre-launch queueing.

Release pipeline: Scripts/release.sh (ios|mac|all) archives and uploads via
xcodebuild + ASC API key; platform-partitioned build numbers (iOS even,
macOS odd) since both targets share one bundle ID and build sequence.
This commit is contained in:
2026-07-14 20:50:06 -04:00
parent 9b231a1978
commit cf8616107b
17 changed files with 460 additions and 7 deletions
+114
View File
@@ -0,0 +1,114 @@
#!/usr/bin/env bash
#
# release.sh — archive Notes and upload to TestFlight / App Store Connect.
#
# Usage: Scripts/release.sh [ios|mac|all] (default: all)
#
# No third-party tooling: pure xcodebuild + the App Store Connect API key.
# Credentials live in .env.release (gitignored); see .env.release.example.
#
# What it does, per platform:
# 1. Regenerates the Xcode project with XcodeGen.
# 2. Stamps CFBundleVersion from the git commit count, platform-partitioned
# (iOS even, macOS odd — Scripts/update_build_number.sh, a post-build
# phase wired in project.yml).
# 3. xcodebuild archive -> .xcarchive
# 4. xcodebuild -exportArchive with destination=upload -> App Store Connect.
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
cd "$ROOT"
PROJECT="Notes.xcodeproj"
BUILD_DIR="$ROOT/build"
PLATFORM="${1:-all}"
# ---- credentials ------------------------------------------------------------
ENV_FILE="$ROOT/.env.release"
if [ -f "$ENV_FILE" ]; then
set -a; . "$ENV_FILE"; set +a
fi
: "${APPLE_TEAM_ID:?Set APPLE_TEAM_ID (copy .env.release.example -> .env.release)}"
: "${ASC_KEY_ID:?Set ASC_KEY_ID in .env.release}"
: "${ASC_ISSUER_ID:?Set ASC_ISSUER_ID in .env.release}"
: "${ASC_KEY_PATH:?Set ASC_KEY_PATH in .env.release}"
[ -f "$ASC_KEY_PATH" ] || { echo "❌ API key not found at: $ASC_KEY_PATH"; exit 1; }
AUTH=(
-authenticationKeyPath "$ASC_KEY_PATH"
-authenticationKeyID "$ASC_KEY_ID"
-authenticationKeyIssuerID "$ASC_ISSUER_ID"
-allowProvisioningUpdates
)
# ---- regenerate project -----------------------------------------------------
if command -v xcodegen >/dev/null 2>&1; then
echo "🧩 Generating $PROJECT ..."
xcodegen generate
elif [ ! -d "$PROJECT" ]; then
echo "$PROJECT missing and xcodegen not installed."; exit 1
fi
# ---- versioning -------------------------------------------------------------
# The Info.plists hold the literal "$(MARKETING_VERSION)" (resolved at build
# time), so read the real value from project.yml. The build number is stamped
# per-platform by Scripts/update_build_number.sh during the archive (even for
# iOS, odd for macOS — see that script); shown here for reference.
BUILD_NUMBER="$(git rev-list HEAD --count)"
MARKETING_VERSION="$(grep -m1 'MARKETING_VERSION:' project.yml | sed -E 's/.*"([^"]+)".*/\1/')"
echo "📦 Version $MARKETING_VERSION — iOS build $((BUILD_NUMBER * 2)), macOS build $((BUILD_NUMBER * 2 + 1))"
mkdir -p "$BUILD_DIR"
# ---- per-platform archive + upload ------------------------------------------
# args: <scheme> <generic destination> <ExportOptions plist>
archive_and_upload() {
local scheme="$1" destination="$2" export_plist="$3"
local archive_path="$BUILD_DIR/$scheme.xcarchive"
local export_path="$BUILD_DIR/$scheme-export"
echo ""
echo "──────── $scheme ────────"
rm -rf "$archive_path" "$export_path"
echo "🛠 Archiving $scheme ..."
xcodebuild archive \
-project "$PROJECT" \
-scheme "$scheme" \
-configuration Release \
-destination "$destination" \
-archivePath "$archive_path" \
CODE_SIGN_STYLE=Automatic \
DEVELOPMENT_TEAM="$APPLE_TEAM_ID" \
"${AUTH[@]}"
echo "🚀 Exporting + uploading $scheme to App Store Connect ..."
xcodebuild -exportArchive \
-archivePath "$archive_path" \
-exportPath "$export_path" \
-exportOptionsPlist "$export_plist" \
"${AUTH[@]}"
echo "$scheme uploaded."
}
do_ios() {
archive_and_upload "Notes" "generic/platform=iOS" "$SCRIPT_DIR/ExportOptions-iOS.plist"
}
do_mac() {
archive_and_upload "NotesMac" "generic/platform=macOS" "$SCRIPT_DIR/ExportOptions-macOS.plist"
}
case "$PLATFORM" in
ios) do_ios ;;
mac|macos) do_mac ;;
all) do_ios; do_mac ;;
*) echo "Usage: Scripts/release.sh [ios|mac|all]"; exit 1 ;;
esac
echo ""
echo "🎉 Done. Builds appear in App Store Connect > TestFlight after processing (~515 min)."