native macOS codings agent orchestrator
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

Move repo settings files to ~/.supacode/repo with legacy migration

onevcat ea9259f8 2ab70fd7

+163 -5
+26 -2
supacode/Features/Settings/BusinessLogic/OnevcatRepositorySettingsKey.swift
··· 25 25 ) { 26 26 @Dependency(\.repositoryLocalSettingsStorage) var repositoryLocalSettingsStorage 27 27 let settingsURL = SupacodePaths.onevcatRepositorySettingsURL(for: rootURL) 28 + let decoder = JSONDecoder() 28 29 if let localData = try? repositoryLocalSettingsStorage.load(settingsURL) { 29 - let decoder = JSONDecoder() 30 30 if let settings = try? decoder.decode(OnevcatRepositorySettings.self, from: localData) { 31 31 continuation.resume(returning: settings.normalized()) 32 32 return 33 33 } 34 34 let path = settingsURL.path(percentEncoded: false) 35 35 SupaLogger("Settings").warning( 36 - "Unable to decode onevcat repository settings at \(path); using defaults." 36 + "Unable to decode onevcat repository settings at \(path); trying legacy settings." 37 + ) 38 + } 39 + 40 + let legacySettingsURL = SupacodePaths.legacyOnevcatRepositorySettingsURL(for: rootURL) 41 + if let legacyData = try? repositoryLocalSettingsStorage.load(legacySettingsURL) { 42 + if let legacySettings = try? decoder.decode(OnevcatRepositorySettings.self, from: legacyData) { 43 + let normalized = legacySettings.normalized() 44 + do { 45 + let encoder = JSONEncoder() 46 + encoder.outputFormatting = [.prettyPrinted, .sortedKeys] 47 + let data = try encoder.encode(normalized) 48 + try repositoryLocalSettingsStorage.save(data, settingsURL) 49 + } catch { 50 + let path = settingsURL.path(percentEncoded: false) 51 + SupaLogger("Settings").warning( 52 + "Unable to write onevcat repository settings to \(path): \(error.localizedDescription)" 53 + ) 54 + } 55 + continuation.resume(returning: normalized) 56 + return 57 + } 58 + let path = legacySettingsURL.path(percentEncoded: false) 59 + SupaLogger("Settings").warning( 60 + "Unable to decode legacy onevcat repository settings at \(path); using defaults." 37 61 ) 38 62 } 39 63
+25 -2
supacode/Features/Settings/BusinessLogic/RepositorySettingsKey.swift
··· 25 25 ) { 26 26 @Dependency(\.repositoryLocalSettingsStorage) var repositoryLocalSettingsStorage 27 27 let repositorySettingsURL = SupacodePaths.repositorySettingsURL(for: rootURL) 28 + let decoder = JSONDecoder() 28 29 if let localData = try? repositoryLocalSettingsStorage.load(repositorySettingsURL) { 29 - let decoder = JSONDecoder() 30 30 if let settings = try? decoder.decode(RepositorySettings.self, from: localData) { 31 31 continuation.resume(returning: settings) 32 32 return 33 33 } 34 34 let path = repositorySettingsURL.path(percentEncoded: false) 35 35 SupaLogger("Settings").warning( 36 - "Unable to decode repository settings at \(path); migrating from global settings." 36 + "Unable to decode repository settings at \(path); trying legacy and global migrations." 37 + ) 38 + } 39 + 40 + let legacySettingsURL = SupacodePaths.legacyRepositorySettingsURL(for: rootURL) 41 + if let legacyData = try? repositoryLocalSettingsStorage.load(legacySettingsURL) { 42 + if let legacySettings = try? decoder.decode(RepositorySettings.self, from: legacyData) { 43 + do { 44 + let encoder = JSONEncoder() 45 + encoder.outputFormatting = [.prettyPrinted, .sortedKeys] 46 + let data = try encoder.encode(legacySettings) 47 + try repositoryLocalSettingsStorage.save(data, repositorySettingsURL) 48 + } catch { 49 + let path = repositorySettingsURL.path(percentEncoded: false) 50 + SupaLogger("Settings").warning( 51 + "Unable to write migrated repository settings to \(path): \(error.localizedDescription)" 52 + ) 53 + } 54 + continuation.resume(returning: legacySettings) 55 + return 56 + } 57 + let path = legacySettingsURL.path(percentEncoded: false) 58 + SupaLogger("Settings").warning( 59 + "Unable to decode legacy repository settings at \(path); migrating from global settings." 37 60 ) 38 61 } 39 62
+28 -1
supacode/Support/SupacodePaths.swift
··· 6 6 .appending(path: ".supacode", directoryHint: .isDirectory) 7 7 } 8 8 9 + static var repositorySettingsDirectory: URL { 10 + baseDirectory.appending(path: "repo", directoryHint: .isDirectory) 11 + } 12 + 9 13 static var reposDirectory: URL { 10 14 baseDirectory.appending(path: "repos", directoryHint: .isDirectory) 11 15 } ··· 20 24 } 21 25 22 26 static func repositorySettingsURL(for rootURL: URL) -> URL { 27 + repositorySettingsDirectory(for: rootURL) 28 + .appending(path: "supacode.json", directoryHint: .notDirectory) 29 + } 30 + 31 + static func onevcatRepositorySettingsURL(for rootURL: URL) -> URL { 32 + repositorySettingsDirectory(for: rootURL) 33 + .appending(path: "supacode.onevcat.json", directoryHint: .notDirectory) 34 + } 35 + 36 + static func legacyRepositorySettingsURL(for rootURL: URL) -> URL { 23 37 rootURL.standardizedFileURL.appending(path: "supacode.json", directoryHint: .notDirectory) 24 38 } 25 39 26 - static func onevcatRepositorySettingsURL(for rootURL: URL) -> URL { 40 + static func legacyOnevcatRepositorySettingsURL(for rootURL: URL) -> URL { 27 41 rootURL.standardizedFileURL.appending(path: "supacode.onevcat.json", directoryHint: .notDirectory) 42 + } 43 + 44 + private static func repositorySettingsDirectory(for rootURL: URL) -> URL { 45 + let name = repositorySettingsDirectoryName(for: rootURL) 46 + return repositorySettingsDirectory.appending(path: name, directoryHint: .isDirectory) 28 47 } 29 48 30 49 private static func repositoryDirectoryName(for rootURL: URL) -> String { ··· 36 55 return "_" 37 56 } 38 57 return trimmed.replacing("/", with: "_") 58 + } 59 + return repoName 60 + } 61 + 62 + private static func repositorySettingsDirectoryName(for rootURL: URL) -> String { 63 + let repoName = rootURL.standardizedFileURL.lastPathComponent 64 + if repoName.isEmpty || repoName == "/" { 65 + return "_" 39 66 } 40 67 return repoName 41 68 }
+38
supacodeTests/OnevcatRepositorySettingsKeyTests.swift
··· 59 59 let decoded = try JSONDecoder().decode(OnevcatRepositorySettings.self, from: localData) 60 60 #expect(decoded == customSettings) 61 61 } 62 + 63 + @Test(.dependencies) func loadMigratesLegacyRepositoryRootOnevcatFile() throws { 64 + let localStorage = RepositoryLocalSettingsTestStorage() 65 + let rootURL = URL(fileURLWithPath: "/tmp/repo") 66 + let localURL = SupacodePaths.onevcatRepositorySettingsURL(for: rootURL) 67 + let legacyURL = SupacodePaths.legacyOnevcatRepositorySettingsURL(for: rootURL) 68 + 69 + let customSettings = OnevcatRepositorySettings( 70 + customCommands: [ 71 + OnevcatCustomCommand( 72 + title: "Legacy", 73 + systemImage: "terminal", 74 + command: "echo legacy", 75 + execution: .shellScript, 76 + shortcut: OnevcatCustomShortcut( 77 + key: "u", 78 + modifiers: OnevcatCustomShortcutModifiers(command: true) 79 + ) 80 + ), 81 + ] 82 + ) 83 + let encoder = JSONEncoder() 84 + encoder.outputFormatting = [.prettyPrinted, .sortedKeys] 85 + try localStorage.save(try encoder.encode(customSettings), at: legacyURL) 86 + 87 + let loaded = withDependencies { 88 + $0.repositoryLocalSettingsStorage = localStorage.storage 89 + } operation: { 90 + @Shared(.onevcatRepositorySettings(rootURL)) var settings: OnevcatRepositorySettings 91 + return settings 92 + } 93 + 94 + #expect(loaded == customSettings) 95 + 96 + let localData = try #require(localStorage.data(at: localURL)) 97 + let decoded = try JSONDecoder().decode(OnevcatRepositorySettings.self, from: localData) 98 + #expect(decoded == customSettings) 99 + } 62 100 }
+18
supacodeTests/RepositoryPathsTests.swift
··· 41 41 42 42 #expect(firstDirectory != secondDirectory) 43 43 } 44 + 45 + @Test func repositorySettingsURLUsesSupacodeRepoDirectory() { 46 + let root = URL(fileURLWithPath: "/tmp/work/repo-alpha") 47 + let settingsURL = SupacodePaths.repositorySettingsURL(for: root) 48 + 49 + #expect(settingsURL.lastPathComponent == "supacode.json") 50 + #expect(settingsURL.deletingLastPathComponent().lastPathComponent == "repo-alpha") 51 + #expect(settingsURL.deletingLastPathComponent().deletingLastPathComponent().lastPathComponent == "repo") 52 + } 53 + 54 + @Test func onevcatRepositorySettingsURLUsesSupacodeRepoDirectory() { 55 + let root = URL(fileURLWithPath: "/tmp/work/repo-alpha/.bare") 56 + let settingsURL = SupacodePaths.onevcatRepositorySettingsURL(for: root) 57 + 58 + #expect(settingsURL.lastPathComponent == "supacode.onevcat.json") 59 + #expect(settingsURL.deletingLastPathComponent().lastPathComponent == ".bare") 60 + #expect(settingsURL.deletingLastPathComponent().deletingLastPathComponent().lastPathComponent == "repo") 61 + } 44 62 }
+28
supacodeTests/RepositorySettingsKeyTests.swift
··· 207 207 #expect(localDecoded == globalSettings) 208 208 } 209 209 210 + @Test(.dependencies) func loadMigratesLegacyRepositoryRootFile() throws { 211 + let globalStorage = SettingsTestStorage() 212 + let localStorage = RepositoryLocalSettingsTestStorage() 213 + let rootURL = URL(fileURLWithPath: "/tmp/repo") 214 + let settingsFileURL = URL(fileURLWithPath: "/tmp/supacode-settings-\(UUID().uuidString).json") 215 + let localURL = SupacodePaths.repositorySettingsURL(for: rootURL) 216 + let legacyURL = SupacodePaths.legacyRepositorySettingsURL(for: rootURL) 217 + var legacySettings = RepositorySettings.default 218 + legacySettings.runScript = "echo from-legacy" 219 + 220 + try localStorage.save(encode(legacySettings), at: legacyURL) 221 + 222 + let loaded = withDependencies { 223 + $0.settingsFileStorage = globalStorage.storage 224 + $0.settingsFileURL = settingsFileURL 225 + $0.repositoryLocalSettingsStorage = localStorage.storage 226 + } operation: { 227 + @Shared(.repositorySettings(rootURL)) var repositorySettings: RepositorySettings 228 + return repositorySettings 229 + } 230 + 231 + #expect(loaded == legacySettings) 232 + 233 + let localData = try #require(localStorage.data(at: localURL)) 234 + let localDecoded = try JSONDecoder().decode(RepositorySettings.self, from: localData) 235 + #expect(localDecoded == legacySettings) 236 + } 237 + 210 238 @Test(.dependencies) func saveWritesLocalWhenLocalFileExists() throws { 211 239 let globalStorage = SettingsTestStorage() 212 240 let localStorage = RepositoryLocalSettingsTestStorage()