bundle_app.sh
1#!/bin/bash2# Bundle Haven into a signed .app and zip it for distribution.3#4# Driven by koh offer --builds via koh.build, which sets KOH_TARGET5# per-platform. macos-aarch64 builds natively; macos-x86_64 cross-compiles6# from Apple Silicon via Swift's --triple flag.7#8# Platform archives stay in .build so release artifacts are not saved in history.9set -euo pipefail1011APP_NAME="Haven"12BUNDLE="${APP_NAME}.app"13ZIP="${KOH_TARGET:+.build/}${APP_NAME}${KOH_TARGET:+-${KOH_TARGET}}.app.zip"14ENTITLEMENTS="${APP_NAME}.entitlements"1516# Wipe stale outputs so re-runs are idempotent and we never ship a17# half-codesigned bundle left over from a previous attempt. Use the18# project-required `trash` command rather than `rm -rf`: it preserves19# recovery via the macOS Trash, which is the project's standing deletion20# convention. Missing either artifact is fine — `trash` is a no-op for21# paths that don't exist.22trash "${BUNDLE}" "${ZIP}" 2>/dev/null || true2324# Build the executable in release mode. Package.swift declares the product25# as `Haven` (capital H); .build/release is a symlink to26# <arch>-apple-macosx/release so this path works for both native and27# cross-compiled builds.28case "${KOH_TARGET:-}" in29 macos-x86_64)30 echo " → swift build -c release --triple x86_64-apple-macosx"31 swift build -c release --triple x86_64-apple-macosx32 ;;33 macos-aarch64|"")34 echo " → swift build -c release"35 swift build -c release36 ;;37 *)38 echo " → swift build -c release (unknown KOH_TARGET='${KOH_TARGET}', defaulting to native)"39 swift build -c release40 ;;41esac4243# Verify the binary actually materialised where we expect it. Catching this44# here beats waiting for koh's "output binary not found" message at the45# upload step.46if [ ! -f ".build/release/${APP_NAME}" ]; then47 echo "✗ .build/release/${APP_NAME} not produced" >&248 exit 149fi5051# Assemble the bundle. The directory shape is what Finder + codesign expect:52# Contents/MacOS/<executable>, Contents/Info.plist, Contents/Resources/.53mkdir -p "${BUNDLE}/Contents/MacOS"54mkdir -p "${BUNDLE}/Contents/Resources"55cp ".build/release/${APP_NAME}" "${BUNDLE}/Contents/MacOS/"56cp Info.plist "${BUNDLE}/Contents/"57if [ -f AppIcon.icns ]; then58 cp AppIcon.icns "${BUNDLE}/Contents/Resources/"59fi6061# Ad-hoc sign with the project's entitlements. Some macOS APIs require even62# an ad-hoc signature to recognise the bundle as runnable.63codesign --force --deep --entitlements "${ENTITLEMENTS}" --sign - "${BUNDLE}"6465# Zip with ditto — the canonical Apple tool. It preserves resource forks66# and extended attributes, which plain `zip` would mangle, and --keepParent67# stores Haven.app/ at the top of the archive so unzip reproduces the68# standard layout.69ditto -c -k --sequesterRsrc --keepParent "${BUNDLE}" "${ZIP}"7071echo "${ZIP} is ready."