listless

A simple list app for Apple platforms
Log | Files | Refs | README | LICENSE

commit cc89aadad02adbe78696fedc38362c7c21b1f1ce
parent a18a5e59728f1e9182b392786ae8ee9dd1bdff48
Author: Michael Camilleri <[email protected]>
Date:   Mon,  2 Mar 2026 10:20:24 +0900

Add build and publishing scripts

Diffstat:
MAGENTS.md | 15+++++++++------
AScripts/publish-ios.sh | 118+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AScripts/publish-macos.sh | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 235 insertions(+), 6 deletions(-)

diff --git a/AGENTS.md b/AGENTS.md @@ -21,20 +21,23 @@ - `xcodebuild test -scheme "Listless iOS" -destination 'platform=iOS Simulator,name=iPhone 16,OS=18.6'` runs unit + UI tests. - `swift format lint --recursive .` must be clean before opening a PR. -## TestFlight Release -- Publish scripts live in `.asc/` and handle archiving, signing, exporting, and uploading via `xcrun iTMSTransporter`. +## App Store Connect Release +- Publish scripts live in `Scripts/` and handle archiving, signing, exporting, and uploading via `xcrun iTMSTransporter`. +- Secrets (team ID, API key ID, issuer ID, p12 passwords) are sourced from `.asc/secrets.sh`, which is gitignored. - Signing uses a temporary keychain (created and cleaned up by the scripts) with distribution `.p12` files from `.asc/`. No login keychain unlock is required. - App Store Connect API auth uses a `.p8` key file in `.asc/`, referenced by `$KEY_ID` from `secrets.sh`. - Build numbers come from scheme pre-action (`YEAR.COMMIT_COUNT`), so archiving from latest `HEAD` produces the latest-commit build. - Internal testers automatically receive all builds; no explicit group assignment is needed. +- Both scripts archive with signing enabled to preserve entitlements (CloudKit, App Sandbox). Do not disable code signing during the archive step. ### iOS -- Run `.asc/publish-ios.sh` from the repo root. -- Archives without code signing, then exports an IPA with manual signing (`iPhone Distribution` cert, `Listless iOS Distribution` profile). +- Run `Scripts/publish-ios.sh` from the repo root. +- Pass `--check` to archive, export, and inspect entitlements without uploading. +- Archives with signing, then exports an IPA with manual signing (`iPhone Distribution` cert, `Listless iOS Distribution` profile). ### macOS -- Run `.asc/publish-macos.sh` from the repo root. -- Archives with signing (required to preserve App Sandbox entitlements), then exports a `.pkg` with manual signing (`3rd Party Mac Developer Application` + `3rd Party Mac Developer Installer` certs, `Listless macOS Distribution` profile). +- Run `Scripts/publish-macos.sh` from the repo root. +- Archives with signing, then exports a `.pkg` with manual signing (`3rd Party Mac Developer Application` + `3rd Party Mac Developer Installer` certs, `Listless macOS Distribution` profile). ## Build Number - `CFBundleVersion` in both Info.plist files uses `$(CURRENT_PROJECT_VERSION)`, sourced from `Generated/BuildNumber.xcconfig`. diff --git a/Scripts/publish-ios.sh b/Scripts/publish-ios.sh @@ -0,0 +1,118 @@ +#!/bin/bash +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +SECRETS="$REPO_ROOT/.asc/secrets.sh" + +if [[ ! -f "$SECRETS" ]]; then + echo "Error: $SECRETS not found. See .asc/secrets.sh.example for the required format." + exit 1 +fi + +source "$SECRETS" + +SCHEME="Listless iOS" +ARCHIVE_PATH="/tmp/Listless-latest.xcarchive" +EXPORT_PATH="/tmp/Listless-export" +EXPORT_PLIST="/tmp/Listless-ExportOptions.plist" +IPA_PATH="$EXPORT_PATH/Listless iOS.ipa" +DIST_P12="$REPO_ROOT/.asc/ios-signing/dist-headless.p12" +TMP_KEYCHAIN="$REPO_ROOT/.asc/build.keychain-db" + +CHECK_ONLY=false +if [[ "${1:-}" == "--check" ]]; then + CHECK_ONLY=true +fi + +cd "$REPO_ROOT" + +echo "==> Setting up temporary keychain..." +security delete-keychain "$TMP_KEYCHAIN" 2>/dev/null || true +security create-keychain -p "$TMP_KEYCHAIN_PASS" "$TMP_KEYCHAIN" +security unlock-keychain -p "$TMP_KEYCHAIN_PASS" "$TMP_KEYCHAIN" +security set-keychain-settings -lut 21600 "$TMP_KEYCHAIN" +security import "$DIST_P12" -k "$TMP_KEYCHAIN" -P "$DIST_P12_PASS" \ + -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild +security set-key-partition-list -S apple-tool:,apple:,codesign:,productbuild: \ + -s -k "$TMP_KEYCHAIN_PASS" "$TMP_KEYCHAIN" +security list-keychains -d user -s "$TMP_KEYCHAIN" ~/Library/Keychains/login.keychain-db + +cleanup_keychain() { + echo "==> Restoring keychain search list..." + security list-keychains -d user -s ~/Library/Keychains/login.keychain-db + security default-keychain -d user -s ~/Library/Keychains/login.keychain-db + security delete-keychain "$TMP_KEYCHAIN" 2>/dev/null || true +} +trap cleanup_keychain EXIT + +echo "==> Archiving $SCHEME..." +xcodebuild \ + -scheme "$SCHEME" \ + -project Listless.xcodeproj \ + -configuration Release \ + -destination 'generic/platform=iOS' \ + -archivePath "$ARCHIVE_PATH" \ + archive + +echo "==> Writing export options..." +cat > "$EXPORT_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>method</key> + <string>app-store-connect</string> + <key>signingStyle</key> + <string>manual</string> + <key>teamID</key> + <string>$TEAM_ID</string> + <key>signingCertificate</key> + <string>iPhone Distribution</string> + <key>provisioningProfiles</key> + <dict> + <key>net.inqk.listless</key> + <string>Listless iOS Distribution</string> + </dict> + <key>destination</key> + <string>export</string> + <key>stripSwiftSymbols</key> + <true/> + <key>manageAppVersionAndBuildNumber</key> + <false/> +</dict> +</plist> +PLIST + +echo "==> Exporting IPA..." +xcodebuild \ + -exportArchive \ + -archivePath "$ARCHIVE_PATH" \ + -exportPath "$EXPORT_PATH" \ + -exportOptionsPlist "$EXPORT_PLIST" + +echo "==> Checking entitlements in exported IPA..." +CHECK_DIR="/tmp/Listless-ipa-check" +rm -rf "$CHECK_DIR" +unzip -q "$IPA_PATH" -d "$CHECK_DIR" +codesign -d --entitlements - "$CHECK_DIR/Payload/Listless iOS.app" +rm -rf "$CHECK_DIR" + +if $CHECK_ONLY; then + echo "==> Check complete. Skipping upload." + exit 0 +fi + +echo "==> Uploading to App Store Connect..." +mkdir -p "$REPO_ROOT/private_keys" +cp "$REPO_ROOT/.asc/AuthKey_${KEY_ID}.p8" "$REPO_ROOT/private_keys/" +xcrun iTMSTransporter \ + -m upload \ + -assetFile "$IPA_PATH" \ + -apiKey "$KEY_ID" \ + -apiIssuer "$ISSUER_ID" \ + -v informational +rm -f "$REPO_ROOT/private_keys/AuthKey_${KEY_ID}.p8" +rmdir "$REPO_ROOT/private_keys" 2>/dev/null || true + +echo "==> Done!" diff --git a/Scripts/publish-macos.sh b/Scripts/publish-macos.sh @@ -0,0 +1,108 @@ +#!/bin/bash +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +SECRETS="$REPO_ROOT/.asc/secrets.sh" + +if [[ ! -f "$SECRETS" ]]; then + echo "Error: $SECRETS not found. See .asc/secrets.sh.example for the required format." + exit 1 +fi + +source "$SECRETS" + +APP_ID="6759801710" +SCHEME="Listless macOS" +ARCHIVE_PATH="/tmp/Listless-mac-latest.xcarchive" +EXPORT_PATH="/tmp/Listless-mac-export" +EXPORT_PLIST="/tmp/Listless-mac-ExportOptions.plist" +PKG_PATH="$EXPORT_PATH/Listless.pkg" +SIGNING_DIR="$REPO_ROOT/.asc/macos-signing" +APP_P12="$SIGNING_DIR/app-headless.p12" +INSTALLER_P12="$SIGNING_DIR/installer-headless.p12" +TMP_KEYCHAIN="$REPO_ROOT/.asc/build.keychain-db" + +cd "$REPO_ROOT" + +echo "==> Setting up temporary keychain..." +security delete-keychain "$TMP_KEYCHAIN" 2>/dev/null || true +security create-keychain -p "$TMP_KEYCHAIN_PASS" "$TMP_KEYCHAIN" +security unlock-keychain -p "$TMP_KEYCHAIN_PASS" "$TMP_KEYCHAIN" +security set-keychain-settings -lut 21600 "$TMP_KEYCHAIN" +security import "$APP_P12" -k "$TMP_KEYCHAIN" -P "$DIST_P12_PASS" \ + -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild +security import "$INSTALLER_P12" -k "$TMP_KEYCHAIN" -P "$DIST_P12_PASS" \ + -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild +security set-key-partition-list -S apple-tool:,apple:,codesign:,productbuild: \ + -s -k "$TMP_KEYCHAIN_PASS" "$TMP_KEYCHAIN" +security list-keychains -d user -s "$TMP_KEYCHAIN" ~/Library/Keychains/login.keychain-db + +cleanup_keychain() { + echo "==> Restoring keychain search list..." + security list-keychains -d user -s ~/Library/Keychains/login.keychain-db + security default-keychain -d user -s ~/Library/Keychains/login.keychain-db + security delete-keychain "$TMP_KEYCHAIN" 2>/dev/null || true +} +trap cleanup_keychain EXIT + +echo "==> Archiving $SCHEME..." +xcodebuild \ + -scheme "$SCHEME" \ + -project Listless.xcodeproj \ + -configuration Release \ + -destination 'generic/platform=macOS' \ + -archivePath "$ARCHIVE_PATH" \ + archive + +echo "==> Writing export options..." +cat > "$EXPORT_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>method</key> + <string>app-store-connect</string> + <key>signingStyle</key> + <string>manual</string> + <key>teamID</key> + <string>$TEAM_ID</string> + <key>signingCertificate</key> + <string>3rd Party Mac Developer Application</string> + <key>installerSigningCertificate</key> + <string>3rd Party Mac Developer Installer</string> + <key>provisioningProfiles</key> + <dict> + <key>net.inqk.listless</key> + <string>Listless macOS Distribution</string> + </dict> + <key>destination</key> + <string>export</string> + <key>stripSwiftSymbols</key> + <true/> + <key>manageAppVersionAndBuildNumber</key> + <false/> +</dict> +</plist> +PLIST + +echo "==> Exporting PKG..." +xcodebuild \ + -exportArchive \ + -archivePath "$ARCHIVE_PATH" \ + -exportPath "$EXPORT_PATH" \ + -exportOptionsPlist "$EXPORT_PLIST" + +echo "==> Uploading to App Store Connect..." +mkdir -p "$REPO_ROOT/private_keys" +cp "$REPO_ROOT/.asc/AuthKey_${KEY_ID}.p8" "$REPO_ROOT/private_keys/" +xcrun iTMSTransporter \ + -m upload \ + -assetFile "$PKG_PATH" \ + -apiKey "$KEY_ID" \ + -apiIssuer "$ISSUER_ID" \ + -v informational +rm -f "$REPO_ROOT/private_keys/AuthKey_${KEY_ID}.p8" +rmdir "$REPO_ROOT/private_keys" 2>/dev/null || true + +echo "==> Done!"