fix(macos): Avoid Bundle.module crash in DeviceModelCatalog

Bundle.module is a SwiftPM-generated computed property that calls
fatalError() when the resource bundle isn't found. This happens in
packaged .app bundles where SwiftPM's expected bundle paths differ
from the actual layout.

The fix uses manual bundle lookup (similar to ClawdbotKitResources.swift
and Textual's Bundle.textual) to search known locations without crashing.

This addresses the same class of issue fixed in Textual 0.3.1:
https://github.com/gonzalezreal/textual/pull/21
This commit is contained in:
Haku 🐉 2026-01-26 23:43:50 +00:00
parent 343882d45c
commit a7e4a26d3d

View File

@ -123,17 +123,51 @@ enum DeviceModelCatalog {
private static func locateResourceBundle() -> Bundle? {
// Prefer main bundle (packaged app), then module bundle (SwiftPM/tests).
// Accessing Bundle.module in the packaged app can crash if the bundle isn't where SwiftPM expects it.
if let bundle = self.bundleIfContainsDeviceModels(Bundle.main) {
return bundle
}
if let bundle = self.bundleIfContainsDeviceModels(Bundle.module) {
return bundle
// Try to locate the module bundle manually to avoid fatalError from Bundle.module
// when the bundle isn't where SwiftPM expects it (e.g., in packaged .app bundles).
if let bundle = locateModuleBundleSafely() {
if let checked = self.bundleIfContainsDeviceModels(bundle) {
return checked
}
}
return nil
}
private static func locateModuleBundleSafely() -> Bundle? {
let bundleName = "Clawdbot_Clawdbot"
let candidates: [URL?] = [
Bundle.main.resourceURL,
Bundle.main.bundleURL,
Bundle(for: BundleLocator.self).resourceURL,
Bundle(for: BundleLocator.self).bundleURL,
]
for candidate in candidates {
guard let baseURL = candidate else { continue }
// Direct path
let directURL = baseURL.appendingPathComponent("\(bundleName).bundle")
if let bundle = Bundle(url: directURL) {
return bundle
}
// Inside Resources/
let resourcesURL = baseURL
.appendingPathComponent("Resources")
.appendingPathComponent("\(bundleName).bundle")
if let bundle = Bundle(url: resourcesURL) {
return bundle
}
}
return nil
}
private static func bundleIfContainsDeviceModels(_ bundle: Bundle) -> Bundle? {
if bundle.url(
forResource: "ios-device-identifiers",
@ -186,3 +220,6 @@ enum DeviceModelCatalog {
}
}
}
// Helper class for bundle lookup via Bundle(for:)
private final class BundleLocator {}