@@ -76,12 +76,12 @@ if [ "$USE_LOCAL_AXE" = true ] && [ -d "$AXE_LOCAL_DIR" ] && [ -f "$AXE_LOCAL_DI
7676 for framework in .build/release/* .framework; do
7777 if [ -d " $framework " ]; then
7878 echo " 📦 Copying framework: $( basename " $framework " ) "
79- cp -r " $framework " " $BUNDLED_DIR /Frameworks/"
79+ cp -R " $framework " " $BUNDLED_DIR /Frameworks/"
8080
8181 # Only copy nested frameworks if they exist
8282 if [ -d " $framework /Frameworks" ]; then
8383 echo " 📦 Found nested frameworks in $( basename " $framework " ) "
84- cp -r " $framework /Frameworks" /* " $BUNDLED_DIR /Frameworks/" 2> /dev/null || true
84+ cp -R " $framework /Frameworks" /* " $BUNDLED_DIR /Frameworks/" 2> /dev/null || true
8585 fi
8686 fi
8787 done
9595 echo " 📥 Downloading latest AXe release from GitHub..."
9696
9797 AXE_RELEASE_BASE_URL=" https://github.com/cameroncooke/AXe/releases/download/v${PINNED_AXE_VERSION} "
98+ AXE_HOMEBREW_URL=" ${AXE_RELEASE_BASE_URL} /AXe-macOS-homebrew-v${PINNED_AXE_VERSION} .tar.gz"
9899 AXE_UNIVERSAL_URL=" ${AXE_RELEASE_BASE_URL} /AXe-macOS-v${PINNED_AXE_VERSION} -universal.tar.gz"
99100 AXE_LEGACY_URL=" ${AXE_RELEASE_BASE_URL} /AXe-macOS-v${PINNED_AXE_VERSION} .tar.gz"
100101
101102 # Create temp directory
102103 mkdir -p " $AXE_TEMP_DIR "
103104 cd " $AXE_TEMP_DIR "
104105
105- # Download and extract the release
106- if curl -fL -o " axe-release.tar.gz" " $AXE_UNIVERSAL_URL " ; then
106+ # Download the release archive.
107+ # Prefer the Homebrew archive (unsigned, proper framework symlinks) so that
108+ # Homebrew can cleanly apply its own ad-hoc signatures during installation.
109+ # Fall back to universal, then legacy.
110+ if curl -fL -o " axe-release.tar.gz" " $AXE_HOMEBREW_URL " ; then
111+ AXE_ARCHIVE_FLAVOR=" homebrew"
112+ echo " ✅ Downloaded AXe homebrew archive"
113+ elif curl -fL -o " axe-release.tar.gz" " $AXE_UNIVERSAL_URL " ; then
107114 AXE_ARCHIVE_FLAVOR=" universal"
108115 if [ " $( uname -s) " != " Darwin" ]; then
109116 echo " ✅ Downloaded AXe universal archive for non-macOS bundling"
@@ -151,10 +158,10 @@ else
151158 mkdir -p " $BUNDLED_DIR /Frameworks"
152159
153160 if [ -d " Frameworks" ]; then
154- cp -r Frameworks/* " $BUNDLED_DIR /Frameworks/"
161+ cp -R Frameworks/* " $BUNDLED_DIR /Frameworks/"
155162 elif [ -d " lib" ]; then
156163 # Look for frameworks in lib directory
157- find lib -name " *.framework" -exec cp -r {} " $BUNDLED_DIR /Frameworks/" \;
164+ find lib -name " *.framework" -exec cp -R {} " $BUNDLED_DIR /Frameworks/" \;
158165 else
159166 echo " ⚠️ No frameworks directory found in release archive"
160167 echo " 📂 Contents of release archive:"
@@ -170,6 +177,61 @@ echo "📦 Copied $FRAMEWORK_COUNT frameworks"
170177echo " 🔍 Bundled frameworks:"
171178ls -la " $BUNDLED_DIR /Frameworks/"
172179
180+ # Validate bundled framework structure.
181+ # Catches two regressions that break Homebrew installations:
182+ # 1. Dereferenced symlinks (e.g. cp -r instead of cp -R) cause codesign to
183+ # report "bundle format is ambiguous (could be app or framework)".
184+ # 2. Pre-signed binaries in the homebrew archive would conflict with
185+ # Homebrew's own ad-hoc signing step.
186+ validate_framework_bundles () {
187+ local has_errors=false
188+
189+ while IFS= read -r framework_path; do
190+ local fw_name=" $( basename " $framework_path " .framework) "
191+
192+ # Versioned framework must have Versions/A
193+ if [ ! -d " $framework_path /Versions/A" ]; then
194+ continue
195+ fi
196+
197+ # Versions/Current must be a symlink to A
198+ if [ -d " $framework_path /Versions/Current" ] && [ ! -L " $framework_path /Versions/Current" ]; then
199+ echo " ❌ $fw_name : Versions/Current is a real directory, expected symlink to A"
200+ has_errors=true
201+ fi
202+
203+ # Top-level binary must be a symlink into Versions/Current/
204+ if [ -e " $framework_path /$fw_name " ] && [ ! -L " $framework_path /$fw_name " ]; then
205+ echo " ❌ $fw_name : top-level binary is a real file, expected symlink"
206+ has_errors=true
207+ fi
208+
209+ # For the homebrew archive, binaries must be unsigned
210+ if [ " $AXE_ARCHIVE_FLAVOR " = " homebrew" ]; then
211+ local fw_binary=" $framework_path /Versions/A/$fw_name "
212+ if [ -f " $fw_binary " ] && codesign -dv " $fw_binary " > /dev/null 2>&1 ; then
213+ echo " ❌ $fw_name : binary has a code signature but homebrew archive should be unsigned"
214+ has_errors=true
215+ fi
216+ fi
217+ done < <( find " $BUNDLED_DIR /Frameworks" -name " *.framework" -type d -maxdepth 1)
218+
219+ if [ " $AXE_ARCHIVE_FLAVOR " = " homebrew" ] && [ -f " $BUNDLED_DIR /axe" ]; then
220+ if codesign -dv " $BUNDLED_DIR /axe" > /dev/null 2>&1 ; then
221+ echo " ❌ axe binary has a code signature but homebrew archive should be unsigned"
222+ has_errors=true
223+ fi
224+ fi
225+
226+ if [ " $has_errors " = true ]; then
227+ echo " ❌ Framework bundle validation failed"
228+ exit 1
229+ fi
230+ echo " ✅ Framework bundle structure validated"
231+ }
232+
233+ validate_framework_bundles
234+
173235ad_hoc_sign_bundled_axe_assets () {
174236 echo " 🔏 Applying ad-hoc signatures to bundled AXe assets..."
175237
@@ -196,8 +258,8 @@ if [ "$OS_NAME" = "Darwin" ]; then
196258 ad_hoc_sign_bundled_axe_assets
197259 fi
198260
199- if [ " $AXE_ARCHIVE_FLAVOR " = " universal" ]; then
200- echo " ℹ️ Universal AXe archive detected; using ad-hoc signatures for local runtime compatibility"
261+ if [ " $AXE_ARCHIVE_FLAVOR " = " homebrew " ] || [ " $AXE_ARCHIVE_FLAVOR " = " universal" ]; then
262+ echo " ℹ️ ${AXE_ARCHIVE_FLAVOR} AXe archive detected; using ad-hoc signatures for local runtime compatibility"
201263 else
202264 echo " 🔏 Verifying AXe signatures..."
203265 if ! codesign --verify --deep --strict " $BUNDLED_DIR /axe" ; then
@@ -222,8 +284,8 @@ if [ "$OS_NAME" = "Darwin" ]; then
222284 done < <( find " $BUNDLED_DIR /Frameworks" -name " *.framework" -type d)
223285 fi
224286
225- if [ " $AXE_ARCHIVE_FLAVOR " = " universal" ]; then
226- echo " ℹ️ Skipping Gatekeeper assessment for universal AXe archive"
287+ if [ " $AXE_ARCHIVE_FLAVOR " = " homebrew " ] || [ " $AXE_ARCHIVE_FLAVOR " = " universal" ]; then
288+ echo " ℹ️ Skipping Gatekeeper assessment for ${AXE_ARCHIVE_FLAVOR} AXe archive"
227289 else
228290 echo " 🛡️ Assessing AXe with Gatekeeper..."
229291 SPCTL_LOG=" $( mktemp) "
0 commit comments