diff --git a/Sources/Models/AppBundle.swift b/Sources/Models/AppBundle.swift index acf0c02..4a8ca20 100644 --- a/Sources/Models/AppBundle.swift +++ b/Sources/Models/AppBundle.swift @@ -117,13 +117,14 @@ public struct AppBundle: Sendable, Identifiable, Hashable, Equatable { } private func loadExtensions() -> Set { - let pluginsURL = fileURL.appendingPathComponent("PlugIns") - guard let contents = try? FileManager.default.contentsOfDirectory( - at: pluginsURL, - includingPropertiesForKeys: nil, - options: [.skipsHiddenFiles] - ) else { - return [] + // App extensions live in PlugIns/; ExtensionKit extensions (EXAppExtensionAttributes, + // e.g. PhotoKit background upload) live in Extensions/. + let contents = ["PlugIns", "Extensions"].flatMap { folder in + (try? FileManager.default.contentsOfDirectory( + at: fileURL.appendingPathComponent(folder), + includingPropertiesForKeys: nil, + options: [.skipsHiddenFiles] + )) ?? [] } let extensions = contents .filter { $0.pathExtension.lowercased() == "appex" } diff --git a/Tests/SideSignTests/SideSignTests.swift b/Tests/SideSignTests/SideSignTests.swift index 81e54fe..3f15d7d 100644 --- a/Tests/SideSignTests/SideSignTests.swift +++ b/Tests/SideSignTests/SideSignTests.swift @@ -6,6 +6,7 @@ // Copyright © 2026 SideSign. All rights reserved. // +import Foundation import Testing @testable import SideSign import CodeSignKit @@ -98,4 +99,26 @@ struct SideSignTests { let decompressed = try reader.readCurrentFile() #expect(decompressed == testContent) } + + @Test + func appExtensionsIncludeExtensionKitExtensions() throws { + let tempDir = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) + defer { try? FileManager.default.removeItem(at: tempDir) } + + func makeBundle(_ url: URL, identifier: String) throws { + try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true) + let plist: [String: Any] = ["CFBundleIdentifier": identifier, "CFBundleName": url.deletingPathExtension().lastPathComponent] + let data = try PropertyListSerialization.data(fromPropertyList: plist, format: .xml, options: 0) + try data.write(to: url.appendingPathComponent("Info.plist")) + } + + let appURL = tempDir.appendingPathComponent("Test.app") + try makeBundle(appURL, identifier: "com.example.test") + // Classic app extension (NSExtension) and ExtensionKit extension (EXAppExtensionAttributes). + try makeBundle(appURL.appendingPathComponent("PlugIns/Widget.appex"), identifier: "com.example.test.widget") + try makeBundle(appURL.appendingPathComponent("Extensions/Upload.appex"), identifier: "com.example.test.upload") + + let app = try #require(AppBundle(fileURL: appURL)) + #expect(Set(app.appExtensions.map(\.bundleIdentifier)) == ["com.example.test.widget", "com.example.test.upload"]) + } }