#!/bin/zsh # Build, package, install, and launch the AI Music Studio macOS application. set -euo pipefail PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)" APP_DISPLAY_NAME="${APP_DISPLAY_NAME:-AI Music Studio}" EXECUTABLE_NAME="${EXECUTABLE_NAME:-music-assistant}" BUNDLE_IDENTIFIER="${BUNDLE_IDENTIFIER:-com.musicassistant.app}" APP_BUNDLE_NAME="${APP_DISPLAY_NAME}.app" BUILD_DIR="${PROJECT_ROOT}/.build/app" APP_BUNDLE="${BUILD_DIR}/${APP_BUNDLE_NAME}" INSTALL_DIR="${INSTALL_DIR:-/Applications}" INSTALL_PATH="${INSTALL_DIR}/${APP_BUNDLE_NAME}" CONTENTS_DIR="${APP_BUNDLE}/Contents" MACOS_DIR="${CONTENTS_DIR}/MacOS" RESOURCES_DIR="${CONTENTS_DIR}/Resources" fail() { print -u2 -- "Error: $1" exit 1 } step() { print -- "==> $1" } find_app_icon() { if [[ -n "${APP_ICON:-}" ]]; then [[ -f "${APP_ICON}" ]] || fail "App icon was not found at ${APP_ICON}." print -- "${APP_ICON}" return fi local candidate for candidate in \ "${PROJECT_ROOT}/Resources/AI Music Studio.icns" \ "${PROJECT_ROOT}/Resources/AppIcon.icns" \ "${PROJECT_ROOT}/Resources/MusicAssistant.icns"; do if [[ -f "${candidate}" ]]; then print -- "${candidate}" return fi done } [[ -f "${PROJECT_ROOT}/Package.swift" ]] || fail "Package.swift was not found in ${PROJECT_ROOT}." command -v swift >/dev/null 2>&1 || fail "Swift is required. Install Xcode or the Xcode Command Line Tools." command -v codesign >/dev/null 2>&1 || fail "codesign is required and should be available with Xcode." command -v ditto >/dev/null 2>&1 || fail "ditto is required and should be available on macOS." command -v open >/dev/null 2>&1 || fail "open is required and should be available on macOS." cd "${PROJECT_ROOT}" step "Stopping any running ${APP_DISPLAY_NAME} application" if pgrep -x "${EXECUTABLE_NAME}" >/dev/null 2>&1; then pkill -x "${EXECUTABLE_NAME}" || fail "Could not stop the running ${APP_DISPLAY_NAME} application." for _ in {1..20}; do pgrep -x "${EXECUTABLE_NAME}" >/dev/null 2>&1 || break sleep 0.1 done pgrep -x "${EXECUTABLE_NAME}" >/dev/null 2>&1 && fail "${APP_DISPLAY_NAME} is still running. Quit it and retry." fi step "Removing stale installed copy" if [[ -e "${INSTALL_PATH}" ]]; then rm -rf "${INSTALL_PATH}" || fail "Could not remove ${INSTALL_PATH}. Check permissions and retry." fi step "Cleaning local Swift build artifacts" swift package clean || fail "Swift Package Manager could not clean the local build artifacts." step "Building ${EXECUTABLE_NAME} in release mode" BIN_PATH="$(swift build -c release --show-bin-path 2>/dev/null | tail -n1)" SOURCE_EXECUTABLE="${BIN_PATH}/${EXECUTABLE_NAME}" built=0 for attempt in 1 2 3; do swift build -c release || fail "Release build failed. Review the Swift compiler diagnostics above." if [[ -x "${SOURCE_EXECUTABLE}" ]]; then built=1 break fi print -- "==> Executable not present after build (attempt ${attempt}); retrying" sleep 1 done [[ "${built}" -eq 1 ]] || fail "Built executable was not found at ${SOURCE_EXECUTABLE}." step "Packaging ${APP_BUNDLE_NAME}" rm -rf "${APP_BUNDLE}" mkdir -p "${MACOS_DIR}" "${RESOURCES_DIR}" cp "${SOURCE_EXECUTABLE}" "${MACOS_DIR}/${EXECUTABLE_NAME}" ICON_PATH="$(find_app_icon)" PLIST_ICON_ENTRY="" if [[ -n "${ICON_PATH}" ]]; then ICON_FILE_NAME="$(basename "${ICON_PATH}")" ICON_FILE_BASE="${ICON_FILE_NAME:r}" cp "${ICON_PATH}" "${RESOURCES_DIR}/${ICON_FILE_NAME}" PLIST_ICON_ENTRY=$' CFBundleIconFile\n '"${ICON_FILE_BASE}"$'' else step "No app icon found; packaging without a custom icon" fi cat > "${CONTENTS_DIR}/Info.plist" < CFBundleDevelopmentRegion en CFBundleDisplayName ${APP_DISPLAY_NAME} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 ${PLIST_ICON_ENTRY} CFBundleName ${APP_DISPLAY_NAME} CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleVersion 1 LSMinimumSystemVersion 14.0 NSHighResolutionCapable NSPrincipalClass NSApplication PLIST plutil -lint "${CONTENTS_DIR}/Info.plist" >/dev/null || fail "Generated Info.plist is invalid." codesign --force --sign - --timestamp=none "${APP_BUNDLE}" || fail "Ad-hoc signing of ${APP_BUNDLE_NAME} failed." codesign --verify --deep --strict "${APP_BUNDLE}" || fail "Packaged ${APP_BUNDLE_NAME} failed code-signature verification." [[ -x "${MACOS_DIR}/${EXECUTABLE_NAME}" ]] || fail "Packaged executable is missing." step "Installing ${APP_BUNDLE_NAME} in ${INSTALL_DIR}" ditto "${APP_BUNDLE}" "${INSTALL_PATH}" || fail "Could not install ${APP_BUNDLE_NAME} in ${INSTALL_DIR}. Check permissions and retry." INSTALLED_EXECUTABLE="${INSTALL_PATH}/Contents/MacOS/${EXECUTABLE_NAME}" [[ -x "${INSTALLED_EXECUTABLE}" ]] || fail "Installed executable is missing at ${INSTALLED_EXECUTABLE}." SOURCE_HASH="$(shasum -a 256 "${MACOS_DIR}/${EXECUTABLE_NAME}" | awk '{print $1}')" INSTALLED_HASH="$(shasum -a 256 "${INSTALLED_EXECUTABLE}" | awk '{print $1}')" [[ "${SOURCE_HASH}" == "${INSTALLED_HASH}" ]] || fail "Installed executable does not match the newly built executable." step "Launching ${APP_DISPLAY_NAME}" open -n "${INSTALL_PATH}" || fail "${APP_DISPLAY_NAME} was installed but could not be opened." print -- "==> ${APP_DISPLAY_NAME} was rebuilt, installed, and launched successfully."