#!/usr/bin/env bash set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" APP_NAME="Firelink" CONFIGURATION="${CONFIGURATION:-release}" DEFAULT_MARKETING_VERSION="$(git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || true)" DEFAULT_BUILD_NUMBER="$(git rev-list --count HEAD 2>/dev/null || true)" MARKETING_VERSION="${MARKETING_VERSION:-${DEFAULT_MARKETING_VERSION:-0.1.0}}" BUILD_NUMBER="${BUILD_NUMBER:-${DEFAULT_BUILD_NUMBER:-1}}" SIGNING_IDENTITY="${CODE_SIGN_IDENTITY:-${SIGNING_IDENTITY:-}}" APP_DIR="$ROOT_DIR/build/$APP_NAME.app" CONTENTS_DIR="$APP_DIR/Contents" MACOS_DIR="$CONTENTS_DIR/MacOS" RESOURCES_DIR="$CONTENTS_DIR/Resources" ICON_NAME="AppIcon" cd "$ROOT_DIR" is_valid_mach_o() { local path="$1" if ! file "$path" | grep -q 'Mach-O'; then return 1 fi lipo -archs "$path" >/dev/null 2>&1 } ensure_ytdlp() { local ytdlp_path="$ROOT_DIR/Sources/Firelink/yt-dlp" local ytdlp_internal_path="$ROOT_DIR/Sources/Firelink/_internal" if [[ -x "$ytdlp_path" ]] && [[ -d "$ytdlp_internal_path" ]] && is_valid_mach_o "$ytdlp_path"; then return fi if ! command -v curl >/dev/null || ! command -v unzip >/dev/null; then echo "WARNING: yt-dlp or its runtime is missing or malformed, and curl/unzip are not available to refresh it." >&2 return fi echo "Refreshing bundled yt-dlp runtime..." local temp_dir temp_dir="$(mktemp -d)" curl -fsSL https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_macos.zip -o "$temp_dir/yt-dlp.zip" unzip -q -o "$temp_dir/yt-dlp.zip" -d "$temp_dir" cp "$temp_dir/yt-dlp_macos" "$ytdlp_path" chmod +x "$ytdlp_path" if [[ -d "$temp_dir/_internal" ]]; then rm -rf "$ROOT_DIR/Sources/Firelink/_internal" cp -R "$temp_dir/_internal" "$ROOT_DIR/Sources/Firelink/_internal" touch "$ROOT_DIR/Sources/Firelink/_internal/.gitkeep" fi rm -rf "$temp_dir" } ensure_ytdlp swift build -c "$CONFIGURATION" rm -rf "$APP_DIR" mkdir -p "$MACOS_DIR" "$RESOURCES_DIR" cp ".build/$CONFIGURATION/$APP_NAME" "$MACOS_DIR/$APP_NAME" cp "$ROOT_DIR/Resources/$ICON_NAME.icns" "$RESOURCES_DIR/$ICON_NAME.icns" cp "$ROOT_DIR/Sources/Firelink/Assets.xcassets/MenuBarIcon.imageset/MenuBarIconTemplate.png" "$RESOURCES_DIR/MenuBarIconTemplate.png" cp "$ROOT_DIR/Resources/GitHubTemplate.png" "$RESOURCES_DIR/GitHubTemplate.png" for media_engine in yt-dlp ffmpeg; do media_engine_path="$ROOT_DIR/Sources/Firelink/$media_engine" if [[ -x "$media_engine_path" ]]; then cp "$media_engine_path" "$RESOURCES_DIR/$media_engine" chmod +x "$RESOURCES_DIR/$media_engine" else echo "WARNING: $media_engine not found or not executable at $media_engine_path" fi done if [[ -d "$ROOT_DIR/Sources/Firelink/_internal" ]]; then cp -R "$ROOT_DIR/Sources/Firelink/_internal" "$RESOURCES_DIR/_internal" fi echo "Packaging Firefox extension..." mkdir -p "$RESOURCES_DIR/FirefoxExtension" cp "$ROOT_DIR/Extensions/Firefox/background.js" "$RESOURCES_DIR/FirefoxExtension/background.js" cp "$ROOT_DIR/Extensions/Firefox/content.js" "$RESOURCES_DIR/FirefoxExtension/content.js" cp "$ROOT_DIR/Extensions/Firefox/manifest.json" "$RESOURCES_DIR/FirefoxExtension/manifest.json" cp -R "$ROOT_DIR/Extensions/Firefox/icons" "$RESOURCES_DIR/FirefoxExtension/icons" cp -R "$ROOT_DIR/Extensions/Firefox/popup" "$RESOURCES_DIR/FirefoxExtension/popup" ARIA2C_PATH=$(which aria2c || true) if [[ -n "$ARIA2C_PATH" && -x "$ARIA2C_PATH" ]]; then echo "Bundling aria2c from $ARIA2C_PATH..." cp "$ARIA2C_PATH" "$RESOURCES_DIR/aria2c" if ! command -v dylibbundler &> /dev/null; then echo "Installing dylibbundler..." brew install dylibbundler fi FRAMEWORKS_DIR="$CONTENTS_DIR/Frameworks" mkdir -p "$FRAMEWORKS_DIR" dylibbundler -od -b -x "$RESOURCES_DIR/aria2c" -d "$FRAMEWORKS_DIR" -p "@executable_path/../Frameworks/" else echo "WARNING: aria2c not found! It will not be bundled. Please install it first." fi FRAMEWORKS_DIR="$CONTENTS_DIR/Frameworks" mkdir -p "$FRAMEWORKS_DIR" install_name_tool -add_rpath "@executable_path/../Frameworks" "$MACOS_DIR/$APP_NAME" || true cat > "$CONTENTS_DIR/Info.plist" < CFBundleDevelopmentRegion en CFBundleExecutable $APP_NAME CFBundleIdentifier local.firelink.swiftui CFBundleInfoDictionaryVersion 6.0 CFBundleName $APP_NAME CFBundleIconFile $ICON_NAME CFBundlePackageType APPL CFBundleShortVersionString $MARKETING_VERSION CFBundleVersion $BUILD_NUMBER LSMinimumSystemVersion 14.0 LSApplicationCategoryType public.app-category.utilities NSHighResolutionCapable NSAppleEventsUsageDescription Firelink needs permission to control Finder so it can sleep, restart, or shut down your Mac after scheduled downloads finish. CFBundleURLTypes CFBundleURLName local.firelink.swiftui CFBundleURLSchemes firelink PLIST if command -v codesign &> /dev/null; then if [[ -n "$SIGNING_IDENTITY" ]]; then CODESIGN_ARGS=(--force --timestamp --options runtime --sign "$SIGNING_IDENTITY") echo "Signing app bundle with identity: $SIGNING_IDENTITY" else CODESIGN_ARGS=(--force --sign -) echo "Ad-hoc signing app bundle for local use." fi sign_path() { local path="$1" codesign "${CODESIGN_ARGS[@]}" "$path" } sign_mach_o_file() { local path="$1" if file "$path" | grep -q 'Mach-O'; then if ! sign_path "$path"; then echo "WARNING: Could not sign Mach-O executable for local build: $path" >&2 fi fi } if [[ -d "$FRAMEWORKS_DIR" ]]; then while IFS= read -r -d '' executable_path; do sign_mach_o_file "$executable_path" done < <(find "$FRAMEWORKS_DIR" -type f -print0) while IFS= read -r -d '' bundle_path; do sign_path "$bundle_path" done < <(find "$FRAMEWORKS_DIR" \( -name "*.xpc" -o -name "*.framework" \) -type d -depth -print0) fi while IFS= read -r -d '' executable_path; do sign_mach_o_file "$executable_path" done < <(find "$RESOURCES_DIR" -type f -print0) sign_path "$MACOS_DIR/$APP_NAME" sign_path "$APP_DIR" codesign --verify --deep --verbose=2 "$APP_DIR" || true fi echo "Created $APP_DIR"