#!/usr/bin/env bash set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" INFO_PLIST="$ROOT_DIR/Apptag/Info.plist" APPTAG_APP_SWIFT="$ROOT_DIR/Apptag/ApptagApp.swift" PREFERENCES_VIEW_SWIFT="$ROOT_DIR/Apptag/PreferencesView.swift" L10N_SWIFT="$ROOT_DIR/Apptag/L10n.swift" LOCALIZATION_DIR="$ROOT_DIR/Apptag/Localization" fail() { printf 'FAIL: %s\n' "$*" >&2 exit 1 } [[ -f "$INFO_PLIST" ]] || fail "missing Info.plist" [[ -f "$APPTAG_APP_SWIFT" ]] || fail "missing ApptagApp.swift" [[ -f "$PREFERENCES_VIEW_SWIFT" ]] || fail "missing PreferencesView.swift" [[ -f "$L10N_SWIFT" ]] || fail "missing L10n.swift" [[ -d "$LOCALIZATION_DIR" ]] || fail "missing Localization directory" plutil -lint "$INFO_PLIST" >/dev/null || fail "Info.plist is not valid" SCHEME=$(/usr/libexec/PlistBuddy -c "Print :CFBundleURLTypes:0:CFBundleURLSchemes:0" "$INFO_PLIST" 2>/dev/null || true) [[ "$SCHEME" == "taglauncher" ]] || fail "Info.plist must register taglauncher URL scheme" VERSION=$(/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "$INFO_PLIST") BUILD=$(/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "$INFO_PLIST") [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || fail "invalid CFBundleShortVersionString: $VERSION" [[ "$BUILD" =~ ^[0-9]{8}\.[0-9]{4}$ ]] || fail "invalid CFBundleVersion: $BUILD" python3 - "$ROOT_DIR" <<'PY' import json import pathlib import re import sys root = pathlib.Path(sys.argv[1]) app = (root / "Apptag" / "ApptagApp.swift").read_text(encoding="utf-8") preferences = (root / "Apptag" / "PreferencesView.swift").read_text(encoding="utf-8") l10n = (root / "Apptag" / "L10n.swift").read_text(encoding="utf-8") localization_dir = root / "Apptag" / "Localization" def fail(message: str) -> None: print(f"FAIL: {message}", file=sys.stderr) sys.exit(1) def require(pattern: str, source: str, message: str) -> None: if not re.search(pattern, source, re.S): fail(message) require( r"func\s+application\s*\(\s*_\s+application\s*:\s*NSApplication\s*,\s*open\s+urls\s*:\s*\[URL\]\s*\)", app, "AppDelegate must implement application(_:open:) for URL scheme activation", ) require(r'externalInvocationScheme\s*=\s*"taglauncher"', app, "URL scheme constant must be taglauncher") require(r'externalInvocationShowHost\s*=\s*"show"', app, "URL show route constant must be show") require(r"pendingShowOverlayInvocation", app, "cold-launch URL requests must have a pending show flag") require(r"didFinishLaunching", app, "URL requests must be gated on launch readiness") require( r"private\s+func\s+requestShowOverlayFromExternalInvocation\s*\(\)\s*\{(?P
.*?)showOrFocusOverlay\s*\(", app, "external show invocation must call showOrFocusOverlay()", ) request_match = re.search( r"private\s+func\s+requestShowOverlayFromExternalInvocation\s*\(\)\s*\{(?P.*?)\n\s*private\s+func\s+consumePendingShowOverlayInvocationIfNeeded", app, re.S, ) if not request_match: fail("could not inspect requestShowOverlayFromExternalInvocation()") request_body = request_match.group("body") if "performToggleOverlay" in request_body or "toggleOverlay" in request_body: fail("external URL show must not use toggle overlay behavior") if "dismissQuickSearchIfNeeded()" not in request_body: fail("external URL show must dismiss conflicting Quick Search transient state") require( r"private\s+func\s+consumePendingShowOverlayInvocationIfNeeded\s*\(\)\s*\{(?P.*?)requestShowOverlayFromExternalInvocation\s*\(\)", app, "launch completion must consume pending URL show requests", ) require( r"quickSearch.globalHotkey(?P.*?)trackpadGesture.title(?P