@@ -61,6 +61,49 @@ final class PluginManager {
6161
6262 private init ( ) { }
6363
64+ // MARK: - Registry Metadata
65+
66+ private struct RegistryMetadata : Codable {
67+ let version : String
68+ let pluginId : String
69+ }
70+
71+ nonisolated private static func metadataURL( for pluginURL: URL ) -> URL {
72+ pluginURL. deletingLastPathComponent ( )
73+ . appendingPathComponent ( pluginURL. lastPathComponent + " .metadata.json " )
74+ }
75+
76+ nonisolated private static func readRegistryVersion( for pluginURL: URL ) -> String ? {
77+ let url = metadataURL ( for: pluginURL)
78+ guard let data = try ? Data ( contentsOf: url) ,
79+ let metadata = try ? JSONDecoder ( ) . decode ( RegistryMetadata . self, from: data) else {
80+ return nil
81+ }
82+ return metadata. version
83+ }
84+
85+ func updatePluginVersion( id: String , version: String ) {
86+ if let index = plugins. firstIndex ( where: { $0. id == id } ) {
87+ plugins [ index] . version = version
88+ }
89+ }
90+
91+ func saveRegistryMetadata( version: String , pluginId: String , pluginURL: URL ) {
92+ let metadata = RegistryMetadata ( version: version, pluginId: pluginId)
93+ let url = Self . metadataURL ( for: pluginURL)
94+ do {
95+ let data = try JSONEncoder ( ) . encode ( metadata)
96+ try data. write ( to: url, options: . atomic)
97+ } catch {
98+ Self . logger. error ( " Failed to save registry metadata for \( pluginId) : \( error. localizedDescription) " )
99+ }
100+ }
101+
102+ private func removeRegistryMetadata( for pluginURL: URL ) {
103+ let url = Self . metadataURL ( for: pluginURL)
104+ try ? FileManager . default. removeItem ( at: url)
105+ }
106+
64107 private func migrateDisabledPluginsKey( ) {
65108 let defaults = UserDefaults . standard
66109 if let legacy = defaults. stringArray ( forKey: Self . legacyDisabledPluginsKey) {
@@ -156,13 +199,14 @@ final class PluginManager {
156199 }
157200
158201 let driverType = principalClass as? any DriverPlugin . Type
202+ let version = readRegistryVersion ( for: entry. url) ?? principalClass. pluginVersion
159203 let loaded = LoadedBundle (
160204 url: entry. url,
161205 source: entry. source,
162206 bundle: bundle,
163207 principalClassName: NSStringFromClass ( principalClass) ,
164208 pluginName: principalClass. pluginName,
165- pluginVersion: principalClass . pluginVersion ,
209+ pluginVersion: version ,
166210 pluginDescription: principalClass. pluginDescription,
167211 capabilities: principalClass. capabilities,
168212 databaseTypeId: driverType? . databaseTypeId,
@@ -359,13 +403,14 @@ final class PluginManager {
359403 let disabled = disabledPluginIds
360404
361405 let driverType = principalClass as? any DriverPlugin . Type
406+ let version = Self . readRegistryVersion ( for: url) ?? principalClass. pluginVersion
362407 let entry = PluginEntry (
363408 id: bundleId,
364409 bundle: bundle,
365410 url: url,
366411 source: source,
367412 name: principalClass. pluginName,
368- version: principalClass . pluginVersion ,
413+ version: version ,
369414 pluginDescription: principalClass. pluginDescription,
370415 capabilities: principalClass. capabilities,
371416 isEnabled: !disabled. contains ( bundleId) ,
@@ -1009,6 +1054,8 @@ final class PluginManager {
10091054 entry. bundle. unload ( )
10101055 plugins. remove ( at: index)
10111056
1057+ removeRegistryMetadata ( for: entry. url)
1058+
10121059 let fm = FileManager . default
10131060 if fm. fileExists ( atPath: entry. url. path) {
10141061 try fm. removeItem ( at: entry. url)
0 commit comments