#!/bin/bash # Bundle Haven into a signed .app and zip it for distribution. # # Driven by koh offer --builds via kepr.build, which sets KEPR_PLATFORM # per-platform. macos-aarch64 builds natively; macos-x86_64 cross-compiles # from Apple Silicon via Swift's --triple flag. # # Output: Haven.app.zip in the repo root, ready for koh to upload as the # release artifact for this platform. set -euo pipefail APP_NAME="Haven" BUNDLE="${APP_NAME}.app" ZIP="${APP_NAME}.app.zip" ENTITLEMENTS="${APP_NAME}.entitlements" # Wipe stale outputs so re-runs are idempotent and we never ship a # half-codesigned bundle left over from a previous attempt. Use the # project-required `trash` command rather than `rm -rf`: it preserves # recovery via the macOS Trash, which is the project's standing deletion # convention. Missing either artifact is fine — `trash` is a no-op for # paths that don't exist. trash "${BUNDLE}" "${ZIP}" 2>/dev/null || true # Build the executable in release mode. Package.swift declares the product # as `Haven` (capital H); .build/release is a symlink to # -apple-macosx/release so this path works for both native and # cross-compiled builds. case "${KEPR_PLATFORM:-}" in macos-x86_64) echo " → swift build -c release --triple x86_64-apple-macosx" swift build -c release --triple x86_64-apple-macosx ;; macos-aarch64|"") echo " → swift build -c release" swift build -c release ;; *) echo " → swift build -c release (unknown KEPR_PLATFORM='${KEPR_PLATFORM}', defaulting to native)" swift build -c release ;; esac # Verify the binary actually materialised where we expect it. Catching this # here beats waiting for koh's "output binary not found" message at the # upload step. if [ ! -f ".build/release/${APP_NAME}" ]; then echo "✗ .build/release/${APP_NAME} not produced" >&2 exit 1 fi # Assemble the bundle. The directory shape is what Finder + codesign expect: # Contents/MacOS/, Contents/Info.plist, Contents/Resources/. mkdir -p "${BUNDLE}/Contents/MacOS" mkdir -p "${BUNDLE}/Contents/Resources" cp ".build/release/${APP_NAME}" "${BUNDLE}/Contents/MacOS/" cp Info.plist "${BUNDLE}/Contents/" if [ -f AppIcon.icns ]; then cp AppIcon.icns "${BUNDLE}/Contents/Resources/" fi # Ad-hoc sign with the project's entitlements. Some macOS APIs require even # an ad-hoc signature to recognise the bundle as runnable. codesign --force --deep --entitlements "${ENTITLEMENTS}" --sign - "${BUNDLE}" # Zip with ditto — the canonical Apple tool. It preserves resource forks # and extended attributes, which plain `zip` would mangle, and --keepParent # stores Haven.app/ at the top of the archive so unzip reproduces the # standard layout. ditto -c -k --sequesterRsrc --keepParent "${BUNDLE}" "${ZIP}" echo "${ZIP} is ready."