#!/usr/bin/env bash set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" DIST="$ROOT/dist" APP="$DIST/PromptGarage.app" INFO_PLIST="$ROOT/AppBundle/Info.plist" fail() { printf 'build-app: %s\n' "$*" >&2 exit 1 } require_command() { command -v "$1" >/dev/null 2>&1 || fail "required command not found: $1" } assert_supported_host() { [[ "$(uname -s)" == "Darwin" ]] || fail "this release build must run on macOS" [[ "$(uname -m)" == "arm64" ]] || fail "this release build requires an Apple Silicon (arm64) Mac" local macos_version macos_major xcode_version xcode_major swift_version macos_version="$(sw_vers -productVersion)" macos_major="${macos_version%%.*}" [[ "$macos_major" =~ ^[0-9]+$ ]] || fail "could not parse macOS version: $macos_version" (( macos_major >= 26 )) || fail "macOS 26 or newer is required; found $macos_version" xcode_version="$(xcodebuild -version | awk 'NR == 1 { print $2 }')" xcode_major="${xcode_version%%.*}" [[ "$xcode_major" =~ ^[0-9]+$ ]] || fail "could not parse Xcode version: $xcode_version" (( xcode_major >= 26 )) || fail "Xcode 26 or newer is required; found $xcode_version" swift_version="$(swift --version | sed -n '1s/.*Swift version \([0-9][0-9.]*\).*/\1/p')" [[ "$swift_version" == 6.2* ]] || fail "Swift 6.2 is required; found ${swift_version:-unknown}" } assert_exact_arm64() { local binary="$1" local architectures architectures="$(lipo -archs "$binary" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" [[ "$architectures" == "arm64" ]] || fail "expected exactly arm64, found: ${architectures:-no architecture}" } for command_name in uname sw_vers xcodebuild swift awk sed lipo codesign plutil install mktemp; do require_command "$command_name" done assert_supported_host [[ -f "$INFO_PLIST" ]] || fail "missing bundle metadata: $INFO_PLIST" plutil -lint "$INFO_PLIST" >/dev/null || fail "invalid Info.plist" cd "$ROOT" swift build --configuration release --arch arm64 BIN_DIR="$(swift build --configuration release --arch arm64 --show-bin-path)" BINARY="$BIN_DIR/PromptGarage" [[ -x "$BINARY" ]] || fail "release executable not found: $BINARY" assert_exact_arm64 "$BINARY" mkdir -p "$DIST" STAGING_ROOT="$(mktemp -d "$DIST/.PromptGarage-build.XXXXXX")" STAGING_APP="$STAGING_ROOT/PromptGarage.app" cleanup() { local status=$? trap - EXIT if [[ -n "${STAGING_ROOT:-}" && -d "$STAGING_ROOT" ]]; then rm -rf "$STAGING_ROOT" fi exit "$status" } trap cleanup EXIT mkdir -p "$STAGING_APP/Contents/MacOS" "$STAGING_APP/Contents/Resources" install -m 0755 "$BINARY" "$STAGING_APP/Contents/MacOS/PromptGarage" install -m 0644 "$INFO_PLIST" "$STAGING_APP/Contents/Info.plist" assert_exact_arm64 "$STAGING_APP/Contents/MacOS/PromptGarage" codesign --force --sign - --timestamp=none "$STAGING_APP" codesign --verify --deep --strict --verbose=2 "$STAGING_APP" # dist/ contains generated release output only. Replace that one known bundle # after the complete staging bundle has passed architecture and signature checks. if [[ -e "$APP" ]]; then rm -rf "$APP" fi mv "$STAGING_APP" "$APP" rmdir "$STAGING_ROOT" STAGING_ROOT="" trap - EXIT assert_exact_arm64 "$APP/Contents/MacOS/PromptGarage" codesign --verify --deep --strict --verbose=2 "$APP" printf 'Built and ad-hoc signed: %s\n' "$APP"