Files
Moltbot/apps/shared/OpenClawKit/Sources/OpenClawKit/OpenClawKitResources.swift
2026-02-15 20:03:48 +00:00

84 lines
3.2 KiB
Swift

import Foundation
public enum OpenClawKitResources {
/// Resource bundle for OpenClawKit.
///
/// Locates the SwiftPM-generated resource bundle, checking multiple locations:
/// 1. Inside Bundle.main (packaged apps)
/// 2. Bundle.module (SwiftPM development/tests)
/// 3. Falls back to Bundle.main if not found (resource lookups will return nil)
///
/// This avoids a fatal crash when Bundle.module can't locate its resources
/// in packaged .app bundles where the resource bundle path differs from
/// SwiftPM's expectations.
public static let bundle: Bundle = locateBundle()
private static let bundleName = "OpenClawKit_OpenClawKit"
private static func locateBundle() -> Bundle {
// 1. Check inside Bundle.main (packaged apps copy resources here)
if let mainResourceURL = Bundle.main.resourceURL {
let bundleURL = mainResourceURL.appendingPathComponent("\(bundleName).bundle")
if let bundle = Bundle(url: bundleURL) {
return bundle
}
}
// 2. Check Bundle.main directly for embedded resources
if Bundle.main.url(forResource: "tool-display", withExtension: "json") != nil {
return Bundle.main
}
// 3. Try Bundle.module (works in SwiftPM development/tests)
// Wrap in a function to defer the fatalError until actually called
if let moduleBundle = loadModuleBundleSafely() {
return moduleBundle
}
// 4. Fallback: return Bundle.main (resource lookups will return nil gracefully)
return Bundle.main
}
private static func loadModuleBundleSafely() -> Bundle? {
// Bundle.module is generated by SwiftPM and will fatalError if not found.
// We check likely locations manually to avoid the crash.
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 }
// SwiftPM often places the resource bundle next to (or near) the test runner bundle,
// not inside it. Walk up a few levels and check common container paths.
var roots: [URL] = []
roots.append(baseURL)
roots.append(baseURL.appendingPathComponent("Resources"))
roots.append(baseURL.appendingPathComponent("Contents/Resources"))
var current = baseURL
for _ in 0 ..< 5 {
current = current.deletingLastPathComponent()
roots.append(current)
roots.append(current.appendingPathComponent("Resources"))
roots.append(current.appendingPathComponent("Contents/Resources"))
}
for root in roots {
let bundleURL = root.appendingPathComponent("\(bundleName).bundle")
if let bundle = Bundle(url: bundleURL) {
return bundle
}
}
}
return nil
}
}
// Helper class for bundle lookup via Bundle(for:)
private final class BundleLocator {}