← All repositories

listless

A simple list app for Apple platforms

git clone https://code.inqk.net/listless.git


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!"